From e35b15ca547c95fcea4af8795fca6ce53a377add Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Mon, 14 Sep 2026 09:00:37 -0700 Subject: [PATCH 01/26] Show namespace placement and structured memory use --- .../ai/context-engine/agent-memory/_index.md | 6 +- .../api-reference/openapi-agent-memory.json | 1876 ++++++++++++++++- .../agent-memory/developer-guide.md | 92 +- .../agent-memory/python-sdk-quickstart.md | 99 +- .../agent-memory/rest-api-quickstart.md | 87 +- .../agent-memory/typescript-sdk-quickstart.md | 100 +- .../agent-memory/self-managed/api-examples.md | 28 +- 7 files changed, 2146 insertions(+), 142 deletions(-) diff --git a/content/develop/ai/context-engine/agent-memory/_index.md b/content/develop/ai/context-engine/agent-memory/_index.md index 153a0d221d..50f21de1d2 100644 --- a/content/develop/ai/context-engine/agent-memory/_index.md +++ b/content/develop/ai/context-engine/agent-memory/_index.md @@ -91,7 +91,7 @@ Agent: Good to know! I'll help you find some vegetarian-friendly restaurants in

Custom memory types

-

Stores domain-specific information in a custom trip_preference type with fields such as destination, travel_period, and dietary_requirement.

+

Stores domain-specific information in a custom trip_preference type with fields such as destinations, travel_period, and dietary_requirements.

@@ -114,7 +114,9 @@ Get started with Redis Agent Memory on Redis Cloud or join the private preview f ### Choose a quickstart -After your Redis Agent Memory service is ready, choose a client. Each quickstart follows the same travel planning scenario through session memory, automatic extraction, summarization, custom memory types, and sensitive-data exclusions. +After your Redis Agent Memory service is ready, choose a client. Each quickstart follows the same travel planning scenario through namespaces, session memory, automatic extraction, summarization, custom memory types, and sensitive-data exclusions. + +Use the developer guide to [organize memories with namespaces]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#organize-memories-with-namespaces" >}}) and [use custom memory types]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#define-custom-memory-types" >}}).
diff --git a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json index bbab8e30d1..f14395130b 100644 --- a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json +++ b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json @@ -21,10 +21,7 @@ } } } - }, - "tags": [ - "Service Health" - ] + } } }, "/v1/stores/{storeId}/health": { @@ -117,6 +114,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -147,10 +154,7 @@ } } } - }, - "tags": [ - "Store Health" - ] + } } }, "/v1/stores/{storeId}/long-term-memory": { @@ -253,6 +257,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -283,10 +297,7 @@ } } } - }, - "tags": [ - "Long-Term Memory" - ] + } }, "post": { "description": "Creates long-term memories in bulk.", @@ -296,6 +307,18 @@ "application/json": { "schema": { "$ref": "#/components/schemas/BulkCreateLongTermMemoriesRequestContent" + }, + "example": { + "memories": [ + { + "id": "mem-1", + "text": "The user prefers dark mode", + "ownerId": "user-1", + "namespaceRef": { + "namespaceId": "ns-product" + } + } + ] } } }, @@ -387,6 +410,177 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "MemoryFailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + } + }, + "/v1/stores/{storeId}/long-term-memory/move": { + "post": { + "description": "Moves long-term memories to one existing namespace resource. Returns 409 when the destination namespace is archived.", + "operationId": "MoveLongTermMemories", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MoveLongTermMemoriesRequestContent" + }, + "example": { + "memoryIds": [ + "mem-1" + ], + "namespaceRef": { + "namespaceId": "ns-product" + } + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "MoveLongTermMemories 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MoveLongTermMemoriesResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -417,10 +611,7 @@ } } } - }, - "tags": [ - "Long-Term Memory" - ] + } } }, "/v1/stores/{storeId}/long-term-memory/search": { @@ -432,6 +623,14 @@ "application/json": { "schema": { "$ref": "#/components/schemas/SearchLongTermMemoryRequestContent" + }, + "example": { + "text": "preferred theme", + "filter": { + "namespaceRef": { + "eq": "ns-product" + } + } } } } @@ -522,12 +721,22 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -552,10 +761,7 @@ } } } - }, - "tags": [ - "Long-Term Memory" - ] + } } }, "/v1/stores/{storeId}/long-term-memory/{memoryId}": { @@ -661,6 +867,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -691,10 +907,7 @@ } } } - }, - "tags": [ - "Long-Term Memory" - ] + } }, "patch": { "description": "Partially updates a long-term memory by its ID.", @@ -807,12 +1020,22 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -837,10 +1060,7 @@ } } } - }, - "tags": [ - "Long-Term Memory" - ] + } } }, "/v1/stores/{storeId}/long-term-memory/{memoryId}/fields": { @@ -939,12 +1159,22 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -969,76 +1199,870 @@ } } } - }, - "tags": [ - "Long-Term Memory" - ] + } } }, - "/v1/stores/{storeId}/session-memory": { + "/v1/stores/{storeId}/namespaces": { "get": { - "description": "Returns a paginated list of session IDs for a store.", - "operationId": "ListSessions", + "description": "Lists namespace roots or direct children.", + "operationId": "ListNamespaces", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true }, { - "name": "limit", + "name": "parentId", "in": "query", - "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "description": "List direct children of this namespace. Omit to list roots.", "schema": { - "type": "integer", - "default": 100, - "maximum": 1000, - "minimum": 1, - "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", - "format": "int32" + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "List direct children of this namespace. Omit to list roots." } }, { - "name": "pageToken", + "name": "scope", "in": "query", - "description": "Opaque token from a previous response for the next page.", + "description": "Restrict root results to one scope. Cannot be combined with parentId.", "schema": { - "type": "string", - "description": "Opaque token from a previous response for the next page." + "$ref": "#/components/schemas/NamespaceScope" } }, { - "name": "filterOwnerId", + "name": "ownerId", "in": "query", - "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll.", + "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll." + "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots." } }, { - "name": "includeAll", + "name": "limit", "in": "query", - "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters.", + "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", "schema": { - "type": "boolean", - "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters." + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" } - } - ], - "responses": { - "200": { + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page." + } + } + ], + "responses": { + "200": { + "description": "ListNamespaces 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListNamespacesResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + }, + "post": { + "description": "Creates a namespace resource as a root, direct child, or explicit hierarchy path. Returns 409 when an active or archived namespace already occupies the requested location.", + "operationId": "CreateNamespace", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateNamespaceRequestContent" + }, + "example": { + "name": "product", + "scope": "SHARED" + } + } + } + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateNamespace 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateNamespaceResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + } + }, + "/v1/stores/{storeId}/namespaces/{namespaceId}": { + "delete": { + "description": "Deletes an empty leaf namespace. Returns 409 when the namespace has children or memory placements, or when it changed concurrently.", + "operationId": "DeleteNamespace", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "namespaceId", + "in": "path", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteNamespace 204 response" + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + }, + "get": { + "description": "Gets one namespace by its canonical ID.", + "operationId": "GetNamespace", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "namespaceId", + "in": "path", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetNamespace 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetNamespaceResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + }, + "patch": { + "description": "Renames or archives a namespace. Returns 409 when the namespace changed concurrently, an active or archived sibling already uses the requested name, or the namespace is archived.", + "operationId": "UpdateNamespace", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateNamespaceRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "namespaceId", + "in": "path", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateNamespace 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateNamespaceResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + } + }, + "/v1/stores/{storeId}/session-memory": { + "get": { + "description": "Returns a paginated list of session IDs for a store.", + "operationId": "ListSessions", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page." + } + }, + { + "name": "filterOwnerId", + "in": "query", + "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll." + } + }, + { + "name": "includeAll", + "in": "query", + "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters.", + "schema": { + "type": "boolean", + "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters." + } + } + ], + "responses": { + "200": { "description": "ListSessions 200 response", "content": { "application/json": { @@ -1108,6 +2132,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1138,10 +2172,7 @@ } } } - }, - "tags": [ - "Session Memory" - ] + } } }, "/v1/stores/{storeId}/session-memory/events": { @@ -1153,6 +2184,20 @@ "application/json": { "schema": { "$ref": "#/components/schemas/AddSessionEventRequestContent" + }, + "example": { + "sessionId": "session-user42-20240315", + "actorId": "user-42", + "role": "USER", + "createdAt": "2024-03-15T10:00:00Z", + "content": [ + { + "text": "What were the action items from last week's meeting?" + } + ], + "namespaceRef": { + "namespaceId": "ns-product" + } } } }, @@ -1244,6 +2289,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1274,10 +2329,7 @@ } } } - }, - "tags": [ - "Session Memory" - ] + } } }, "/v1/stores/{storeId}/session-memory/{sessionId}": { @@ -1376,6 +2428,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1406,10 +2468,7 @@ } } } - }, - "tags": [ - "Session Memory" - ] + } }, "get": { "description": "Returns the session memory for a session.", @@ -1439,7 +2498,8 @@ "pattern": "^[a-zA-Z0-9-]+$", "description": "The session ID." }, - "required": true + "required": true, + "example": "session-user42-20240315" }, { "name": "includeSummarisedEvents", @@ -1522,6 +2582,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1552,10 +2622,7 @@ } } } - }, - "tags": [ - "Session Memory" - ] + } } }, "/v1/stores/{storeId}/session-memory/{sessionId}/events/{eventId}": { @@ -1667,6 +2734,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1697,10 +2774,7 @@ } } } - }, - "tags": [ - "Session Memory" - ] + } }, "get": { "description": "Returns a single event from a session by event ID.", @@ -1817,6 +2891,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1847,10 +2931,7 @@ } } } - }, - "tags": [ - "Session Memory" - ] + } } } }, @@ -1864,36 +2945,52 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional session ID. If omitted, the server generates a unique ID." + "description": "Optional session ID. If omitted, the server generates a unique ID.", + "example": "session-user42-20240315" }, "actorId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant." + "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant.", + "example": "user-42" }, "namespace": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional namespace for the session, used as the namespace of every long-term memory extracted from it.\n\nLike `ownerId`, this is a session-level value taken from the first event: the value sent with the first event of a session is stored on the session, and values sent with later events are ignored. Omit it to leave extracted memories without a namespace." + "description": "Optional deprecated namespace label for the session. This label does not create or resolve a namespace resource. Do not supply it with namespaceRef.\n\nLike `ownerId`, this is a session-level value taken from the first event: the value sent with the first event of a session is stored on the session, and values sent with later events are ignored. Omit it to leave extracted memories without a namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput", + "example": { + "namespaceId": "ns-product" + } }, "role": { - "$ref": "#/components/schemas/MessageRole" + "$ref": "#/components/schemas/MessageRole", + "example": "USER" }, "content": { "type": "array", "items": { "$ref": "#/components/schemas/Content" }, - "description": "The message content as an array of typed content parts." + "description": "The message content as an array of typed content parts.", + "example": [ + { + "text": "What were the action items from last week's meeting?" + } + ] }, "createdAt": { "type": "string", "description": "Client-supplied timestamp for when the event happened (UTC).", - "format": "date-time" + "format": "date-time", + "example": "2024-03-15T10:00:00Z" }, "metadata": { "description": "Optional metadata as any valid JSON value." @@ -1904,7 +3001,21 @@ "content", "createdAt", "role" - ] + ], + "example": { + "sessionId": "session-user42-20240315", + "actorId": "user-42", + "role": "USER", + "createdAt": "2024-03-15T10:00:00Z", + "content": [ + { + "text": "What were the action items from last week's meeting?" + } + ], + "namespaceRef": { + "namespaceId": "ns-product" + } + } }, "AddSessionEventResponseContent": { "type": "object", @@ -2191,6 +3302,45 @@ "id" ] }, + "ConflictErrorResponseContent": { + "type": "object", + "description": "The request conflicts with the current state of the resource.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 409, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/ConflictErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "ConflictErrorType": { + "type": "string", + "description": "Problem type URI for conflict errors.", + "enum": [ + "/errors/conflict" + ] + }, "Content": { "description": "A single content item in a message. Exactly one member is set.", "oneOf": [ @@ -2200,7 +3350,8 @@ "properties": { "text": { "type": "string", - "description": "Text content." + "description": "Text content.", + "example": "What were the action items from last week's meeting?" } }, "required": [ @@ -2218,13 +3369,15 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Client-provided memory record ID for idempotent creation." + "description": "Client-provided memory record ID for idempotent creation.", + "example": "mem-1" }, "text": { "type": "string", "maxLength": 50000, "minLength": 1, - "description": "The memory content (1-50000 chars)." + "description": "The memory content (1-50000 chars).", + "example": "The user prefers dark mode" }, "memoryType": { "type": "string", @@ -2245,14 +3398,22 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner ID associated with this memory (1-64 chars, alphanumeric and dashes)." + "description": "Owner ID associated with this memory (1-64 chars, alphanumeric and dashes).", + "example": "user-1" }, "namespace": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional namespace for logical grouping (1-64 chars, alphanumeric and dashes). Omit to create the record without a namespace. Records created here are explicit writes, so this value is taken as given; extraction instead inherits the source session's namespace." + "description": "Optional deprecated namespace label. It does not create or resolve a namespace resource. Omit it to create the record without a namespace, and do not supply it with namespaceRef.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput", + "example": { + "namespaceId": "ns-product" + } }, "topics": { "type": "array", @@ -2273,6 +3434,66 @@ "id", "ownerId", "text" + ], + "example": { + "id": "mem-1", + "text": "The user prefers dark mode", + "ownerId": "user-1", + "namespaceRef": { + "namespaceId": "ns-product" + } + } + }, + "CreateNamespaceRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One namespace segment. Use with scope for a root or parentId for a child.", + "example": "product" + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "A complete hierarchy path to create. Missing segments are created as needed. Mutually exclusive with name and parentId. The path does not encode scope." + }, + "scope": { + "$ref": "#/components/schemas/NamespaceScope", + "example": "SHARED" + }, + "parentId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Parent namespace for a direct child." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner for a personal root or path. Required for personal scope; omit for shared roots and children." + } + }, + "example": { + "name": "product", + "scope": "SHARED" + } + }, + "CreateNamespaceResponseContent": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" + } + }, + "required": [ + "namespace" ] }, "CreatedAtFilter": { @@ -2457,7 +3678,11 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping. Absent when the record has none: either it was written directly without one, or it was extracted from a session that has no namespace." + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" }, "topics": { "type": "array", @@ -2491,6 +3716,17 @@ "updatedAt" ] }, + "GetNamespaceResponseContent": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" + } + }, + "required": [ + "namespace" + ] + }, "GetSessionEventResponseContent": { "type": "object", "description": "Response containing a single requested session event.", @@ -2526,7 +3762,11 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The namespace of the session, set from the namespace of the first event. Absent when the first event did not supply one; long-term memories extracted from this session then carry no namespace." + "description": "Deprecated namespace label for the session, set from the first event. Absent when the session uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" }, "events": { "type": "array", @@ -2564,6 +3804,24 @@ "healthy" ] }, + "ListNamespacesResponseContent": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Namespace" + } + }, + "nextPageToken": { + "type": "string", + "description": "Opaque token for fetching the next page. Omitted when no more results." + } + }, + "required": [ + "items" + ] + }, "ListSessionsResponseContent": { "type": "object", "description": "Paginated list of session IDs for a store.", @@ -2607,6 +3865,16 @@ "namespace": { "$ref": "#/components/schemas/NamespaceFilter" }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefFilter", + "example": { + "eq": "ns-product" + } + }, + "unnamespaced": { + "type": "boolean", + "description": "When true, matches only records that have neither a deprecated namespace label nor a namespace resource placement. This scope is always combined with every other filter condition. Do not supply namespace or namespaceRef with this field." + }, "topics": { "$ref": "#/components/schemas/TopicsFilter" }, @@ -2621,6 +3889,48 @@ } } }, + "MemoryFailedDependencyErrorResponseContent": { + "type": "object", + "description": "A resource or model-provider dependency required by Agent Memory could not complete the request.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 424, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/MemoryFailedDependencyErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "MemoryFailedDependencyErrorType": { + "type": "string", + "description": "Problem type URI for Agent Memory failed-dependency errors.", + "enum": [ + "/errors/resource-unavailable", + "/errors/database-out-of-memory", + "/errors/invalid-model-credentials", + "/errors/model-quota-exceeded" + ] + }, "MemoryRecord": { "type": "object", "description": "A long-term memory record.", @@ -2662,7 +3972,11 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping. Absent when the record has none: either it was written directly without one, or it was extracted from a session that has no namespace." + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" }, "topics": { "type": "array", @@ -2733,23 +4047,155 @@ "SYSTEM" ] }, + "MoveLongTermMemoriesRequestContent": { + "type": "object", + "properties": { + "memoryIds": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "maxItems": 100, + "minItems": 1 + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" + } + }, + "required": [ + "memoryIds", + "namespaceRef" + ], + "example": { + "memoryIds": [ + "mem-1" + ], + "namespaceRef": { + "namespaceId": "ns-product" + } + } + }, + "MoveLongTermMemoriesResponseContent": { + "type": "object", + "properties": { + "moved": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + } + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkOperationError" + } + } + }, + "required": [ + "moved" + ] + }, + "Namespace": { + "type": "object", + "description": "A namespace hierarchy record.", + "properties": { + "namespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One case-sensitive namespace path segment." + }, + "parentId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "rootId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "ownerId": { + "type": "string", + "minLength": 1, + "description": "An opaque application-user ID selected by the platform for a namespace root." + }, + "scope": { + "$ref": "#/components/schemas/NamespaceScope" + }, + "state": { + "$ref": "#/components/schemas/NamespaceState" + }, + "revision": { + "type": "integer", + "format": "int64" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "Current case-sensitive path, derived from the live hierarchy." + } + }, + "required": [ + "createdAt", + "name", + "namespaceId", + "path", + "revision", + "rootId", + "scope", + "state", + "updatedAt" + ] + }, "NamespaceFilter": { "type": "object", - "description": "Filter by namespace. Matching is case-insensitive.\n\nNamespace is optional, so records without one need care. The positive operators (`eq`, `in`, `all`) require the field to be present and therefore never match a record that has no namespace — including memories extracted from a session that has none. `ne` is a plain negation and *does* match those records, so use `eq` or `in` when you need strict namespace isolation rather than `ne` against the namespaces you want to exclude.\n\nOmit this filter to search across every namespace, and use it to keep recall inside one project, workspace, team, environment, or tenant.", + "description": "Filter by namespace. Matching is case-insensitive.\n\nNamespace is optional, so records without one need care. The positive operators (`eq`, `in`, `all`) require the field to be present and therefore never match a record that has no namespace \u2014 including memories extracted from a session that has none. `ne` is a plain negation and *does* match those records, so use `eq` or `in` when you need strict namespace isolation rather than `ne` against the namespaces you want to exclude.\n\nOmit this filter to search across every namespace, and use it to keep recall inside one project, workspace, team, environment, or tenant.", "properties": { "eq": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value." + "description": "Equals the given value.", + "deprecated": true }, "ne": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Not equal to the given value." + "description": "Not equal to the given value.", + "deprecated": true }, "in": { "type": "array", @@ -2758,7 +4204,8 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping (1-64 chars, alphanumeric and dashes).\n\nThis is the shared namespace format for both session memory and long-term memory: sessions carry it as session-level state, and long-term memory records carry it per record. The same constraints apply everywhere it is used.\n\nNamespace is always optional; a record without one is valid. Applications use it to isolate memories by project, workspace, team, environment, or tenant. See `NamespaceFilter` for how records without a namespace behave under search.\n\nA namespace reaches a record one of two ways. Records written directly carry whatever namespace the caller sets. Records produced by extraction inherit the namespace of the session they were extracted from, which is fixed when that session is created; a session with no namespace yields records with no namespace. Existing records are never backfilled when a namespace is introduced later." + "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true }, "description": "Matches any of the given values." }, @@ -2769,12 +4216,112 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping (1-64 chars, alphanumeric and dashes).\n\nThis is the shared namespace format for both session memory and long-term memory: sessions carry it as session-level state, and long-term memory records carry it per record. The same constraints apply everywhere it is used.\n\nNamespace is always optional; a record without one is valid. Applications use it to isolate memories by project, workspace, team, environment, or tenant. See `NamespaceFilter` for how records without a namespace behave under search.\n\nA namespace reaches a record one of two ways. Records written directly carry whatever namespace the caller sets. Records produced by extraction inherit the namespace of the session they were extracted from, which is fixed when that session is created; a session with no namespace yields records with no namespace. Existing records are never backfilled when a namespace is introduced later." + "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true }, "description": "Matches all of the given values." } } }, + "NamespaceRef": { + "type": "object", + "description": "Reference to a namespace resource with its stable identifier and current display data.", + "properties": { + "namespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable namespace resource identifier." + }, + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Current namespace name." + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "Current namespace path." + } + }, + "required": [ + "name", + "namespaceId", + "path" + ] + }, + "NamespaceRefFilter": { + "type": "object", + "description": "Filter by stable namespace resource identifier. Supports exact or any-of matching and matches only resource-backed records, never deprecated namespace labels.", + "properties": { + "eq": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Equals the given value.", + "example": "ns-product" + }, + "in": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "description": "Matches any of the given values." + } + } + }, + "NamespaceRefInput": { + "type": "object", + "description": "Reference to an existing namespace resource for a write or namespace-scoped operation.", + "properties": { + "namespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable namespace resource identifier.", + "example": "ns-product" + } + }, + "required": [ + "namespaceId" + ], + "example": { + "namespaceId": "ns-product" + } + }, + "NamespaceScope": { + "type": "string", + "description": "The root scope of a namespace hierarchy.", + "enum": [ + "PERSONAL", + "SHARED" + ] + }, + "NamespaceState": { + "type": "string", + "description": "Whether a namespace accepts new children and placements.", + "enum": [ + "ACTIVE", + "ARCHIVED" + ] + }, + "NamespaceUpdateState": { + "type": "string", + "description": "The archive-only state transition accepted by UpdateNamespace.", + "enum": [ + "ARCHIVED" + ] + }, "NotFoundErrorResponseContent": { "type": "object", "description": "The requested resource does not exist.", @@ -2895,13 +4442,53 @@ "/errors/payload-too-large" ] }, + "ResourceSuspendedErrorResponseContent": { + "type": "object", + "description": "The requested resource exists but is suspended by an administrator.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 423, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/ResourceSuspendedErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "ResourceSuspendedErrorType": { + "type": "string", + "description": "Problem type URI for suspended resources.", + "enum": [ + "/errors/resource-suspended" + ] + }, "SearchLongTermMemoryRequestContent": { "type": "object", "description": "Semantic search request for long-term memory records.", "properties": { "text": { "type": "string", - "description": "Text to use for semantic search." + "description": "Text to use for semantic search.", + "example": "preferred theme" }, "similarityThreshold": { "type": "number", @@ -2928,6 +4515,14 @@ "type": "string", "description": "Opaque token from a previous response for the next page." } + }, + "example": { + "text": "preferred theme", + "filter": { + "namespaceRef": { + "eq": "ns-product" + } + } } }, "SearchLongTermMemoryResponseContent": { @@ -3299,7 +4894,11 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The caller's namespace. The update is rejected with 409 unless the record's stored namespace matches exactly.\n\nOmit it to assert that the record has no namespace — an omitted value matches only records stored without one, it does not mean \"any namespace\". The check is applied atomically with the update, so a record cannot be re-filed into another namespace between the check and the write." + "description": "The caller's current deprecated namespace label. The update is rejected with 409 unless the record still stores that label. Do not supply it with namespaceRef.\n\nOmit both namespace and namespaceRef to assert that the record has no namespace placement. Omission does not mean \"any namespace\".", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" }, "text": { "type": "string", @@ -3345,7 +4944,11 @@ }, "namespace": { "type": "string", - "description": "Updated namespace. When provided, must be 1-64 chars alphanumeric and dashes; omit the field or send an empty string to clear.\n\nThis is the only way to change the namespace of an existing record, including one produced by extraction: a record inherits its session's namespace at creation and is not revised afterwards, so use this to re-file a record or to add a namespace to one created before the session had any." + "description": "Updated deprecated namespace label. When provided, it must be 1-64 alphanumeric characters or dashes. Send an empty string to clear it.\nDo not supply it with namespaceRef.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" }, "ownerId": { "type": "string", @@ -3398,7 +5001,11 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping. Absent when the record has none: either it was written directly without one, or it was extracted from a session that has no namespace." + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" }, "topics": { "type": "array", @@ -3431,7 +5038,44 @@ "text", "updatedAt" ] + }, + "UpdateNamespaceRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One case-sensitive namespace path segment." + }, + "state": { + "$ref": "#/components/schemas/NamespaceUpdateState" + } + } + }, + "UpdateNamespaceResponseContent": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" + } + }, + "required": [ + "namespace" + ] } + }, + "securitySchemes": { + "apiKey": { + "type": "http", + "scheme": "bearer" + } + } + }, + "security": [ + { + "apiKey": [] } - } + ] } diff --git a/content/develop/ai/context-engine/agent-memory/developer-guide.md b/content/develop/ai/context-engine/agent-memory/developer-guide.md index f0520ec30d..023c6ba9f1 100644 --- a/content/develop/ai/context-engine/agent-memory/developer-guide.md +++ b/content/develop/ai/context-engine/agent-memory/developer-guide.md @@ -49,10 +49,76 @@ Follow the [Redis Cloud setup guide](/content/operate/iris/agent-memory/create-s | `sessionId` | Identifies a conversation or interaction session. | | `actorId` | Identifies the actor that produced a session event. | | `ownerId` | Identifies the user or entity associated with a long-term memory. | +| `namespaceId` | Identifies a namespace resource used to group sessions and long-term memories. | | Memory ID | Uniquely identifies a long-term memory within the store. | An application can use the same user identifier for `actorId` and `ownerId`, but the fields describe different relationships. +## Organize memories with namespaces + +Use a namespace to group memories for a project, team, or user. For example, a travel agent can keep a user's trip memories in a personal `travel` namespace. A namespace controls where you place and search for memories; a custom memory type describes the information in each record. + +Create the namespace before writing to it. The response contains a server-generated `namespaceId`. Save this ID and use it in `namespaceRef` on session events and long-term memory writes. Names and paths can change when you rename a namespace; its ID stays the same. + +### Create a namespace hierarchy + +Choose a root scope when you create a namespace: + +| Scope | Creation fields | Example use | +|:------|:----------------|:------------| +| `PERSONAL` | `name`, `scope`, and `ownerId` | A user's travel plans. | +| `SHARED` | `name` and `scope`, without `ownerId` | Information used across a team. | + +Create a child with `name` and `parentId`. You can also create a hierarchy with `path`, such as `travel/japan`, and the root scope. Supply `ownerId` for a personal path. Missing path segments are created as needed. Do not combine `path` with `name` or `parentId`. + +Names and paths are case-sensitive. The path does not encode scope. Use the returned ID to distinguish namespaces that have the same path under different owners or scopes. + +Follow the [REST quickstart]({{< relref "/develop/ai/context-engine/agent-memory/rest-api-quickstart#create-a-namespace" >}}), [Python quickstart]({{< relref "/develop/ai/context-engine/agent-memory/python-sdk-quickstart#create-a-namespace" >}}), or [TypeScript quickstart]({{< relref "/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart#create-a-namespace" >}}) to create a personal namespace and use its ID throughout a conversation. + +### Place and retrieve memories + +Pass `namespaceRef: {"namespaceId": ""}` when you start a session. Memories extracted from the session use that namespace. Use a new session ID when following the quickstarts if you already ran them without a namespace. + +For direct long-term memory creation, set `namespaceRef` on each record. To move existing records, use `MoveLongTermMemories` with their IDs and the destination `namespaceRef`. Check both `moved` and `errors` in the response before treating a batch as complete. + +Search with `filter.namespaceRef.eq` for one namespace or `filter.namespaceRef.in` for several IDs. These filters match exact namespace IDs; they do not expand a parent into its descendants. List the children and include their IDs when you need to search several levels of a hierarchy. Keep the `ownerId` filter when recalling one user's memories. Namespace scope and search filters do not replace your application's access checks. + +### Manage namespaces + +| Operation | Behavior | +|:----------|:---------| +| List | List roots, or pass `parentId` to list direct children. Follow `nextPageToken` for additional pages. | +| Get | Retrieve the current name, path, scope, and state by ID. | +| Rename | Update `name`; continue using the same ID for placement and retrieval. | +| Archive | Set `state` to `ARCHIVED` to stop new children and placements. The update API does not offer an unarchive transition. | +| Delete | Delete an empty leaf namespace. A namespace with children or memory placements returns `409 Conflict`. | + +Creating a namespace at an occupied location returns `409 Conflict`, including when the existing namespace is archived. Save and reuse the returned ID instead of creating the same namespace on each agent turn. For request fields and error responses, see the [namespace API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}#operation/CreateNamespace). + +### Migrate legacy namespace labels + +The `namespace` string is deprecated. It is a label, and does not create or resolve a namespace resource. Creating a resource with the same name does not move records that carry the old label. A `namespaceRef` search matches only records placed in a namespace resource. + +1. Create the destination namespace and save its ID. +1. Find the records to move with the legacy `filter.namespace` and the appropriate owner filter. Follow search pagination and review the IDs before moving them. +1. Move the selected records with `MoveLongTermMemories`. +1. Check `moved` and any per-record `errors`, then search with `filter.namespaceRef` to verify the new placement. +1. Update application writes and searches to use `namespaceRef`. Start new sessions with the namespace reference for future extraction. + +Using the connection variables from the [REST quickstart]({{< relref "/develop/ai/context-engine/agent-memory/rest-api-quickstart#save-the-connection-values" >}}), move a selected record as follows. Set `NAMESPACE_ID` to the destination ID and replace `` with a reviewed record ID: + +```sh +curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data "$(jq -n --arg ns "$NAMESPACE_ID" --arg id '' \ + '{memoryIds: [$id], namespaceRef: {namespaceId: $ns}}')" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/move" | jq +``` + +Do not send `namespace` and `namespaceRef` together. Moving long-term records does not reconfigure the session that produced them. + ## Work with session memory Use a stable `sessionId` to store a conversation as an ordered sequence of events. Add an event for each user, assistant, or system message that your application needs to retain. @@ -102,7 +168,8 @@ Long-term memory stores information that remains useful beyond one conversation. | `memoryType` | Built-in or custom memory type. | | `sessionId` | Session associated with the memory. | | `ownerId` | User or entity associated with the memory. | -| `namespace` | Logical grouping for the memory. | +| `namespaceRef` | Namespace ID and its current name and path. | +| `attributes` | Custom memory fields and their values. | | `topics` | Topic tags used to categorize the memory. | | `createdAt` | Time the memory was created. | | `updatedAt` | Time the memory was last updated. | @@ -161,12 +228,33 @@ For example, a travel application could define a `trip_preference` type with the | Field | Type | Captures | |:------|:-----|:---------| -| `destination` | `str` | City or country the user plans to visit. | +| `destinations` | `list[str]` | Cities or countries the user plans to visit. | | `travel_period` | `str` | Dates or period of the trip. | | `dietary_requirements` | `list[str]` | Dietary requirements that affect recommendations. | +| `food_preferences` | `list[str]` | Cuisines, flavors, or dining preferences. | When this type is enabled, Redis Agent Memory can extract a structured `trip_preference` memory from relevant session events. Each enabled custom type processes session events independently. +Search with `memoryType`, `ownerId`, and `namespaceRef` filters together to retrieve this user's trip preferences from the intended namespace. Read the structured fields from each result's `attributes` object. For example, this excerpt shows fields your application can use to constrain restaurant recommendations: + +```json +{ + "memoryType": "trip_preference", + "attributes": { + "destinations": ["Tokyo", "Kyoto"], + "travel_period": "next month", + "dietary_requirements": ["vegetarian"], + "food_preferences": ["spicy food"] + } +} +``` + +This is an illustrative excerpt, not a guaranteed extraction result. Check for missing fields before using them. Extraction runs asynchronously: an empty `items` array can mean that processing is not finished or no matching information was extracted. Retry after a short wait, then check the type's enabled setting, extraction prompt, and search filters if results remain empty. Do not interpret an empty result as the absence of a dietary requirement. + +You can also create custom memories directly. Register the type on the service first, then send `id`, `text`, `ownerId`, `memoryType`, `attributes`, and optionally `namespaceRef` through `BulkCreateLongTermMemories`. Attribute names and JSON values must match the registered fields. Use this path for structured data from an application form or an import. It does not run the extraction prompt or sensitive-data exclusions. + +The quickstarts show both extraction and direct creation with the same `trip_preference` schema. Custom types are configured for the service; placing a record in a namespace does not define a new type. + See [custom memory types](/content/operate/iris/agent-memory/create-service.md#custom-memory-types) for configuration requirements and limits. Follow any of the client quickstarts to create and search long-term memory. For complete schemas, see the [long-term-memory API reference](/content/develop/ai/context-engine/agent-memory/api-reference.md#tag/long-term-memory). diff --git a/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md b/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md index f98b5534ee..f44d3126f1 100644 --- a/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md @@ -39,8 +39,12 @@ Keep the API key out of source control, application logs, and other unsecured lo ## Install the SDK +Create and activate a virtual environment with [uv](https://docs.astral.sh/uv/): + ```sh -python -m pip install redis-agent-memory +uv venv +source .venv/bin/activate +uv pip install "redis-agent-memory==0.4.0" ``` ## Create the client and check the service health @@ -87,13 +91,35 @@ python quickstart.py A healthy response confirms that the client can reach Redis Agent Memory and authenticate with the API key. The first store request validates the Store ID. +## Create a namespace + +Add this code after the health check, inside the `with` block. It creates a personal namespace for the user's travel memories when `NAMESPACE_ID` is not set: + +```python + namespace_id = os.environ.get("NAMESPACE_ID") + if not namespace_id: + created_namespace = agent_memory.create_namespace(request={ + "name": "travel", + "scope": models.NamespaceScope.PERSONAL, + "owner_id": USER_ID, + }) + namespace_id = created_namespace.namespace.namespace_id + print(f'export NAMESPACE_ID="{namespace_id}"') + namespace_ref = {"namespace_id": namespace_id} +``` + +Run the file once, then run the printed `export` command in your shell before running the file again. Later runs reuse that ID. Creating the same namespace again returns `409 Conflict`. If you lose the ID, use `list_namespaces` with `scope="PERSONAL"` and `owner_id=USER_ID` to find it. + +Use a fresh `SESSION_ID` if you already ran this quickstart without a namespace. The session events will reference this namespace, and memories extracted from the session will use it. + ## 1. Build conversation context with session memory -Session memory stores a conversation as an ordered sequence of events. Add the following code after the health check, inside the `with` block: +Session memory stores a conversation as an ordered sequence of events. Add the following code after the namespace setup, inside the `with` block: ```python event = agent_memory.add_session_event( session_id=SESSION_ID, + namespace_ref=namespace_ref, actor_id=USER_ID, role=models.MessageRole.USER, content=[models.Text( @@ -117,7 +143,7 @@ Run the file again. The session response contains the stored message, its role, > [!NOTE] > **What to expect:** The `events` array contains the travel message. Redis Agent Memory adds an `eventId` and `systemTimestamp`, showing that the application can recover the complete event later using only the session ID. -After the event is stored, comment out the call to `add_session_event` before subsequent runs to avoid adding the same message again. +After the event is stored, comment out its creation and the `show("Created event", event)` line before subsequent runs. Keep the namespace setup and session retrieval. This avoids adding the same message again. ## 2. Recall automatically extracted information @@ -130,6 +156,7 @@ Wait at least one minute, then add this search after the session retrieval: request={ "text": "What dietary requirements and food preferences does the user have?", "filter_": { + "namespace_ref": {"eq": namespace_id}, "owner_id": { "eq": USER_ID, } @@ -172,6 +199,7 @@ Add this code after the first session event to continue the conversation past th for role, text in turns: agent_memory.add_session_event( session_id=SESSION_ID, + namespace_ref=namespace_ref, actor_id=USER_ID if role == models.MessageRole.USER else "travel-agent", role=role, content=[models.Text(text=text)], @@ -210,6 +238,7 @@ Search for the structured memory: request={ "text": "What are the requirements for the user's trip?", "filter_": { + "namespace_ref": {"eq": namespace_id}, "owner_id": {"eq": USER_ID}, "memory_type": {"eq": "trip_preference"}, }, @@ -219,10 +248,62 @@ Search for the structured memory: show("Trip preference memories", custom_results) ``` -The result uses `trip_preference` as its `memoryType` and contains travel information extracted from the conversation. The exact text and returned fields depend on the conversation, extraction model, and client. +The `items` array contains records with `memoryType` set to `trip_preference`. Custom fields are inside each record's `attributes` object. For example, a result can include this excerpt: + +```json +{ + "memoryType": "trip_preference", + "attributes": { + "destinations": ["Tokyo", "Kyoto"], + "travel_period": "next month", + "dietary_requirements": ["vegetarian"], + "food_preferences": ["spicy food"] + } +} +``` + +The values depend on the conversation and extraction model. An application can use `dietary_requirements` to constrain restaurant recommendations, while `food_preferences` helps rank suitable choices. Check that each field is present and has the expected type before using it. -> [!NOTE] -> **What to expect:** A result with `memoryType` set to `trip_preference` that combines the destinations, travel period, and dietary preferences. This shows that the custom type processed the same conversation independently from the built-in memory types. +Add this code after the custom-memory search to collect dietary requirements for the next agent turn: + +```python + dietary_requirements = set() + if not custom_results.items: + print("No matching trip preferences yet. Retry after a short wait.") + for memory in custom_results.items: + attributes = memory.attributes or {} + requirements = attributes.get("dietary_requirements", []) + if isinstance(requirements, list): + dietary_requirements.update( + value for value in requirements if isinstance(value, str) + ) + print("Restaurant requirements:", sorted(dietary_requirements)) +``` + +If results remain empty, check that the type is enabled and that the owner and namespace IDs match the stored records. An empty result does not mean the user has no dietary requirements. + +### Create a custom memory directly + +If your application already has structured trip data, write it directly using the registered `trip_preference` type. This optional example represents data from a form and uses the same namespace: + +```python + direct_result = agent_memory.bulk_create_long_term_memories(memories=[{ + "id": "trip-form-1", + "text": "The user plans to visit Tokyo and Kyoto next month and requires vegetarian food.", + "owner_id": USER_ID, + "memory_type": "trip_preference", + "namespace_ref": namespace_ref, + "attributes": { + "destinations": ["Tokyo", "Kyoto"], + "travel_period": "next month", + "dietary_requirements": ["vegetarian"], + "food_preferences": ["spicy food"], + }, + }]) + show("Directly created trip preference", direct_result) +``` + +Inspect the bulk response for per-record errors. Run the custom-memory search again to retrieve the record. Direct creation does not wait for background extraction and does not apply the extraction prompt or sensitive-data exclusions. Run this write once; the memory ID identifies the record within the store. See [custom memory types](/content/operate/iris/agent-memory/create-service.md#custom-memory-types) for configuration requirements and limits. @@ -233,6 +314,7 @@ The semantic exclusion prompt tells Redis Agent Memory which information should ```python sensitive_event = agent_memory.add_session_event( session_id=SESSION_ID, + namespace_ref=namespace_ref, actor_id=USER_ID, role=models.MessageRole.USER, content=[models.Text( @@ -246,13 +328,14 @@ The semantic exclusion prompt tells Redis Agent Memory which information should show("Event with excluded information", sensitive_event) ``` -Run the code once, then comment out the call to `add_session_event`. Wait at least one minute and search for the safe hotel information: +Run the code once, then comment out the event creation and its `show` line. Wait at least one minute and search for the safe hotel information: ```python exclusion_results = agent_memory.search_long_term_memory( request={ "text": "Where is the user staying in Tokyo?", "filter_": { + "namespace_ref": {"eq": namespace_id}, "owner_id": {"eq": USER_ID}, }, "limit": 5, @@ -273,6 +356,8 @@ See [sensitive-data exclusions](/content/operate/iris/agent-memory/create-servic ## Next steps +* Learn how to [organize memories with namespaces]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#organize-memories-with-namespaces" >}}). + * Review the [Python SDK package and reference](https://pypi.org/project/redis-agent-memory/). * Try the [TypeScript SDK quickstart](/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md) or [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md). * Learn when to [create long term memories directly](/content/develop/ai/context-engine/agent-memory/developer-guide.md#create-long-term-memories). diff --git a/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md b/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md index 9a5d00feab..f347b8c9a0 100644 --- a/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md @@ -54,6 +54,26 @@ curl --fail-with-body --silent --show-error \ "$AGENT_MEMORY_URL/health" | jq ``` +## Create a namespace + +Create a personal namespace for the user's travel memories. Run this request once and keep the returned ID in your shell: + +```sh +NAMESPACE_ID=$(curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data "$(jq -n --arg owner "$OWNER_ID" \ + '{name: "travel", scope: "PERSONAL", ownerId: $owner}')" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespaces" | jq -er '.namespace.namespaceId') +export NAMESPACE_ID +printf '%s\n' "$NAMESPACE_ID" +``` + +Continue only after the request succeeds and prints a namespace ID. Save that ID for later runs. Creating the same namespace again returns `409 Conflict`; use the existing namespace ID instead. If you lose the ID, list personal roots with `GET /v1/stores/{storeId}/namespaces?scope=PERSONAL&ownerId=`. + +Use a fresh `SESSION_ID` if you already ran this quickstart without a namespace. The session events will reference this namespace, and memories extracted from the session will use it. + ## 1. Build conversation context with session memory Session memory stores a conversation as an ordered sequence of events. Add a user message that contains details the travel agent will need later: @@ -69,6 +89,7 @@ curl --fail-with-body --silent --show-error \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/events" < [!NOTE] -> **What to expect:** A result with `memoryType` set to `trip_preference` that combines the destinations, travel period, and dietary preferences. This shows that the custom type processed the same conversation independently from the built-in memory types. +```json +{ + "memoryType": "trip_preference", + "attributes": { + "destinations": ["Tokyo", "Kyoto"], + "travel_period": "next month", + "dietary_requirements": ["vegetarian"], + "food_preferences": ["spicy food"] + } +} +``` + +The values depend on the conversation and extraction model. An application can use `dietary_requirements` to constrain restaurant recommendations, while `food_preferences` helps rank suitable choices. Check that each field is present and has the expected type before using it. + +To inspect the structured fields, run the search with this `jq` filter in place of the final `jq` command: + +```sh +jq 'if (.items | length) == 0 then + {status: "No matching trip preferences yet. Retry after a short wait."} + else + .items[] | {id, attributes} + end' +``` + +If results remain empty, check that the type is enabled and that the owner and namespace IDs match the stored records. An empty result does not mean the user has no dietary requirements. + +### Create a custom memory directly + +If your application already has structured trip data, write it directly using the registered `trip_preference` type. This optional example represents data from a form and uses the same namespace: + +```sh +curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data @- \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory" <}}). + * Follow the [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md) or [TypeScript SDK quickstart](/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md). * Learn when to [create long term memories directly](/content/develop/ai/context-engine/agent-memory/developer-guide.md#create-long-term-memories). * Use the [Redis Agent Memory API reference](/content/develop/ai/context-engine/agent-memory/api-reference.md) for endpoint and schema details. diff --git a/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md b/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md index e4e513a8d1..1fa5988959 100644 --- a/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md @@ -92,7 +92,7 @@ Keep the API key out of source control, application logs, and other unsecured lo mkdir agent-memory-quickstart cd agent-memory-quickstart npm init -y -npm install @redis-iris/agent-memory +npm install @redis-iris/agent-memory@0.3.0 npm install --save-dev tsx ``` @@ -139,13 +139,36 @@ npx tsx quickstart.ts A healthy response confirms that the client can reach Redis Agent Memory and authenticate with the API key. The first store request validates the Store ID. +## Create a namespace + +Add this code after the health check, inside `run`. It creates a personal namespace for the user's travel memories when `NAMESPACE_ID` is not set: + +```typescript + let namespaceId = process.env.NAMESPACE_ID; + if (!namespaceId) { + const createdNamespace = await agentMemory.createNamespace({ + name: "travel", + scope: "PERSONAL", + ownerId: userId, + }); + namespaceId = createdNamespace.namespace.namespaceId; + console.log(`export NAMESPACE_ID="${namespaceId}"`); + } + const namespaceRef = { namespaceId }; +``` + +Run the file once, then run the printed `export` command in your shell before running the file again. Later runs reuse that ID. Creating the same namespace again returns `409 Conflict`. If you lose the ID, use `listNamespaces` with `scope: "PERSONAL"` and `ownerId: userId` to find it. + +Use a fresh `sessionId` if you already ran this quickstart without a namespace. The session events will reference this namespace, and memories extracted from the session will use it. + ## 1. Build conversation context with session memory -Session memory stores a conversation as an ordered sequence of events. Add the following code after the health check, inside `run`: +Session memory stores a conversation as an ordered sequence of events. Add the following code after the namespace setup, inside `run`: ```typescript const event = await agentMemory.addSessionEvent({ sessionId, + namespaceRef, actorId: userId, role: "USER", content: [{ @@ -166,7 +189,7 @@ Run the file again. The session response contains the stored message, its role, > [!NOTE] > **What to expect:** The `events` array contains the travel message. Redis Agent Memory adds an `eventId` and `systemTimestamp`, showing that the application can recover the complete event later using only the session ID. -After the event is stored, comment out the call to `addSessionEvent` before subsequent runs to avoid adding the same message again. +After the event is stored, comment out its creation and the two lines that print it before subsequent runs. Keep the namespace setup and session retrieval. This avoids adding the same message again. ## 2. Recall automatically extracted information @@ -178,6 +201,7 @@ Wait at least one minute, then add this search after the session retrieval: const results = await agentMemory.searchLongTermMemory({ text: "What dietary requirements and food preferences does the user have?", filter: { + namespaceRef: { eq: namespaceId }, ownerId: { eq: userId, }, @@ -218,6 +242,7 @@ Add this code after the first session event to continue the conversation past th for (const turn of turns) { await agentMemory.addSessionEvent({ sessionId, + namespaceRef, actorId: turn.actorId, role: turn.role, content: [{ text: turn.text }], @@ -255,6 +280,7 @@ Search for the structured memory: const customResults = await agentMemory.searchLongTermMemory({ text: "What are the requirements for the user's trip?", filter: { + namespaceRef: { eq: namespaceId }, ownerId: { eq: userId }, memoryType: { eq: "trip_preference" }, }, @@ -264,10 +290,66 @@ Search for the structured memory: console.dir(customResults, { depth: null }); ``` -The result uses `trip_preference` as its `memoryType` and contains travel information extracted from the conversation. The exact text and returned fields depend on the conversation, extraction model, and client. +The `items` array contains records with `memoryType` set to `trip_preference`. Custom fields are inside each record's `attributes` object. For example, a result can include this excerpt: -> [!NOTE] -> **What to expect:** A result with `memoryType` set to `trip_preference` that combines the destinations, travel period, and dietary preferences. This shows that the custom type processed the same conversation independently from the built-in memory types. +```json +{ + "memoryType": "trip_preference", + "attributes": { + "destinations": ["Tokyo", "Kyoto"], + "travel_period": "next month", + "dietary_requirements": ["vegetarian"], + "food_preferences": ["spicy food"] + } +} +``` + +The values depend on the conversation and extraction model. An application can use `dietary_requirements` to constrain restaurant recommendations, while `food_preferences` helps rank suitable choices. Check that each field is present and has the expected type before using it. + +Add this code after the custom-memory search to collect dietary requirements for the next agent turn: + +```typescript + const dietaryRequirements = new Set(); + if (customResults.items.length === 0) { + console.log("No matching trip preferences yet. Retry after a short wait."); + } + for (const memory of customResults.items) { + const requirements = memory.attributes?.dietary_requirements; + if (Array.isArray(requirements)) { + for (const value of requirements) { + if (typeof value === "string") dietaryRequirements.add(value); + } + } + } + console.log("Restaurant requirements:", [...dietaryRequirements].sort()); +``` + +If results remain empty, check that the type is enabled and that the owner and namespace IDs match the stored records. An empty result does not mean the user has no dietary requirements. + +### Create a custom memory directly + +If your application already has structured trip data, write it directly using the registered `trip_preference` type. This optional example represents data from a form and uses the same namespace: + +```typescript + const directResult = await agentMemory.bulkCreateLongTermMemories({ + memories: [{ + id: "trip-form-1", + text: "The user plans to visit Tokyo and Kyoto next month and requires vegetarian food.", + ownerId: userId, + memoryType: "trip_preference", + namespaceRef, + attributes: { + destinations: ["Tokyo", "Kyoto"], + travel_period: "next month", + dietary_requirements: ["vegetarian"], + food_preferences: ["spicy food"], + }, + }], + }); + console.dir(directResult, { depth: null }); +``` + +Inspect the bulk response for per-record errors. Run the custom-memory search again to retrieve the record. Direct creation does not wait for background extraction and does not apply the extraction prompt or sensitive-data exclusions. Run this write once; the memory ID identifies the record within the store. See [custom memory types](/content/operate/iris/agent-memory/create-service.md#custom-memory-types) for configuration requirements and limits. @@ -278,6 +360,7 @@ The semantic exclusion prompt tells Redis Agent Memory which information should ```typescript const sensitiveEvent = await agentMemory.addSessionEvent({ sessionId, + namespaceRef, actorId: userId, role: "USER", content: [{ @@ -289,12 +372,13 @@ The semantic exclusion prompt tells Redis Agent Memory which information should console.dir(sensitiveEvent, { depth: null }); ``` -Run the code once, then comment out the call to `addSessionEvent`. Wait at least one minute and search for the safe hotel information: +Run the code once, then comment out the event creation and the two lines that print it. Wait at least one minute and search for the safe hotel information: ```typescript const exclusionResults = await agentMemory.searchLongTermMemory({ text: "Where is the user staying in Tokyo?", filter: { + namespaceRef: { eq: namespaceId }, ownerId: { eq: userId }, }, limit: 5, @@ -315,6 +399,8 @@ See [sensitive-data exclusions](/content/operate/iris/agent-memory/create-servic ## Next steps +* Learn how to [organize memories with namespaces]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#organize-memories-with-namespaces" >}}). + * Review the [TypeScript SDK package and reference](https://www.npmjs.com/package/@redis-iris/agent-memory). * Try the [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md) or [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md). * Learn when to [create long term memories directly](/content/develop/ai/context-engine/agent-memory/developer-guide.md#create-long-term-memories). diff --git a/content/operate/iris/agent-memory/self-managed/api-examples.md b/content/operate/iris/agent-memory/self-managed/api-examples.md index 6bca9153f7..f7ebef0a06 100644 --- a/content/operate/iris/agent-memory/self-managed/api-examples.md +++ b/content/operate/iris/agent-memory/self-managed/api-examples.md @@ -246,37 +246,55 @@ curl -sS "$DP_URL/v1/stores/$STORE_ID/session-memory?includeAll=true" \ ### Create long-term memories directly +Create a personal namespace for the user's preferences and save its ID. Run this once, then reuse the ID for later writes: + +```bash +NAMESPACE_ID=$(curl --fail-with-body -sS -X POST "$DP_URL/v1/stores/$STORE_ID/namespaces" \ + -H "Authorization: Bearer $RAM_AGENT_KEY" \ + -H "Content-Type: application/json" \ + -d '{"name":"preferences","scope":"PERSONAL","ownerId":"user-001"}' \ + | jq -er '.namespace.namespaceId') +export NAMESPACE_ID +``` + +After the request succeeds, place the memory in that namespace: + ```bash curl -sS -X POST "$DP_URL/v1/stores/$STORE_ID/long-term-memory" \ -H "Authorization: Bearer $RAM_AGENT_KEY" \ -H "Content-Type: application/json" \ - -d '{ + -d "$(jq -n --arg ns "$NAMESPACE_ID" '{ "memories": [ { "id": "pref-email-updates", "text": "User prefers email updates.", "memoryType": "semantic", "ownerId": "user-001", - "namespace": "preferences", + "namespaceRef": {"namespaceId": $ns}, "topics": ["communications"] } ] - }' + }')" ``` +See [organize memories with namespaces]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#organize-memories-with-namespaces" >}}) for hierarchy, search, and migration guidance. + ### Search long-term memory +Filter by the namespace ID to recall preferences from that namespace: + ```bash curl -sS -X POST "$DP_URL/v1/stores/$STORE_ID/long-term-memory/search" \ -H "Authorization: Bearer $RAM_AGENT_KEY" \ -H "Content-Type: application/json" \ - -d '{ + -d "$(jq -n --arg ns "$NAMESPACE_ID" '{ "text": "How should we contact this user?", "filter": { + "namespaceRef": {"eq": $ns}, "ownerId": { "eq": "user-001" } }, "limit": 5 - }' + }')" ``` From 770f120b72ad55dfd0047677406b45f7d563f461 Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Mon, 14 Sep 2026 17:14:16 -0700 Subject: [PATCH 02/26] Make Agent Memory docs easier to learn and apply --- content/develop/ai/context-engine/_index.md | 2 +- .../ai/context-engine/agent-memory/_index.md | 63 +-- .../context-engine/agent-memory/concepts.md | 38 ++ .../agent-memory/developer-guide.md | 260 ++--------- .../agent-memory/long-term-memory.md | 436 ++++++++++++++++++ .../context-engine/agent-memory/namespaces.md | 234 ++++++++++ .../agent-memory/python-sdk-quickstart.md | 363 --------------- .../context-engine/agent-memory/quickstart.md | 372 +++++++++++++++ .../agent-memory/rest-api-quickstart.md | 336 -------------- .../context-engine/agent-memory/sessions.md | 170 +++++++ .../agent-memory/typescript-sdk-quickstart.md | 406 ---------------- content/embeds/rc-agent-memory-get-started.md | 2 +- ...-agent-memory-quickstart-create-service.md | 33 +- .../iris/agent-memory/create-service.md | 4 +- .../agent-memory/self-managed/api-examples.md | 2 +- .../operate/iris/agent-memory/view-service.md | 2 +- 16 files changed, 1310 insertions(+), 1413 deletions(-) create mode 100644 content/develop/ai/context-engine/agent-memory/concepts.md create mode 100644 content/develop/ai/context-engine/agent-memory/long-term-memory.md create mode 100644 content/develop/ai/context-engine/agent-memory/namespaces.md delete mode 100644 content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md create mode 100644 content/develop/ai/context-engine/agent-memory/quickstart.md delete mode 100644 content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md create mode 100644 content/develop/ai/context-engine/agent-memory/sessions.md delete mode 100644 content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md diff --git a/content/develop/ai/context-engine/_index.md b/content/develop/ai/context-engine/_index.md index 0c25a643d9..a2fec44eeb 100644 --- a/content/develop/ai/context-engine/_index.md +++ b/content/develop/ai/context-engine/_index.md @@ -77,7 +77,7 @@ POST /v1/caches/{cacheId}/entries } ``` -See [LangCache API examples](/content/develop/ai/context-engine/langcache/api-examples.md) and the [Agent Memory REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md) for more. +See [LangCache API examples](/content/develop/ai/context-engine/langcache/api-examples.md) and the [Agent Memory quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) for more. Redis Iris context engine includes four services: diff --git a/content/develop/ai/context-engine/agent-memory/_index.md b/content/develop/ai/context-engine/agent-memory/_index.md index 50f21de1d2..86061424c6 100644 --- a/content/develop/ai/context-engine/agent-memory/_index.md +++ b/content/develop/ai/context-engine/agent-memory/_index.md @@ -23,11 +23,7 @@ When enabled, automatic summarization compacts session memory by summarizing old Access Redis Agent Memory through the Python and TypeScript SDKs or its REST API. It works with any agent framework or LLM provider. -
- {{< image-card image="images/python-sdk-mark.svg" alt="Python SDK mark" title="Python SDK quickstart — Explore Redis Agent Memory with Python" url="/develop/ai/context-engine/agent-memory/python-sdk-quickstart" >}} - {{< image-card image="images/typescript-sdk-mark.svg" alt="TypeScript SDK mark" title="TypeScript SDK quickstart — Explore Redis Agent Memory with TypeScript" url="/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart" >}} - {{< image-card image="images/rest-api-mark.svg" alt="REST API mark" title="REST API quickstart — Explore Redis Agent Memory with curl" url="/develop/ai/context-engine/agent-memory/rest-api-quickstart" >}} -
+Follow the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) with Python, TypeScript, or curl. ## Why use Redis Agent Memory? @@ -35,35 +31,13 @@ Access Redis Agent Memory through the Python and TypeScript SDKs or its REST API * **Automatic session summarization:** Automatically summarize older conversation events while retaining recent messages in full. * **Automatic long-term memory:** Automatically extract durable information from session events in the background. You can also create long-term memories directly from external data. * **Sensitive-data exclusions:** Guide automatic extraction away from information that should not be kept in long-term memory. -* **Relevant retrieval:** Search long-term memory using semantic, keyword, or hybrid search. +* **Relevant retrieval:** Search long-term memory semantically and narrow results with filters. * **Multi-session recall:** Retrieve relevant memories across conversations and filter results by owner, session, namespace, topic, or memory type. * **Custom memory types:** Define memory types for your business domain, with structured fields and instructions that control what Redis Agent Memory extracts. ## Two-tier memory model -Redis Agent Memory provides two memory tiers: - -
-
-

Session memory

-

Stores the ordered events and metadata for a conversation.

-
    -
  • Configurable retention: Set a TTL to control how long session events are retained.
  • -
  • Automatic summarization: Condense older events after a configured threshold while retaining recent events in full, reducing the conversation history sent to the model's context window.
  • -
-
-
-

Long-term memory

-

Stores durable information that can be retrieved across sessions using semantic, keyword, or hybrid search.

-
    -
  • Automatic extraction: Process session events asynchronously and store important information with vector embeddings and metadata.
  • -
  • Custom memory types: Define domain-specific memories with structured fields and extraction instructions.
  • -
  • Sensitive-data exclusions: Guide automatic extraction away from specified sensitive information.
  • -
  • Direct memory creation: Create memories through the API or import knowledge from external sources.
  • -
  • Configurable retention: Set a separate TTL for long-term memories.
  • -
-
-
+Session memory keeps the current conversation available to the agent. Long-term memory preserves useful information across conversations. See [Concepts]({{< relref "/develop/ai/context-engine/agent-memory/concepts" >}}) for an overview of both and the optional namespaces that organize them. ### Example: Travel planning agent @@ -112,28 +86,17 @@ Get started with Redis Agent Memory on Redis Cloud or join the private preview f
-### Choose a quickstart +### Follow the quickstart -After your Redis Agent Memory service is ready, choose a client. Each quickstart follows the same travel planning scenario through namespaces, session memory, automatic extraction, summarization, custom memory types, and sensitive-data exclusions. +After your service is ready, follow the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}). Choose Python, TypeScript, or curl to explore the same travel planning scenario. -Use the developer guide to [organize memories with namespaces]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#organize-memories-with-namespaces" >}}) and [use custom memory types]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#define-custom-memory-types" >}}). +## Explore the documentation -
-
-

Python SDK

-

Explore the Redis Agent Memory workflow with the Python SDK.

-

}}">Open the Python quickstart

-
-
-

TypeScript SDK

-

Explore the Redis Agent Memory workflow with the TypeScript SDK.

-

}}">Open the TypeScript quickstart

-
-
-

REST API

-

Explore the Redis Agent Memory workflow with curl.

-

}}">Open the REST API quickstart

-
-
+* [Concepts]({{< relref "/develop/ai/context-engine/agent-memory/concepts" >}}): Understand session memory, long-term memory, and namespaces. +* [Quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}): Store a conversation and recall an extracted memory. +* [Developer guide]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide" >}}): Put the pieces together in an application. +* [Sessions]({{< relref "/develop/ai/context-engine/agent-memory/sessions" >}}): Store conversation events and configure retention and summarization. +* [Long-term memory]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory" >}}): Create and retrieve memories across conversations, including custom memory types. +* [Namespaces]({{< relref "/develop/ai/context-engine/agent-memory/namespaces" >}}): Organize memories and manage namespace hierarchies. -For shared integration concepts, identifiers, and authentication, see the [Redis Agent Memory developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md). +* [API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}): Look up request and response schemas. diff --git a/content/develop/ai/context-engine/agent-memory/concepts.md b/content/develop/ai/context-engine/agent-memory/concepts.md new file mode 100644 index 0000000000..6dcb51a40e --- /dev/null +++ b/content/develop/ai/context-engine/agent-memory/concepts.md @@ -0,0 +1,38 @@ +--- +alwaysopen: false +categories: +- docs +- develop +- ai +description: Understand session memory, long-term memory, and namespaces in Redis Agent Memory. +hideListLinks: true +linktitle: Concepts +title: Concepts +weight: 4 +--- + +Redis Agent Memory keeps conversation context in session memory and information for future conversations in long-term memory. Optional namespaces organize both. + +## Session memory + +Session memory is the ordered history of a conversation. It includes messages and their roles, timestamps, and metadata. Use it to give an agent the context it needs to continue an interaction, such as the cities a user has discussed with a travel agent. + +Sessions have configurable retention. Automatic summarization condenses older messages while keeping recent messages in full, so a long conversation takes less space in the model's context window. + +For event fields, retrieval, retention, and summarization settings, see [Sessions]({{< relref "/develop/ai/context-engine/agent-memory/sessions" >}}). + +## Long-term memory + +Long-term memory holds information that remains useful across conversations, such as a user's dietary requirements. It lets an agent recall relevant information in a later session, including after the original session expires. Long-term memory has its own retention settings. + +Redis Agent Memory can extract memories from session events in the background. Applications can also create memories directly. Custom memory types add structured fields for information specific to your application. + +For creation and retrieval details, see [Long-term memory]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory" >}}). For schemas and extraction instructions, see [Custom memory types]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory#custom-memory-types" >}}). + +## Namespaces + +Namespaces are optional groups for session memory and long-term memory. Use them to organize information by user, project, or team. A travel application could group a user's conversations and extracted memories in a personal travel namespace. + +Namespaces can form a hierarchy, with personal or shared scope. Each namespace has a stable ID, so applications can keep referring to it when its name or path changes. + +For creation, memory placement, search, and management, see [Namespaces]({{< relref "/develop/ai/context-engine/agent-memory/namespaces" >}}). diff --git a/content/develop/ai/context-engine/agent-memory/developer-guide.md b/content/develop/ai/context-engine/agent-memory/developer-guide.md index 023c6ba9f1..a3d42e4b61 100644 --- a/content/develop/ai/context-engine/agent-memory/developer-guide.md +++ b/content/develop/ai/context-engine/agent-memory/developer-guide.md @@ -8,262 +8,82 @@ description: Connect an application to Redis Agent Memory and work with session hideListLinks: true linktitle: Developer guide title: Redis Agent Memory developer guide -weight: 5 +weight: 6 --- -Use the Python SDK, TypeScript SDK, or REST API to add session events, retrieve conversation context, create long-term memories, and search for relevant information. +Use session memory and long-term memory in your agent's request cycle. Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first to verify the connection and make your first requests. For the memory model, see [Concepts]({{< relref "/develop/ai/context-engine/agent-memory/concepts" >}}). -## Integration workflow +## Configure the application -1. Connect to a Redis Agent Memory service with its endpoint, Store ID, and API key. -1. Add conversation events to session memory. -1. Retrieve session memory before an agent turn to reconstruct the conversation context. -1. Search long-term memory for information relevant to the current interaction. +Keep the service endpoint, Store ID, and application programming interface (API) key in application configuration. Load the key from a secret store or environment variable. The quickstart shows client initialization for Python, TypeScript, and curl. -Redis Agent Memory can automatically summarize older session events and extract long-term memories in the background. Applications can also create long-term memories directly. +Choose identifiers your application can reuse: -## Choose a client +| Identifier | Application choice | +|:-----------|:-------------------| +| `sessionId` | Use one ID for a conversation. Reuse it on later turns and use a new ID for a new conversation. | +| `actorId` | Identify who produced each event, such as the user or assistant. | +| `ownerId` | Use a stable user or entity ID when creating and searching long-term memories. | -| Client | Use it when | Package and quickstart | -|:-------|:------------|:-----------------------| -| Python SDK | Your application or agent uses Python. | Install [`redis-agent-memory`](https://pypi.org/project/redis-agent-memory/) and follow the [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md). | -| TypeScript SDK | Your application or agent uses JavaScript or TypeScript. | Install [`@redis-iris/agent-memory`](https://www.npmjs.com/package/@redis-iris/agent-memory) and follow the [TypeScript SDK quickstart](/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md). | -| REST API | You need language-independent HTTP access or don't want an SDK dependency. | No package required. Follow the [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md). | +Resolve the user and their allowed sessions in your application before making memory requests. Build recall filters from that identity. Search filters do not replace application access checks. -## Connect to a Redis Agent Memory service +## Build context for each agent turn -Every client requires: +For example, a user returns to a travel agent and asks for restaurants in Kyoto. The application needs the current conversation and any relevant preferences from earlier conversations. -* The Redis Agent Memory endpoint. -* The Store ID. -* A Redis Agent Memory API key. +1. Retrieve session memory with the conversation's `sessionId`. +1. Search long-term memory using the user's request and an `ownerId` filter. +1. Build the model context from the session summary, recent events, relevant search results, and the current user message. +1. Call the model to produce the assistant's response. +1. Store the new user and assistant messages as session events, with their roles and actor IDs. -The Python and TypeScript SDKs accept the endpoint, Store ID, and API key when you create the client. When you use the REST API, send the API key as a bearer token and include the Store ID in request paths. +Include the current user message once. If your application stores it before retrieval, avoid adding a second copy to the model context. Store the assistant message after it has been generated. -Follow the [Redis Cloud setup guide](/content/operate/iris/agent-memory/create-service.md) if you don't have a service. After you create one, copy its endpoint and Store ID from the **Configuration** tab and save the API key securely. +Select relevant memories and recent conversation history that fit your model's context window. Use the session summary when automatic summarization is enabled. See [Sessions]({{< relref "/develop/ai/context-engine/agent-memory/sessions" >}}) for event fields, retention, and summarization. -## Identify users and conversations +## Handle long-term recall -| Identifier | Purpose | -|:-----------|:--------| -| `sessionId` | Identifies a conversation or interaction session. | -| `actorId` | Identifies the actor that produced a session event. | -| `ownerId` | Identifies the user or entity associated with a long-term memory. | -| `namespaceId` | Identifies a namespace resource used to group sessions and long-term memories. | -| Memory ID | Uniquely identifies a long-term memory within the store. | +Automatic extraction runs in the background. A successful event write does not mean that a long-term memory is ready to search. Use the current message and session history to answer the current turn; allow later turns to recall extracted information. -An application can use the same user identifier for `actorId` and `ownerId`, but the fields describe different relationships. +An empty search result means that no memories matched the request. It can also occur while extraction is pending. Continue with the available conversation context. If expected memories remain absent, check the extraction settings and search filters. -## Organize memories with namespaces - -Use a namespace to group memories for a project, team, or user. For example, a travel agent can keep a user's trip memories in a personal `travel` namespace. A namespace controls where you place and search for memories; a custom memory type describes the information in each record. - -Create the namespace before writing to it. The response contains a server-generated `namespaceId`. Save this ID and use it in `namespaceRef` on session events and long-term memory writes. Names and paths can change when you rename a namespace; its ID stays the same. - -### Create a namespace hierarchy - -Choose a root scope when you create a namespace: - -| Scope | Creation fields | Example use | -|:------|:----------------|:------------| -| `PERSONAL` | `name`, `scope`, and `ownerId` | A user's travel plans. | -| `SHARED` | `name` and `scope`, without `ownerId` | Information used across a team. | - -Create a child with `name` and `parentId`. You can also create a hierarchy with `path`, such as `travel/japan`, and the root scope. Supply `ownerId` for a personal path. Missing path segments are created as needed. Do not combine `path` with `name` or `parentId`. - -Names and paths are case-sensitive. The path does not encode scope. Use the returned ID to distinguish namespaces that have the same path under different owners or scopes. - -Follow the [REST quickstart]({{< relref "/develop/ai/context-engine/agent-memory/rest-api-quickstart#create-a-namespace" >}}), [Python quickstart]({{< relref "/develop/ai/context-engine/agent-memory/python-sdk-quickstart#create-a-namespace" >}}), or [TypeScript quickstart]({{< relref "/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart#create-a-namespace" >}}) to create a personal namespace and use its ID throughout a conversation. - -### Place and retrieve memories - -Pass `namespaceRef: {"namespaceId": ""}` when you start a session. Memories extracted from the session use that namespace. Use a new session ID when following the quickstarts if you already ran them without a namespace. - -For direct long-term memory creation, set `namespaceRef` on each record. To move existing records, use `MoveLongTermMemories` with their IDs and the destination `namespaceRef`. Check both `moved` and `errors` in the response before treating a batch as complete. - -Search with `filter.namespaceRef.eq` for one namespace or `filter.namespaceRef.in` for several IDs. These filters match exact namespace IDs; they do not expand a parent into its descendants. List the children and include their IDs when you need to search several levels of a hierarchy. Keep the `ownerId` filter when recalling one user's memories. Namespace scope and search filters do not replace your application's access checks. - -### Manage namespaces - -| Operation | Behavior | -|:----------|:---------| -| List | List roots, or pass `parentId` to list direct children. Follow `nextPageToken` for additional pages. | -| Get | Retrieve the current name, path, scope, and state by ID. | -| Rename | Update `name`; continue using the same ID for placement and retrieval. | -| Archive | Set `state` to `ARCHIVED` to stop new children and placements. The update API does not offer an unarchive transition. | -| Delete | Delete an empty leaf namespace. A namespace with children or memory placements returns `409 Conflict`. | - -Creating a namespace at an occupied location returns `409 Conflict`, including when the existing namespace is archived. Save and reuse the returned ID instead of creating the same namespace on each agent turn. For request fields and error responses, see the [namespace API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}#operation/CreateNamespace). - -### Migrate legacy namespace labels - -The `namespace` string is deprecated. It is a label, and does not create or resolve a namespace resource. Creating a resource with the same name does not move records that carry the old label. A `namespaceRef` search matches only records placed in a namespace resource. - -1. Create the destination namespace and save its ID. -1. Find the records to move with the legacy `filter.namespace` and the appropriate owner filter. Follow search pagination and review the IDs before moving them. -1. Move the selected records with `MoveLongTermMemories`. -1. Check `moved` and any per-record `errors`, then search with `filter.namespaceRef` to verify the new placement. -1. Update application writes and searches to use `namespaceRef`. Start new sessions with the namespace reference for future extraction. - -Using the connection variables from the [REST quickstart]({{< relref "/develop/ai/context-engine/agent-memory/rest-api-quickstart#save-the-connection-values" >}}), move a selected record as follows. Set `NAMESPACE_ID` to the destination ID and replace `` with a reviewed record ID: - -```sh -curl --fail-with-body --silent --show-error \ - --request POST \ - --header "Authorization: Bearer $API_KEY" \ - --header 'Content-Type: application/json' \ - --data "$(jq -n --arg ns "$NAMESPACE_ID" --arg id '' \ - '{memoryIds: [$id], namespaceRef: {namespaceId: $ns}}')" \ - "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/move" | jq -``` - -Do not send `namespace` and `namespaceRef` together. Moving long-term records does not reconfigure the session that produced them. - -## Work with session memory - -Use a stable `sessionId` to store a conversation as an ordered sequence of events. Add an event for each user, assistant, or system message that your application needs to retain. - -Each stored event can include: - -| Field | Purpose | -|:------|:--------| -| `eventId` | Server-generated identifier for the event. | -| `sessionId` | Session that contains the event. | -| `actorId` | User, agent, or other actor that produced the event. | -| `role` | Role of the message in the conversation. | -| `content` | Message content, including its text. | -| `createdAt` | Time the event occurred in the application. | -| `systemTimestamp` | Time Redis Agent Memory stored the event. | -| `metadata` | Optional application-specific information associated with the event. | - -Before an agent turn, retrieve the session by `sessionId` and provide the relevant events to the agent. This lets the application reconstruct the conversation without maintaining a separate conversation store. - -### Session retention +Treat a failed request separately from an empty result. Decide whether your application can answer with reduced context or needs to ask the user to retry. Check the API response before retrying: correct authentication and request errors first. Before retrying a write after a timeout, check whether it succeeded to avoid duplicate events or records. -The session-memory TTL controls how long sessions remain available. Configure it according to the retention requirements of your application. When a session expires, its events are no longer available through session-memory retrieval. - -See [memory configuration](/content/operate/iris/agent-memory/create-service.md#memory-configuration) to configure the session-memory TTL in Redis Cloud. - -### Automatic session summarization - -Automatic summarization limits the amount of conversation history that must be added to a model's context window. Configure: - -* **Summarize after:** The number of messages a session can contain before older messages are summarized. -* **Keep most recent:** The number of recent messages that remain available in full. - -For example, with **Summarize after** set to 20 and **Keep most recent** set to 10, Redis Agent Memory summarizes the older 10 messages when the session reaches 20 messages and retains the 10 most recent messages in full. +### Create long-term memories -See [automatic summarization](/content/operate/iris/agent-memory/create-service.md#automatic-summarization) to enable summarization and configure both thresholds in Redis Cloud. +Create memories directly when your application already has information to store, such as a preference submitted through a form. Set the owner and a stable record ID, and check the bulk response for per-record errors. Direct creation does not apply automatic extraction or sensitive-data exclusions. -Follow any of the client quickstarts to add and retrieve a session event. For complete schemas, see the [session-memory API reference](/content/develop/ai/context-engine/agent-memory/api-reference.md#tag/session-memory). +See [Long-term memory]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory" >}}) for creation methods, search fields, retention, and extraction controls. -## Work with long-term memory +## Define custom memory types -Long-term memory stores information that remains useful beyond one conversation. A long-term memory record includes content and fields that let applications classify, scope, and retrieve it: +If your application needs structured values, define a custom memory type. For example, `trip_preference` can capture `destinations`, `travel_period`, `dietary_requirements`, and `food_preferences`. -| Field | Purpose | -|:------|:--------| -| `id` | Unique identifier for the memory. | -| `text` | Memory content used for retrieval. | -| `memoryType` | Built-in or custom memory type. | -| `sessionId` | Session associated with the memory. | -| `ownerId` | User or entity associated with the memory. | -| `namespaceRef` | Namespace ID and its current name and path. | -| `attributes` | Custom memory fields and their values. | -| `topics` | Topic tags used to categorize the memory. | -| `createdAt` | Time the memory was created. | -| `updatedAt` | Time the memory was last updated. | +Enable the type and supply extraction instructions. Search with the `memoryType` and `ownerId` filters, then read each result's `attributes`. Check for missing fields before using them to choose restaurants. -### Create long-term memories +See [Custom memory types]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory#custom-memory-types" >}}) to define fields, handle extraction results, and create records directly. -Redis Agent Memory provides two creation paths: +## Organize memories with namespaces -* **Automatic extraction:** Redis Agent Memory processes session events asynchronously and creates durable memories from relevant information. Configure the extraction cadence to control how often session events are processed. -* **Direct creation:** Your application creates one or more long-term memories through the API or an SDK. Use direct creation when importing existing information or when your application determines exactly what to store. +Namespaces are optional. Add them when your application needs to group conversations and memories by user, project, or team. -Configure the long-term-memory TTL separately from the session-memory TTL. +For example, create a personal `travel` namespace and save its `namespaceId`. Use that ID in `namespaceRef` when starting a session or creating long-term memories. You will also use this ID to search for memories in that namespace. Names and paths can change when you rename a namespace, but its ID stays the same. -See [memory configuration](/content/operate/iris/agent-memory/create-service.md#memory-configuration) to configure the extraction cadence and long-term-memory TTL in Redis Cloud. +See [Namespaces]({{< relref "/develop/ai/context-engine/agent-memory/namespaces" >}}) for hierarchy, placement, search filters, and management. -### Exclude sensitive data from automatic extraction +## Exclude sensitive data from automatic extraction -Sensitive-data exclusions keep specified information out of long-term memory during automatic extraction. A store's exclusions policy combines three mechanisms that you enable independently: +Sensitive-data exclusions keep specified information out of long-term memory during automatic extraction. Enable the controls your store needs: -* **Built-in detectors:** Validated patterns for common identifiers, such as payment card numbers, email addresses, phone numbers, IP addresses, and US Social Security numbers. Select the ones a store should apply. +* **Built-in detectors:** Validated patterns for common identifiers, such as payment card numbers, email addresses, phone numbers, Internet Protocol (IP) addresses, and US Social Security numbers. Select the ones a store should apply. * **Custom detectors:** Regular expressions you write yourself, for identifiers specific to your domain such as tenant IDs or internal reference numbers. * **Semantic exclusions:** A plain-language exclusion prompt describing what must not be kept, such as passwords, access tokens, recovery codes, payment card information, or booking confirmation codes. Use it for concepts a pattern cannot express. -Every detector chooses what happens to a memory it matches: redact the matched text and keep the rest, or drop the memory. When a memory matches several detectors that choose different actions, the memory is dropped. +For each detector, choose an action: redact the matched text and keep the rest, or drop the memory. When a memory matches several detectors that choose different actions, the memory is dropped. Exclusions apply to automatic extraction from session events. They do not apply when an application creates long-term memories directly, and session memory itself is never altered. > [!WARNING] > Detector matches are deterministic, but semantic exclusions are advisory and do not guarantee exclusion. Sensitive session content still reaches the extraction model provider. Use appropriate controls before sending sensitive information to Redis Agent Memory or the model provider. -See [sensitive-data exclusions](/content/operate/iris/agent-memory/create-service.md#sensitive-data-exclusions) to configure the feature in Redis Cloud. - -### Search long-term memory - -Search long-term memory using semantic, keyword, or hybrid retrieval. Scope results with filters for owners, sessions, namespaces, topics, and memory types. - -Use `ownerId` to restrict recall to the relevant user or entity. Add narrower filters when the application needs memories from a particular session, namespace, topic, or memory type. - -For request fields, filter operators, and response schemas, see [`SearchLongTermMemory`](/content/develop/ai/context-engine/agent-memory/api-reference.md#tag/long-term-memory/operation/SearchLongTermMemory). - -### Define custom memory types - -Custom memory types capture structured information specific to your business domain. Define them in the Redis Agent Memory service configuration with: - -| Setting | Purpose | -|:--------|:--------| -| **Name** | Unique name used as the memory's `memoryType`. | -| **Description** | Description of the information represented by the type. | -| **Extraction prompt** | Instructions that tell Redis Agent Memory when and how to extract the custom memory from session events. | -| **Enabled** | Controls whether Redis Agent Memory extracts new memories for the type. | -| **Custom fields** | Structured fields added to memories of this type. | - -Custom fields support `str`, `int`, `float`, `bool`, `list[str]`, `list[float]`, and `object`. Each field has a name and description that explain what it captures. Every custom memory also includes the built-in long-term memory fields listed above. - -For example, a travel application could define a `trip_preference` type with these fields: - -| Field | Type | Captures | -|:------|:-----|:---------| -| `destinations` | `list[str]` | Cities or countries the user plans to visit. | -| `travel_period` | `str` | Dates or period of the trip. | -| `dietary_requirements` | `list[str]` | Dietary requirements that affect recommendations. | -| `food_preferences` | `list[str]` | Cuisines, flavors, or dining preferences. | - -When this type is enabled, Redis Agent Memory can extract a structured `trip_preference` memory from relevant session events. Each enabled custom type processes session events independently. - -Search with `memoryType`, `ownerId`, and `namespaceRef` filters together to retrieve this user's trip preferences from the intended namespace. Read the structured fields from each result's `attributes` object. For example, this excerpt shows fields your application can use to constrain restaurant recommendations: - -```json -{ - "memoryType": "trip_preference", - "attributes": { - "destinations": ["Tokyo", "Kyoto"], - "travel_period": "next month", - "dietary_requirements": ["vegetarian"], - "food_preferences": ["spicy food"] - } -} -``` - -This is an illustrative excerpt, not a guaranteed extraction result. Check for missing fields before using them. Extraction runs asynchronously: an empty `items` array can mean that processing is not finished or no matching information was extracted. Retry after a short wait, then check the type's enabled setting, extraction prompt, and search filters if results remain empty. Do not interpret an empty result as the absence of a dietary requirement. - -You can also create custom memories directly. Register the type on the service first, then send `id`, `text`, `ownerId`, `memoryType`, `attributes`, and optionally `namespaceRef` through `BulkCreateLongTermMemories`. Attribute names and JSON values must match the registered fields. Use this path for structured data from an application form or an import. It does not run the extraction prompt or sensitive-data exclusions. - -The quickstarts show both extraction and direct creation with the same `trip_preference` schema. Custom types are configured for the service; placing a record in a namespace does not define a new type. - -See [custom memory types](/content/operate/iris/agent-memory/create-service.md#custom-memory-types) for configuration requirements and limits. - -Follow any of the client quickstarts to create and search long-term memory. For complete schemas, see the [long-term-memory API reference](/content/develop/ai/context-engine/agent-memory/api-reference.md#tag/long-term-memory). - -## References - -* [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md) -* [TypeScript SDK quickstart](/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md) -* [Python SDK reference](https://pypi.org/project/redis-agent-memory/) -* [TypeScript SDK reference](https://www.npmjs.com/package/@redis-iris/agent-memory) -* [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md) -* [Redis Agent Memory API reference](/content/develop/ai/context-engine/agent-memory/api-reference.md) +See [sensitive-data exclusions]({{< relref "/operate/iris/agent-memory/create-service#sensitive-data-exclusions" >}}) to configure the feature in Redis Cloud. diff --git a/content/develop/ai/context-engine/agent-memory/long-term-memory.md b/content/develop/ai/context-engine/agent-memory/long-term-memory.md new file mode 100644 index 0000000000..7789c9557b --- /dev/null +++ b/content/develop/ai/context-engine/agent-memory/long-term-memory.md @@ -0,0 +1,436 @@ +--- +alwaysopen: false +categories: +- docs +- develop +- ai +description: Create and search memories that remain useful across conversations. +hideListLinks: true +linktitle: Long-term memory +title: Long-term memory +weight: 11 +aliases: +- /develop/ai/context-engine/agent-memory/custom-memory-types/ +--- + +Long-term memory stores information that remains useful beyond one conversation. Use automatic extraction or direct creation to store memories, then search for information relevant to an agent turn. + +## Memory fields + +A long-term memory record includes: + +| Field | Purpose | +|:------|:--------| +| `id` | Unique identifier for the memory. | +| `text` | Memory content used for retrieval. | +| `memoryType` | Built-in or custom memory type. | +| `sessionId` | Session associated with the memory. | +| `ownerId` | User or entity associated with the memory. | +| `namespaceRef` | Optional namespace ID and its current name and path. | +| `attributes` | Custom memory fields and their values. | +| `topics` | Topic tags used to categorize the memory. | +| `createdAt` | Time the memory was created. | +| `updatedAt` | Time the memory was last updated. | + +## Create long-term memories + +Redis Agent Memory provides two ways to create memories: + +* **Automatic extraction:** Redis Agent Memory processes session events asynchronously and creates durable memories from relevant information. Configure the extraction cadence to control how often session events are processed. +* **Direct creation:** Your application creates one or more long-term memories through the application programming interface (API) or a software development kit (SDK). Use direct creation when importing existing information or when your application determines exactly what to store. + +Configure the long-term-memory time to live (TTL) separately from the session-memory TTL. + +See [memory configuration]({{< relref "/operate/iris/agent-memory/create-service#memory-configuration" >}}) to configure the extraction cadence and long-term-memory TTL in Redis Cloud. + +## Search long-term memory + +Search long-term memory semantically and narrow results with filters. Scope results with filters for owners, sessions, namespaces, topics, and memory types. + +Use `ownerId` to restrict recall to the relevant user or entity. Add narrower filters when the application needs memories from a particular session, namespace, topic, or memory type. + +For request fields, filter operators, and response schemas, see [`SearchLongTermMemory`]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}#operation/SearchLongTermMemory). + +For domain-specific fields and extraction instructions, see [Custom memory types]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory#custom-memory-types" >}}). To group memories in a hierarchy, see [Namespaces]({{< relref "/develop/ai/context-engine/agent-memory/namespaces" >}}). + +For controls that apply during automatic extraction, see [Exclude sensitive data from automatic extraction]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#exclude-sensitive-data-from-automatic-extraction" >}}). + +## Custom memory types + +Custom memory types capture structured information specific to your business domain. + +### Define a memory type + +Configure the type on your Redis Agent Memory service with: + +| Setting | Purpose | +|:--------|:--------| +| **Name** | Unique name used as the memory's `memoryType`. | +| **Description** | Description of the information represented by the type. | +| **Extraction prompt** | Instructions that tell Redis Agent Memory when and how to extract the custom memory from session events. | +| **Enabled** | Controls whether Redis Agent Memory extracts new memories for the type. | +| **Custom fields** | Structured fields added to memories of this type. | + +Custom fields support `str`, `int`, `float`, `bool`, `list[str]`, `list[float]`, and `object`. Each field has a name and description that explain what it captures. Every custom memory also includes the built-in [long-term memory fields]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory#memory-fields" >}}). + +For example, a travel application can define a `trip_preference` type with these fields: + +| Field | Type | Captures | +|:------|:-----|:---------| +| `destinations` | `list[str]` | Cities or countries the user plans to visit. | +| `travel_period` | `str` | Dates or period of the trip. | +| `dietary_requirements` | `list[str]` | Dietary requirements that affect recommendations. | +| `food_preferences` | `list[str]` | Cuisines, flavors, or dining preferences. | + +When this type is enabled, Redis Agent Memory can extract a structured `trip_preference` memory from relevant session events. Each enabled custom type processes session events independently. + +### Extract custom memories + +Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first. Reuse its client, connection values, and user and session identifiers. Add Python snippets inside the `with` block and TypeScript snippets inside `run`. Run curl commands in the same shell. Run each write once; comment out completed writes and their output statements before rerunning an SDK file. + +Custom memory types let an application extract structured information for its business domain. Configure and enable the `trip_preference` type with the fields in [Define a memory type]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory#define-a-memory-type" >}}). Use this extraction prompt: + +```text +Extract trip requirements only when the user states a destination or travel plan. Preserve explicit dietary requirements and food preferences. +``` + +After enabling the type, add the travel message from the quickstart to a new session. Wait at least one extraction interval before searching. + +Search for the structured memory: + +{{< multitabs id="custom-memory-search" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + custom_results = agent_memory.search_long_term_memory( + request={ + "text": "What are the requirements for the user's trip?", + "filter_": { + "owner_id": {"eq": USER_ID}, + "memory_type": {"eq": "trip_preference"}, + }, + "limit": 5, + }, + ) + show("Trip preference memories", custom_results) +``` + +-tab-sep- + +```typescript + const customResults = await agentMemory.searchLongTermMemory({ + text: "What are the requirements for the user's trip?", + filter: { + ownerId: { eq: userId }, + memoryType: { eq: "trip_preference" }, + }, + limit: 5, + }); + console.log("Trip preference memories:"); + console.dir(customResults, { depth: null }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data @- \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/search" <}} + +The `items` array contains records with `memoryType` set to `trip_preference`. Custom fields are inside each record's `attributes` object. For example, a result can include this excerpt: + +```json +{ + "memoryType": "trip_preference", + "attributes": { + "destinations": ["Tokyo", "Kyoto"], + "travel_period": "next month", + "dietary_requirements": ["vegetarian"], + "food_preferences": ["spicy food"] + } +} +``` + +The values depend on the conversation and extraction model. An application can use `dietary_requirements` to constrain restaurant recommendations, while `food_preferences` helps rank suitable choices. Check that each field is present and has the expected type before using it. + +Inspect the structured fields. The SDK examples collect dietary requirements for the next agent turn: + +{{< multitabs id="custom-memory-fields" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + dietary_requirements = set() + if not custom_results.items: + print("No matching trip preferences yet. Retry after a short wait.") + for memory in custom_results.items: + attributes = memory.attributes or {} + requirements = attributes.get("dietary_requirements", []) + if isinstance(requirements, list): + dietary_requirements.update( + value for value in requirements if isinstance(value, str) + ) + print("Restaurant requirements:", sorted(dietary_requirements)) +``` + +-tab-sep- + +```typescript + const dietaryRequirements = new Set(); + if (customResults.items.length === 0) { + console.log("No matching trip preferences yet. Retry after a short wait."); + } + for (const memory of customResults.items) { + const requirements = memory.attributes?.dietary_requirements; + if (Array.isArray(requirements)) { + for (const value of requirements) { + if (typeof value === "string") dietaryRequirements.add(value); + } + } + } + console.log("Restaurant requirements:", [...dietaryRequirements].sort()); +``` + +-tab-sep- + +Run the search with this filter in place of the final `jq` command: + +```sh +jq 'if (.items | length) == 0 then + {status: "No matching trip preferences yet. Retry after a short wait."} + else + .items[] | {id, attributes} + end' +``` + +{{< /multitabs >}} + +If results remain empty, check that the type is enabled and that the owner ID matches the stored records. An empty result does not mean the user has no dietary requirements. + + +### Create a custom memory directly + +If your application already has structured trip data, write it directly using the registered `trip_preference` type. This optional example represents data from a form and uses the same user identifier: + +{{< multitabs id="direct-memory" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + direct_result = agent_memory.bulk_create_long_term_memories(memories=[{ + "id": "trip-form-1", + "text": "The user plans to visit Tokyo and Kyoto next month and requires vegetarian food.", + "owner_id": USER_ID, + "memory_type": "trip_preference", + "attributes": { + "destinations": ["Tokyo", "Kyoto"], + "travel_period": "next month", + "dietary_requirements": ["vegetarian"], + "food_preferences": ["spicy food"], + }, + }]) + show("Directly created trip preference", direct_result) +``` + +-tab-sep- + +```typescript + const directResult = await agentMemory.bulkCreateLongTermMemories({ + memories: [{ + id: "trip-form-1", + text: "The user plans to visit Tokyo and Kyoto next month and requires vegetarian food.", + ownerId: userId, + memoryType: "trip_preference", + attributes: { + destinations: ["Tokyo", "Kyoto"], + travel_period: "next month", + dietary_requirements: ["vegetarian"], + food_preferences: ["spicy food"], + }, + }], + }); + console.dir(directResult, { depth: null }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data @- \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory" <}} + +Inspect the bulk response for per-record errors. Run the custom-memory search again to retrieve the record. Direct creation does not wait for background extraction and does not apply the extraction prompt or sensitive-data exclusions. Run this write once; the memory ID identifies the record within the store. + +See [custom memory types]({{< relref "/operate/iris/agent-memory/create-service#custom-memory-types" >}}) for configuration requirements and limits. + +## Test semantic exclusions + +Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first. Reuse its client, connection values, and user and session identifiers. Add Python snippets inside the `with` block and TypeScript snippets inside `run`. Run curl commands in the same shell. Run each write once; comment out completed writes and their output statements before rerunning an SDK file. + +In the service configuration, enable **Semantic exclusions** and set this prompt: + +```text +Do not keep passwords, access tokens, recovery codes, payment card information, or booking confirmation codes in long-term memory. +``` + +The semantic exclusion prompt tells Redis Agent Memory which information should not be kept in long-term memory. Add an event containing a fictional booking code and information that is safe to retain: + +{{< multitabs id="exclusion-event" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + sensitive_event = agent_memory.add_session_event( + session_id=SESSION_ID, + actor_id=USER_ID, + role=models.MessageRole.USER, + content=[models.Text( + text=( + "I booked Hotel Sakura in Tokyo. For this example, " + "the fictional booking confirmation code is DEMO-7QX9." + ), + )], + created_at=datetime.now(timezone.utc), + ) + show("Event with excluded information", sensitive_event) +``` + +-tab-sep- + +```typescript + const sensitiveEvent = await agentMemory.addSessionEvent({ + sessionId, + actorId: userId, + role: "USER", + content: [{ + text: "I booked Hotel Sakura in Tokyo. For this example, the fictional booking confirmation code is DEMO-7QX9.", + }], + createdAt: new Date(), + }); + console.log("Event with excluded information:"); + console.dir(sensitiveEvent, { depth: null }); +``` + +-tab-sep- + +```sh +export EVENT_CREATED_AT="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" + +curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data @- \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/events" <}} + +Run the event write once. In the SDK files, comment out event creation and its output statements before subsequent runs. Wait at least one minute and search for the safe hotel information: + +{{< multitabs id="exclusion-search" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + exclusion_results = agent_memory.search_long_term_memory( + request={ + "text": "Where is the user staying in Tokyo?", + "filter_": { + "owner_id": {"eq": USER_ID}, + }, + "limit": 5, + }, + ) + show("Memories after semantic exclusion", exclusion_results) +``` + +-tab-sep- + +```typescript + const exclusionResults = await agentMemory.searchLongTermMemory({ + text: "Where is the user staying in Tokyo?", + filter: { + ownerId: { eq: userId }, + }, + limit: 5, + }); + console.log("Memories after semantic exclusion:"); + console.dir(exclusionResults, { depth: null }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data @- \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/search" <}} + +Inspect the returned memories. They can retain the hotel name, but should not contain `DEMO-7QX9` because the exclusion prompt covers booking confirmation codes. + +> [!NOTE] +> **What to expect:** A memory similar to `User booked Hotel Sakura in Tokyo` without the fictional confirmation code. If the code appears, refine the exclusion prompt and test again. Exclusions remain advisory. + +> [!WARNING] +> Semantic exclusions are advisory and do not guarantee that sensitive information is excluded. Session content still reaches the extraction model provider. Do not use real sensitive data in this exercise. Exclusions do not apply to directly created long-term memories. + +See [sensitive-data exclusions]({{< relref "/operate/iris/agent-memory/create-service#sensitive-data-exclusions" >}}) for configuration details. diff --git a/content/develop/ai/context-engine/agent-memory/namespaces.md b/content/develop/ai/context-engine/agent-memory/namespaces.md new file mode 100644 index 0000000000..de6a314f9c --- /dev/null +++ b/content/develop/ai/context-engine/agent-memory/namespaces.md @@ -0,0 +1,234 @@ +--- +alwaysopen: false +categories: +- docs +- develop +- ai +description: Group session memory and long-term memory with optional namespaces. +hideListLinks: true +linktitle: Namespaces +title: Namespaces +weight: 13 +--- + +Namespaces are optional. Use them to group memories for a project, team, or user. For example, a travel agent can keep a user's trip memories in a personal `travel` namespace. + +Create the namespace before writing to it. The response contains a server-generated `namespaceId`. Save this ID and use it in `namespaceRef` on session events and long-term memory writes. You will also use this ID to search for memories in that namespace. Names and paths can change when you rename a namespace, but its ID stays the same. + +## Create a namespace hierarchy + +Choose a root scope when you create a namespace: + +| Scope | Creation fields | Example use | +|:------|:----------------|:------------| +| `PERSONAL` | `name`, `scope`, and `ownerId` | A user's travel plans. | +| `SHARED` | `name` and `scope`, without `ownerId` | Information used across a team. | + +Create a child with `name` and `parentId`. You can also create a hierarchy with `path`, such as `travel/japan`, and the root scope. Supply `ownerId` for a personal path. Missing path segments are created as needed. Do not combine `path` with `name` or `parentId`. + +Names and paths are case-sensitive. The path does not encode scope. Use the returned ID to distinguish namespaces that have the same path under different owners or scopes. + +For runnable examples, see [Create a personal namespace]({{< relref "/develop/ai/context-engine/agent-memory/namespaces#create-a-personal-namespace" >}}) or [Create a shared namespace]({{< relref "/develop/ai/context-engine/agent-memory/namespaces#create-a-shared-namespace" >}}). + +## Place and retrieve memories + +Pass `namespaceRef: {"namespaceId": ""}` when you start a session. Memories extracted from the session use that namespace. Use a new session ID when adding a namespace to the quickstart example. + +For direct long-term memory creation, set `namespaceRef` on each record. To move existing records, use `MoveLongTermMemories` with their IDs and the destination `namespaceRef`. Check both `moved` and `errors` in the response before treating a batch as complete. + +Search with `filter.namespaceRef.eq` for one namespace or `filter.namespaceRef.in` for several IDs. These filters match exact namespace IDs; they do not expand a parent into its descendants. List the children and include their IDs when you need to search several levels of a hierarchy. Keep the `ownerId` filter when recalling one user's memories. Namespace scope and search filters do not replace your application's access checks. + +## Manage namespaces + +| Operation | Behavior | +|:----------|:---------| +| List | List roots, or pass `parentId` to list direct children. Follow `nextPageToken` for additional pages. | +| Get | Retrieve the current name, path, scope, and state by ID. | +| Rename | Update `name`; continue using the same ID for placement and retrieval. | +| Archive | Set `state` to `ARCHIVED` to stop new children and placements. The update application programming interface (API) does not offer an unarchive transition. | +| Delete | Delete an empty leaf namespace. A namespace with children or memory placements returns `409 Conflict`. | + +Creating a namespace at an occupied location returns `409 Conflict`, including when the existing namespace is archived. Save and reuse the returned ID instead of creating the same namespace on each agent turn. For request fields and error responses, see the [namespace API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}#operation/CreateNamespace). + +## Create a personal namespace + +Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first. Reuse its client, connection values, and user and session identifiers. Add Python snippets inside the `with` block and TypeScript snippets inside `run`. Run curl commands in the same shell. Run each write once; comment out completed writes and their output statements before rerunning an SDK file. + +Namespaces are optional. This example uses a personal namespace to group the trip memories. + +{{< multitabs id="namespace" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +Add this code inside the `with` block. It creates a personal namespace for the user's travel memories when `NAMESPACE_ID` is not set: + +```python + namespace_id = os.environ.get("NAMESPACE_ID") + if not namespace_id: + created_namespace = agent_memory.create_namespace(request={ + "name": "travel", + "scope": models.NamespaceScope.PERSONAL, + "owner_id": USER_ID, + }) + namespace_id = created_namespace.namespace.namespace_id + print(f'export NAMESPACE_ID="{namespace_id}"') + namespace_ref = {"namespace_id": namespace_id} +``` + +Run the file once, then run the printed `export` command in your shell before running the file again. Later runs reuse that ID. Creating the same namespace again returns `409 Conflict`. If you lose the ID, use `list_namespaces` with `scope="PERSONAL"` and `owner_id=USER_ID` to find it. + +Use a fresh `SESSION_ID` if you already ran this quickstart without a namespace. For new session events, add `namespace_ref=namespace_ref` in Python, `namespaceRef` in TypeScript, or `"namespaceRef": {"namespaceId": "$NAMESPACE_ID"}` in curl. Extracted memories use the session namespace. + +-tab-sep- + +Add this code inside `run`. It creates a personal namespace for the user's travel memories when `NAMESPACE_ID` is not set: + +```typescript + let namespaceId = process.env.NAMESPACE_ID; + if (!namespaceId) { + const createdNamespace = await agentMemory.createNamespace({ + name: "travel", + scope: "PERSONAL", + ownerId: userId, + }); + namespaceId = createdNamespace.namespace.namespaceId; + console.log(`export NAMESPACE_ID="${namespaceId}"`); + } + const namespaceRef = { namespaceId }; +``` + +Run the file once, then run the printed `export` command in your shell before running the file again. Later runs reuse that ID. Creating the same namespace again returns `409 Conflict`. If you lose the ID, use `listNamespaces` with `scope: "PERSONAL"` and `ownerId: userId` to find it. + +Use a fresh `sessionId` if you already ran this quickstart without a namespace. For new session events, add `namespace_ref=namespace_ref` in Python, `namespaceRef` in TypeScript, or `"namespaceRef": {"namespaceId": "$NAMESPACE_ID"}` in curl. Extracted memories use the session namespace. + +-tab-sep- + +Create a personal namespace for the user's travel memories. Run this request once and keep the returned ID in your shell: + +```sh +NAMESPACE_ID=$(curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data "$(jq -n --arg owner "$OWNER_ID" \ + '{name: "travel", scope: "PERSONAL", ownerId: $owner}')" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespaces" | jq -er '.namespace.namespaceId') +export NAMESPACE_ID +printf '%s\n' "$NAMESPACE_ID" +``` + +Continue only after the request succeeds and prints a namespace ID. Save that ID for later runs. Creating the same namespace again returns `409 Conflict`; use the existing namespace ID instead. If you lose the ID, list personal roots with `GET /v1/stores/{storeId}/namespaces?scope=PERSONAL&ownerId=`. + +Use a fresh `SESSION_ID` if you already ran this quickstart without a namespace. For new session events, add `namespace_ref=namespace_ref` in Python, `namespaceRef` in TypeScript, or `"namespaceRef": {"namespaceId": "$NAMESPACE_ID"}` in curl. Extracted memories use the session namespace. + +{{< /multitabs >}} + +## Create a shared namespace + +A shared namespace groups memories that an application uses across users or agents. For example, a travel-planning team can use a shared `team-travel` namespace for information that everyone on the team needs. The namespace has `SHARED` scope and no namespace owner. Individual long-term memories still have their own `ownerId` values. + +Applications use the same namespace ID when starting sessions or creating long-term memories for the team. Each conversation keeps its own session ID. Memories extracted from those sessions use the shared namespace, so a later search can recall information from several conversations and owners. + +Your application decides who can contribute to and retrieve the team's memories. Check the user's access before writing or searching. `SHARED` scope describes how memories are grouped; it does not grant users permission to access them. + +Use the client and connection values from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}). Create the namespace with `scope` set to `SHARED` and omit `ownerId`: + +{{< multitabs id="shared-namespace" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +Add this code inside the `with` block: + +```python + shared_namespace_id = os.environ.get("SHARED_NAMESPACE_ID") + if not shared_namespace_id: + shared_namespace = agent_memory.create_namespace(request={ + "name": "team-travel", + "scope": models.NamespaceScope.SHARED, + }) + shared_namespace_id = shared_namespace.namespace.namespace_id + print(f'export SHARED_NAMESPACE_ID="{shared_namespace_id}"') + shared_namespace_ref = {"namespace_id": shared_namespace_id} +``` + +Run the file once, then run the printed `export` command in your shell. Later runs reuse the namespace ID. + +-tab-sep- + +Add this code inside `run`: + +```typescript + let sharedNamespaceId = process.env.SHARED_NAMESPACE_ID; + if (!sharedNamespaceId) { + const sharedNamespace = await agentMemory.createNamespace({ + name: "team-travel", + scope: "SHARED", + }); + sharedNamespaceId = sharedNamespace.namespace.namespaceId; + console.log(`export SHARED_NAMESPACE_ID="${sharedNamespaceId}"`); + } + const sharedNamespaceRef = { namespaceId: sharedNamespaceId }; +``` + +Run the file once, then run the printed `export` command in your shell. Later runs reuse the namespace ID. + +-tab-sep- + +Run this request once and save the returned ID: + +```sh +SHARED_NAMESPACE_ID=$(curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data '{"name":"team-travel","scope":"SHARED"}' \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespaces" | jq -er '.namespace.namespaceId') +export SHARED_NAMESPACE_ID +printf '%s\n' "$SHARED_NAMESPACE_ID" +``` + +Continue after the request succeeds and prints a namespace ID. Reuse that ID for later requests. + +{{< /multitabs >}} + +Creating the same root again returns `409 Conflict`. If you lose the ID, list root namespaces with `scope=SHARED` and find `team-travel`. + +To add a conversation to this namespace, use a new session ID and set its first event's namespace reference to the shared ID. Use `namespace_ref=shared_namespace_ref` in Python, `namespaceRef: sharedNamespaceRef` in TypeScript, or `"namespaceRef": {"namespaceId": "$SHARED_NAMESPACE_ID"}` in curl. For direct long-term memory creation, set the reference on each record. Creating the namespace does not move existing memories into it. + +### Recall shared memories + +After writing memories to the shared namespace, search with its namespace ID. The examples omit the owner filter so results can include memories associated with different owners in that namespace. Add an `ownerId` filter when you need only one owner's memories. An empty namespace returns no memories. + +{{< multitabs id="shared-memory-search" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + shared_results = agent_memory.search_long_term_memory(request={ + "text": "What travel requirements has the team discussed?", + "filter_": {"namespace_ref": {"eq": shared_namespace_id}}, + "limit": 5, + }) + show("Shared travel memories", shared_results) +``` + +-tab-sep- + +```typescript + const sharedResults = await agentMemory.searchLongTermMemory({ + text: "What travel requirements has the team discussed?", + filter: { namespaceRef: { eq: sharedNamespaceId } }, + limit: 5, + }); + console.dir(sharedResults, { depth: null }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data "$(jq -n --arg ns "$SHARED_NAMESPACE_ID" \ + '{text: "What travel requirements has the team discussed?", filter: {namespaceRef: {eq: $ns}}, limit: 5}')" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/search" | jq +``` + +{{< /multitabs >}} + +The namespace filter selects that exact namespace. To search its children too, list them and include their IDs with `filter.namespaceRef.in`. diff --git a/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md b/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md deleted file mode 100644 index f44d3126f1..0000000000 --- a/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md +++ /dev/null @@ -1,363 +0,0 @@ ---- -alwaysopen: false -categories: -- docs -- develop -- ai -description: Explore session memory, automatic extraction, summarization, custom memory types, and sensitive-data exclusions with the Redis Agent Memory Python SDK. -hideListLinks: true -linktitle: Python SDK quickstart -title: Redis Agent Memory Python SDK quickstart -weight: 6 ---- - -Use this quickstart to follow a travel planning conversation through Redis Agent Memory. You will retrieve the conversation from session memory, recall information extracted in the background, inspect an automatically generated session summary, extract structured travel information, and guide extraction away from sensitive data. - -## Before you begin - -To complete this quickstart, you need: - -{{< embed-md "rc-agent-memory-quickstart-prerequisites.md" >}} - -You also need Python 3.10 or later. - -## Create a Redis Agent Memory service - -{{< embed-md "rc-agent-memory-quickstart-create-service.md" >}} - -## Save the connection values - -1. Open the Redis Agent Memory service in the Redis Cloud console. -1. On the **Configuration** tab, copy the **Endpoint** and **Store ID**. -1. Export the API key in your shell: - - ```sh - export API_KEY='' - ``` - -Keep the API key out of source control, application logs, and other unsecured locations. - -## Install the SDK - -Create and activate a virtual environment with [uv](https://docs.astral.sh/uv/): - -```sh -uv venv -source .venv/bin/activate -uv pip install "redis-agent-memory==0.4.0" -``` - -## Create the client and check the service health - -Create `quickstart.py` with the following code. Replace `` and `` with the values from Redis Cloud. The endpoint must include `https://`. - -```python -import os -from datetime import datetime, timezone - -from redis_agent_memory import AgentMemory, models - - -ENDPOINT = "" -STORE_ID = "" -SESSION_ID = "travel-planning-session" -USER_ID = "quickstart-user" - - -def show(label, response): - print(f"{label}:") - print(response.model_dump_json(by_alias=True, indent=2)) - - -def main(): - with AgentMemory( - ENDPOINT, - store_id=STORE_ID, - api_key=os.environ["API_KEY"], - ) as agent_memory: - health = agent_memory.health() - show("Service health", health) - - -if __name__ == "__main__": - main() -``` - -Run the file: - -```sh -python quickstart.py -``` - -A healthy response confirms that the client can reach Redis Agent Memory and authenticate with the API key. The first store request validates the Store ID. - -## Create a namespace - -Add this code after the health check, inside the `with` block. It creates a personal namespace for the user's travel memories when `NAMESPACE_ID` is not set: - -```python - namespace_id = os.environ.get("NAMESPACE_ID") - if not namespace_id: - created_namespace = agent_memory.create_namespace(request={ - "name": "travel", - "scope": models.NamespaceScope.PERSONAL, - "owner_id": USER_ID, - }) - namespace_id = created_namespace.namespace.namespace_id - print(f'export NAMESPACE_ID="{namespace_id}"') - namespace_ref = {"namespace_id": namespace_id} -``` - -Run the file once, then run the printed `export` command in your shell before running the file again. Later runs reuse that ID. Creating the same namespace again returns `409 Conflict`. If you lose the ID, use `list_namespaces` with `scope="PERSONAL"` and `owner_id=USER_ID` to find it. - -Use a fresh `SESSION_ID` if you already ran this quickstart without a namespace. The session events will reference this namespace, and memories extracted from the session will use it. - -## 1. Build conversation context with session memory - -Session memory stores a conversation as an ordered sequence of events. Add the following code after the namespace setup, inside the `with` block: - -```python - event = agent_memory.add_session_event( - session_id=SESSION_ID, - namespace_ref=namespace_ref, - actor_id=USER_ID, - role=models.MessageRole.USER, - content=[models.Text( - text=( - "I am visiting Tokyo and Kyoto next month. " - "I am vegetarian and prefer spicy food." - ), - )], - created_at=datetime.now(timezone.utc), - ) - show("Created event", event) - - session = agent_memory.get_session_memory( - session_id=SESSION_ID, - ) - show("Session memory", session) -``` - -Run the file again. The session response contains the stored message, its role, actor, and timestamps. An application can retrieve this session before the next agent turn and add the events to the model's context. - -> [!NOTE] -> **What to expect:** The `events` array contains the travel message. Redis Agent Memory adds an `eventId` and `systemTimestamp`, showing that the application can recover the complete event later using only the session ID. - -After the event is stored, comment out its creation and the `show("Created event", event)` line before subsequent runs. Keep the namespace setup and session retrieval. This avoids adding the same message again. - -## 2. Recall automatically extracted information - -Redis Agent Memory processes session events in the background and creates long term memories for information that may be useful in later conversations. You configured the extraction cadence to one minute when you created the service. You do not need to call a memory creation method. - -Wait at least one minute, then add this search after the session retrieval: - -```python - results = agent_memory.search_long_term_memory( - request={ - "text": "What dietary requirements and food preferences does the user have?", - "filter_": { - "namespace_ref": {"eq": namespace_id}, - "owner_id": { - "eq": USER_ID, - } - }, - "limit": 5, - }, - ) - show("Automatically extracted memories", results) -``` - -Run the file. The `items` array should contain memories derived from the conversation, such as the vegetarian requirement or preference for spicy food. Extraction is asynchronous, so run the search again if the array is empty. - -> [!NOTE] -> **What to expect:** Results similar to `User is a vegetarian` and `User prefers spicy food`. Your application did not create these memories directly. Redis Agent Memory derived them from the session event. The exact text and memory types can vary. - -The extracted memory remains searchable after the session expires, subject to the long term memory TTL. You can change the extraction cadence and both TTLs in the [Redis Agent Memory service configuration](/content/operate/iris/agent-memory/create-service.md#memory-configuration). - -The Python SDK uses snake case for method arguments and request fields. Serialized API requests and responses use camel case. - -## 3. Keep long conversations concise with automatic summarization - -Automatic summarization condenses older events and retains the most recent events in full. The retrieved session then contains a `summary` object and the recent `events` array, so the application can provide useful history without filling the model's context window with every original message. - -You enabled automatic summarization when you created the service. When the session reaches six events, Redis Agent Memory summarizes the older events and retains the two most recent events in full. - -### Add conversation turns - -Add this code after the first session event to continue the conversation past the configured threshold: - -```python - turns = [ - (models.MessageRole.ASSISTANT, "What dates are you traveling?"), - (models.MessageRole.USER, "I arrive on October 10 and leave on October 18."), - (models.MessageRole.ASSISTANT, "Would you like formal or casual restaurants?"), - (models.MessageRole.USER, "Mostly casual places near public transit."), - (models.MessageRole.ASSISTANT, "Do you have a preferred budget?"), - (models.MessageRole.USER, "About 40 euros per person."), - ] - - for role, text in turns: - agent_memory.add_session_event( - session_id=SESSION_ID, - namespace_ref=namespace_ref, - actor_id=USER_ID if role == models.MessageRole.USER else "travel-agent", - role=role, - content=[models.Text(text=text)], - created_at=datetime.now(timezone.utc), - ) -``` - -Run the code once, then comment out the loop to avoid adding the same turns again. Summarization runs in the background. - -### Retrieve the summarized session - -After a short wait, retrieve the session again: - -```python - compacted_session = agent_memory.get_session_memory( - session_id=SESSION_ID, - ) - show("Compacted session memory", compacted_session) -``` - -Run the retrieval again after a short wait if `summary` is not present. Compare `summary.text` with the recent events. The summary should preserve earlier trip decisions while recent turns remain available in full. - -> [!NOTE] -> **What to expect:** A `summary` object that preserves details such as Tokyo, Kyoto, the travel dates, and food preferences. `summarizedUpToEventId` identifies the last event covered by the summary, while `events` contains the newer turns that remain in full. The exact summary text can vary. - -See [automatic summarization configuration](/content/operate/iris/agent-memory/create-service.md#automatic-summarization) for details. - -## 4. Extract business specific data with a custom memory type - -Built in memories preserve generally useful information. Custom memory types let an application extract structured information for its business domain. You configured `trip_preference` when you created the service, so it processed the same travel planning event independently. - -Search for the structured memory: - -```python - custom_results = agent_memory.search_long_term_memory( - request={ - "text": "What are the requirements for the user's trip?", - "filter_": { - "namespace_ref": {"eq": namespace_id}, - "owner_id": {"eq": USER_ID}, - "memory_type": {"eq": "trip_preference"}, - }, - "limit": 5, - }, - ) - show("Trip preference memories", custom_results) -``` - -The `items` array contains records with `memoryType` set to `trip_preference`. Custom fields are inside each record's `attributes` object. For example, a result can include this excerpt: - -```json -{ - "memoryType": "trip_preference", - "attributes": { - "destinations": ["Tokyo", "Kyoto"], - "travel_period": "next month", - "dietary_requirements": ["vegetarian"], - "food_preferences": ["spicy food"] - } -} -``` - -The values depend on the conversation and extraction model. An application can use `dietary_requirements` to constrain restaurant recommendations, while `food_preferences` helps rank suitable choices. Check that each field is present and has the expected type before using it. - -Add this code after the custom-memory search to collect dietary requirements for the next agent turn: - -```python - dietary_requirements = set() - if not custom_results.items: - print("No matching trip preferences yet. Retry after a short wait.") - for memory in custom_results.items: - attributes = memory.attributes or {} - requirements = attributes.get("dietary_requirements", []) - if isinstance(requirements, list): - dietary_requirements.update( - value for value in requirements if isinstance(value, str) - ) - print("Restaurant requirements:", sorted(dietary_requirements)) -``` - -If results remain empty, check that the type is enabled and that the owner and namespace IDs match the stored records. An empty result does not mean the user has no dietary requirements. - -### Create a custom memory directly - -If your application already has structured trip data, write it directly using the registered `trip_preference` type. This optional example represents data from a form and uses the same namespace: - -```python - direct_result = agent_memory.bulk_create_long_term_memories(memories=[{ - "id": "trip-form-1", - "text": "The user plans to visit Tokyo and Kyoto next month and requires vegetarian food.", - "owner_id": USER_ID, - "memory_type": "trip_preference", - "namespace_ref": namespace_ref, - "attributes": { - "destinations": ["Tokyo", "Kyoto"], - "travel_period": "next month", - "dietary_requirements": ["vegetarian"], - "food_preferences": ["spicy food"], - }, - }]) - show("Directly created trip preference", direct_result) -``` - -Inspect the bulk response for per-record errors. Run the custom-memory search again to retrieve the record. Direct creation does not wait for background extraction and does not apply the extraction prompt or sensitive-data exclusions. Run this write once; the memory ID identifies the record within the store. - -See [custom memory types](/content/operate/iris/agent-memory/create-service.md#custom-memory-types) for configuration requirements and limits. - -## 5. Guide extraction away from sensitive data - -The semantic exclusion prompt tells Redis Agent Memory which information should not be kept in long-term memory. Add an event containing a fictional booking code and information that is safe to retain: - -```python - sensitive_event = agent_memory.add_session_event( - session_id=SESSION_ID, - namespace_ref=namespace_ref, - actor_id=USER_ID, - role=models.MessageRole.USER, - content=[models.Text( - text=( - "I booked Hotel Sakura in Tokyo. For this example, " - "the fictional booking confirmation code is DEMO-7QX9." - ), - )], - created_at=datetime.now(timezone.utc), - ) - show("Event with excluded information", sensitive_event) -``` - -Run the code once, then comment out the event creation and its `show` line. Wait at least one minute and search for the safe hotel information: - -```python - exclusion_results = agent_memory.search_long_term_memory( - request={ - "text": "Where is the user staying in Tokyo?", - "filter_": { - "namespace_ref": {"eq": namespace_id}, - "owner_id": {"eq": USER_ID}, - }, - "limit": 5, - }, - ) - show("Memories after semantic exclusion", exclusion_results) -``` - -Inspect the returned memories. They can retain the hotel name, but should not contain `DEMO-7QX9` because the exclusion prompt covers booking confirmation codes. - -> [!NOTE] -> **What to expect:** A memory similar to `User booked Hotel Sakura in Tokyo` without the fictional confirmation code. If the code appears, refine the exclusion prompt and test again. Exclusions remain advisory. - -> [!WARNING] -> Semantic exclusions are advisory and do not guarantee that sensitive information is excluded. Session content still reaches the extraction model provider. Do not use real sensitive data in this exercise. Exclusions do not apply to directly created long-term memories. - -See [sensitive-data exclusions](/content/operate/iris/agent-memory/create-service.md#sensitive-data-exclusions) for configuration details. - -## Next steps - -* Learn how to [organize memories with namespaces]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#organize-memories-with-namespaces" >}}). - -* Review the [Python SDK package and reference](https://pypi.org/project/redis-agent-memory/). -* Try the [TypeScript SDK quickstart](/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md) or [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md). -* Learn when to [create long term memories directly](/content/develop/ai/context-engine/agent-memory/developer-guide.md#create-long-term-memories). diff --git a/content/develop/ai/context-engine/agent-memory/quickstart.md b/content/develop/ai/context-engine/agent-memory/quickstart.md new file mode 100644 index 0000000000..f10d8ba278 --- /dev/null +++ b/content/develop/ai/context-engine/agent-memory/quickstart.md @@ -0,0 +1,372 @@ +--- +alwaysopen: false +categories: +- docs +- develop +- ai +description: Store and retrieve a conversation, then recall long-term memory with Python, TypeScript, or curl. +hideListLinks: true +linktitle: Quickstart +title: Redis Agent Memory quickstart +weight: 5 +aliases: +- /develop/ai/context-engine/agent-memory/python-sdk-quickstart/ +- /develop/ai/context-engine/agent-memory/typescript-sdk-quickstart/ +- /develop/ai/context-engine/agent-memory/rest-api-quickstart/ +- /develop/ai/context-engine/agent-memory/api-examples/ +- /operate/rc/context-engine/agent-memory/use-agent-memory/ +--- + +Store a travel-planning message, retrieve the conversation, and recall a preference extracted into long-term memory. Choose Python, TypeScript, or curl for each example. + +## Before you begin + +To complete this quickstart, you need: + +{{< embed-md "rc-agent-memory-quickstart-prerequisites.md" >}} + +Choose Python, TypeScript, or curl for the examples and use it throughout this page. + +{{< multitabs id="prerequisites" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +You also need Python 3.10 or later. + +-tab-sep- + +You also need Node.js and npm. + +-tab-sep- + +You also need a shell with `curl` and `jq` installed. + +{{< /multitabs >}} + +## Create a Redis Agent Memory service + +{{< embed-md "rc-agent-memory-quickstart-create-service.md" >}} + +## Save the connection values + +1. Open the Redis Agent Memory service in the Redis Cloud console. +1. On the **Configuration** tab, copy the **Endpoint** and **Store ID**. +1. Export the values for your chosen client in your shell: + +{{< multitabs id="connection-values" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```sh +export API_KEY='' +``` + +-tab-sep- + +```sh +export API_KEY='' +``` + +-tab-sep- + +```sh +export AGENT_MEMORY_URL='' +export STORE_ID='' +export API_KEY='' +export SESSION_ID='travel-planning-session' +export OWNER_ID='quickstart-user' +``` + +{{< /multitabs >}} + +The endpoint must include `https://`. Keep the API key out of source control, application logs, and other unsecured locations. + +## Install the SDK + +{{< multitabs id="installation" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +Create and activate a virtual environment with [uv](https://docs.astral.sh/uv/): + +```sh +uv venv +source .venv/bin/activate +uv pip install "redis-agent-memory==0.4.0" +``` + +-tab-sep- + +```sh +mkdir agent-memory-quickstart +cd agent-memory-quickstart +npm init -y +npm install @redis-iris/agent-memory@0.3.0 +npm install --save-dev tsx +``` + +-tab-sep- + +Use `curl` and `jq` directly. No SDK installation is required. + +{{< /multitabs >}} + + + +## Create the client and check the service health + +{{< multitabs id="health" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +Create `quickstart.py` with the following code. Replace `` and `` with the values from Redis Cloud. The endpoint must include `https://`. + +```python +import os +from datetime import datetime, timezone + +from redis_agent_memory import AgentMemory, models + + +ENDPOINT = "" +STORE_ID = "" +SESSION_ID = "travel-planning-session" +USER_ID = "quickstart-user" + + +def show(label, response): + print(f"{label}:") + print(response.model_dump_json(by_alias=True, indent=2)) + + +def main(): + with AgentMemory( + ENDPOINT, + store_id=STORE_ID, + api_key=os.environ["API_KEY"], + ) as agent_memory: + health = agent_memory.health() + show("Service health", health) + + +if __name__ == "__main__": + main() +``` + +Run the file: + +```sh +python quickstart.py +``` + +-tab-sep- + +Create `quickstart.ts` with the following code. Replace `` and `` with the values from Redis Cloud. The endpoint must include `https://`. + +```typescript +import { AgentMemory } from "@redis-iris/agent-memory"; + +const apiKey = process.env.API_KEY; +if (!apiKey) { + throw new Error("Set the API_KEY environment variable."); +} + +const serverURL = ""; +const storeId = ""; +const sessionId = "travel-planning-session"; +const userId = "quickstart-user"; + +const agentMemory = new AgentMemory({ + serverURL, + storeId, + apiKey, +}); + +async function run() { + const health = await agentMemory.health(); + console.log("Service health:"); + console.dir(health, { depth: null }); +} + +run().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +``` + +Run the file: + +```sh +npx tsx quickstart.ts +``` + +-tab-sep- + +Verify that the service is available: + +```sh +curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $API_KEY" \ + "$AGENT_MEMORY_URL/health" | jq +``` + +{{< /multitabs >}} + +A healthy response confirms that the client can reach Redis Agent Memory and authenticate with the API key. The first store request validates the Store ID. + +For Python, add subsequent snippets inside the `with` block in `main`. For TypeScript, add them inside `run`. After each step, run `python quickstart.py` or `npx tsx quickstart.ts`. With curl, run each command in the same shell. Run writes once; comment out completed writes and their output statements before rerunning an SDK file. + +## 1. Build conversation context with session memory + +Session memory stores a conversation as an ordered sequence of events. Add a user message after the health check, then retrieve the session: + +{{< multitabs id="session-events" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + event = agent_memory.add_session_event( + session_id=SESSION_ID, + actor_id=USER_ID, + role=models.MessageRole.USER, + content=[models.Text( + text=( + "I am visiting Tokyo and Kyoto next month. " + "I am vegetarian and prefer spicy food." + ), + )], + created_at=datetime.now(timezone.utc), + ) + show("Created event", event) + + session = agent_memory.get_session_memory( + session_id=SESSION_ID, + ) + show("Session memory", session) +``` + +-tab-sep- + +```typescript + const event = await agentMemory.addSessionEvent({ + sessionId, + actorId: userId, + role: "USER", + content: [{ + text: "I am visiting Tokyo and Kyoto next month. I am vegetarian and prefer spicy food.", + }], + createdAt: new Date(), + }); + console.log("Created event:"); + console.dir(event, { depth: null }); + + const session = await agentMemory.getSessionMemory(sessionId); + console.log("Session memory:"); + console.dir(session, { depth: null }); +``` + +-tab-sep- + +```sh +export EVENT_CREATED_AT="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" + +curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data @- \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/events" <}} + +Run the example. The session response contains the stored message, its role, actor, and timestamps. An application can retrieve this session before the next agent turn and add the events to the model's context. + +> [!NOTE] +> **What to expect:** The `events` array contains the travel message. Redis Agent Memory adds an `eventId` and `systemTimestamp`, showing that the application can recover the complete event later using only the session ID. + +Run the event write once. In the SDK files, comment out event creation and its output statements before subsequent runs. Keep the session retrieval. + + +## 2. Recall automatically extracted information + +Redis Agent Memory processes session events in the background and creates long-term memories for information that may be useful in later conversations. You configured the extraction cadence to one minute when you created the service. + +Wait at least one minute, then add this search after the session retrieval: + +{{< multitabs id="memory-search" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + results = agent_memory.search_long_term_memory( + request={ + "text": "What dietary requirements and food preferences does the user have?", + "filter_": { + "owner_id": { + "eq": USER_ID, + } + }, + "limit": 5, + }, + ) + show("Automatically extracted memories", results) +``` + +-tab-sep- + +```typescript + const results = await agentMemory.searchLongTermMemory({ + text: "What dietary requirements and food preferences does the user have?", + filter: { + ownerId: { + eq: userId, + }, + }, + limit: 5, + }); + console.log("Automatically extracted memories:"); + console.dir(results, { depth: null }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data @- \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/search" <}} + +Run the search. The `items` array should contain memories derived from the conversation, such as the vegetarian requirement or preference for spicy food. Extraction is asynchronous, so run the search again if the array is empty. + +> [!NOTE] +> **What to expect:** Results similar to `User is a vegetarian` and `User prefers spicy food`. Your application did not create these memories directly. Redis Agent Memory derived them from the session event. The exact text and memory types can vary. + +The extracted memory remains searchable after the session expires, subject to the long-term memory time to live (TTL). You can change the extraction cadence and both TTLs in the [Redis Agent Memory service configuration]({{< relref "/operate/iris/agent-memory/create-service#memory-configuration" >}}). + +## Next steps + +Follow the [developer guide]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide" >}}) to use memory in an agent’s request cycle. For specific features, see [Sessions]({{< relref "/develop/ai/context-engine/agent-memory/sessions" >}}), [Custom memory types]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory#custom-memory-types" >}}), and [Namespaces]({{< relref "/develop/ai/context-engine/agent-memory/namespaces" >}}). diff --git a/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md b/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md deleted file mode 100644 index f347b8c9a0..0000000000 --- a/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md +++ /dev/null @@ -1,336 +0,0 @@ ---- -alwaysopen: false -categories: -- docs -- develop -- ai -description: Explore session memory, automatic extraction, summarization, custom memory types, and sensitive-data exclusions with the Redis Agent Memory REST API. -hideListLinks: true -linktitle: REST quickstart -title: Redis Agent Memory REST API quickstart -weight: 8 -aliases: -- /operate/rc/context-engine/agent-memory/use-agent-memory/ -- /develop/ai/context-engine/agent-memory/api-examples/ ---- - -Use this quickstart to follow a travel planning conversation through Redis Agent Memory. You will retrieve the conversation from session memory, recall information extracted in the background, inspect an automatically generated session summary, extract structured travel information, and guide extraction away from sensitive data. - -## Before you begin - -To complete this quickstart, you need: - -* A Redis Cloud account that can create Redis Agent Memory services. -* An eligible Redis Cloud database, or permission to create one. -* A shell with `curl` and `jq` installed. - -## Create a Redis Agent Memory service - -{{< embed-md "rc-agent-memory-quickstart-create-service.md" >}} - -## Save the connection values - -1. Open the Redis Agent Memory service in the Redis Cloud console. -1. On the **Configuration** tab, copy the **Endpoint** and **Store ID**. -1. Export the values in your shell: - - ```sh - export AGENT_MEMORY_URL='' - export STORE_ID='' - export API_KEY='' - export SESSION_ID='travel-planning-session' - export OWNER_ID='quickstart-user' - ``` - -`AGENT_MEMORY_URL` must include `https://`. Keep the API key out of source control, application logs, and other unsecured locations. - -## Check the service health - -Verify that the service is available: - -```sh -curl --fail-with-body --silent --show-error \ - --header "Authorization: Bearer $API_KEY" \ - "$AGENT_MEMORY_URL/health" | jq -``` - -## Create a namespace - -Create a personal namespace for the user's travel memories. Run this request once and keep the returned ID in your shell: - -```sh -NAMESPACE_ID=$(curl --fail-with-body --silent --show-error \ - --request POST \ - --header "Authorization: Bearer $API_KEY" \ - --header 'Content-Type: application/json' \ - --data "$(jq -n --arg owner "$OWNER_ID" \ - '{name: "travel", scope: "PERSONAL", ownerId: $owner}')" \ - "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespaces" | jq -er '.namespace.namespaceId') -export NAMESPACE_ID -printf '%s\n' "$NAMESPACE_ID" -``` - -Continue only after the request succeeds and prints a namespace ID. Save that ID for later runs. Creating the same namespace again returns `409 Conflict`; use the existing namespace ID instead. If you lose the ID, list personal roots with `GET /v1/stores/{storeId}/namespaces?scope=PERSONAL&ownerId=`. - -Use a fresh `SESSION_ID` if you already ran this quickstart without a namespace. The session events will reference this namespace, and memories extracted from the session will use it. - -## 1. Build conversation context with session memory - -Session memory stores a conversation as an ordered sequence of events. Add a user message that contains details the travel agent will need later: - -```sh -export EVENT_CREATED_AT="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" - -curl --fail-with-body --silent --show-error \ - --request POST \ - --header "Authorization: Bearer $API_KEY" \ - --header 'Content-Type: application/json' \ - --data @- \ - "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/events" < [!NOTE] -> **What to expect:** The `events` array contains the travel message. Redis Agent Memory adds an `eventId` and `systemTimestamp`, showing that the application can recover the complete event later using only the session ID. - -## 2. Recall automatically extracted information - -Redis Agent Memory processes session events in the background and creates long term memories for information that may be useful in later conversations. You configured the extraction cadence to one minute when you created the service. You do not need to submit a separate memory creation request. - -Wait at least one minute, then search for the user's dietary requirements: - -```sh -curl --fail-with-body --silent --show-error \ - --request POST \ - --header "Authorization: Bearer $API_KEY" \ - --header 'Content-Type: application/json' \ - --data @- \ - "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/search" < [!NOTE] -> **What to expect:** Results similar to `User is a vegetarian` and `User prefers spicy food`. Your application did not create these memories directly. Redis Agent Memory derived them from the session event. The exact text and memory types can vary. - -The extracted memory remains searchable after the session expires, subject to the long term memory TTL. You can change the extraction cadence and both TTLs in the [Redis Agent Memory service configuration](/content/operate/iris/agent-memory/create-service.md#memory-configuration). - -## 3. Keep long conversations concise with automatic summarization - -Automatic summarization condenses older events and retains the most recent events in full. The retrieved session then contains a `summary` object and the recent `events` array, so the application can provide useful history without filling the model's context window with every original message. - -You enabled automatic summarization when you created the service. When the session reaches six events, Redis Agent Memory summarizes the older events and retains the two most recent events in full. - -### Add conversation turns - -Add enough user and assistant events to reach the configured threshold. Use the request from the first step and change `role`, `actorId`, `content`, and `createdAt` for each event. The Python and TypeScript quickstarts use six additional turns about travel dates, restaurant style, and budget. - -Summarization runs in the background after the session reaches the threshold. - -### Retrieve the summarized session - -After a short wait, retrieve the session again: - -```sh -curl --fail-with-body --silent --show-error \ - --header "Authorization: Bearer $API_KEY" \ - "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/$SESSION_ID" | jq -``` - -Repeat the retrieval after a short wait if `summary` is not present. Compare `summary.text` with the recent events. The summary should preserve earlier decisions about the trip while recent turns remain available in full. - -> [!NOTE] -> **What to expect:** A `summary` object that preserves details such as Tokyo, Kyoto, the travel dates, and food preferences. `summarizedUpToEventId` identifies the last event covered by the summary, while `events` contains the newer turns that remain in full. The exact summary text can vary. - -See [automatic summarization configuration](/content/operate/iris/agent-memory/create-service.md#automatic-summarization) for details. - -## 4. Extract business specific data with a custom memory type - -Built in memories preserve generally useful information. Custom memory types let an application extract structured information for its business domain. You configured `trip_preference` when you created the service, so it processed the same travel planning event independently. - -Search for the structured memory: - -```sh -curl --fail-with-body --silent --show-error \ - --request POST \ - --header "Authorization: Bearer $API_KEY" \ - --header 'Content-Type: application/json' \ - --data @- \ - "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/search" < [!NOTE] -> **What to expect:** A memory similar to `User booked Hotel Sakura in Tokyo` without the fictional confirmation code. If the code appears, refine the exclusion prompt and test again. Exclusions remain advisory. - -> [!WARNING] -> Semantic exclusions are advisory and do not guarantee that sensitive information is excluded. Session content still reaches the extraction model provider. Do not use real sensitive data in this exercise. Exclusions do not apply to directly created long-term memories. - -See [sensitive-data exclusions](/content/operate/iris/agent-memory/create-service.md#sensitive-data-exclusions) for configuration details. - -## Next steps - -* Learn how to [organize memories with namespaces]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#organize-memories-with-namespaces" >}}). - -* Follow the [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md) or [TypeScript SDK quickstart](/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md). -* Learn when to [create long term memories directly](/content/develop/ai/context-engine/agent-memory/developer-guide.md#create-long-term-memories). -* Use the [Redis Agent Memory API reference](/content/develop/ai/context-engine/agent-memory/api-reference.md) for endpoint and schema details. diff --git a/content/develop/ai/context-engine/agent-memory/sessions.md b/content/develop/ai/context-engine/agent-memory/sessions.md new file mode 100644 index 0000000000..ccb2221038 --- /dev/null +++ b/content/develop/ai/context-engine/agent-memory/sessions.md @@ -0,0 +1,170 @@ +--- +alwaysopen: false +categories: +- docs +- develop +- ai +description: Add and retrieve conversation events, configure retention, and summarize older messages. +hideListLinks: true +linktitle: Sessions +title: Sessions +weight: 10 +--- + +Use a stable `sessionId` to store a conversation as an ordered sequence of events. Add an event for each user, assistant, or system message that your application needs to retain. + +## Add session events + +Each stored event can include: + +| Field | Purpose | +|:------|:--------| +| `eventId` | Server-generated identifier for the event. | +| `sessionId` | Session that contains the event. | +| `actorId` | User, agent, or other actor that produced the event. | +| `role` | Role of the message in the conversation. | +| `content` | Message content, including its text. | +| `createdAt` | Time the event occurred in the application. | +| `systemTimestamp` | Time Redis Agent Memory stored the event. | +| `metadata` | Optional application-specific information associated with the event. | + +## Retrieve conversation context + +Before an agent turn, retrieve the session by `sessionId` and provide the relevant events to the agent. Use these events as conversation context for the model. + +## Configure session retention + +The session-memory time to live (TTL) controls how long sessions remain available. Configure it according to the retention requirements of your application. When a session expires, its events are no longer available through session-memory retrieval. + +See [memory configuration]({{< relref "/operate/iris/agent-memory/create-service#memory-configuration" >}}) to configure the session-memory TTL in Redis Cloud. + +## Configure automatic summarization + +Automatic summarization limits the amount of conversation history that must be added to a model's context window. Configure: + +* **Summarize after:** The number of messages a session can contain before older messages are summarized. +* **Keep most recent:** The number of recent messages that remain available in full. + +For example, with **Summarize after** set to 20 and **Keep most recent** set to 10, Redis Agent Memory summarizes the older 10 messages when the session reaches 20 messages and retains the 10 most recent messages in full. + +See [automatic summarization]({{< relref "/operate/iris/agent-memory/create-service#automatic-summarization" >}}) to enable summarization and configure both thresholds in Redis Cloud. + +For an example that combines session events with long-term recall, follow the [developer guide]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide" >}}). For complete schemas, see the [session-memory application programming interface (API) reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}#operation/GetSessionMemory). + +To group a session and its extracted memories, use an optional [namespace]({{< relref "/develop/ai/context-engine/agent-memory/namespaces#place-and-retrieve-memories" >}}). + +## Try automatic summarization + +Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first. Reuse its client, connection values, and user and session identifiers. Add Python snippets inside the `with` block and TypeScript snippets inside `run`. Run curl commands in the same shell. Run each write once; comment out completed writes and their output statements before rerunning an SDK file. + +Automatic summarization condenses older events and retains the most recent events in full. The retrieved session then contains a `summary` object and the recent `events` array, so the application can provide useful history without filling the model's context window with every original message. + +In the service configuration, enable automatic summarization. Set **Summarize after** to `6` and **Keep most recent** to `2` for this example. When the session reaches six events, Redis Agent Memory summarizes the older events and retains the two most recent events in full. + + +### Add conversation turns + +Add these events to the session from the quickstart to continue the conversation past the configured threshold: + +{{< multitabs id="conversation-turns" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + turns = [ + (models.MessageRole.ASSISTANT, "What dates are you traveling?"), + (models.MessageRole.USER, "I arrive on October 10 and leave on October 18."), + (models.MessageRole.ASSISTANT, "Would you like formal or casual restaurants?"), + (models.MessageRole.USER, "Mostly casual places near public transit."), + (models.MessageRole.ASSISTANT, "Do you have a preferred budget?"), + (models.MessageRole.USER, "About 40 euros per person."), + ] + + for role, text in turns: + agent_memory.add_session_event( + session_id=SESSION_ID, + actor_id=USER_ID if role == models.MessageRole.USER else "travel-agent", + role=role, + content=[models.Text(text=text)], + created_at=datetime.now(timezone.utc), + ) +``` + +-tab-sep- + +```typescript + const turns = [ + { role: "ASSISTANT", actorId: "travel-agent", text: "What dates are you traveling?" }, + { role: "USER", actorId: userId, text: "I arrive on October 10 and leave on October 18." }, + { role: "ASSISTANT", actorId: "travel-agent", text: "Would you like formal or casual restaurants?" }, + { role: "USER", actorId: userId, text: "Mostly casual places near public transit." }, + { role: "ASSISTANT", actorId: "travel-agent", text: "Do you have a preferred budget?" }, + { role: "USER", actorId: userId, text: "About 40 euros per person." }, + ] as const; + + for (const turn of turns) { + await agentMemory.addSessionEvent({ + sessionId, + actorId: turn.actorId, + role: turn.role, + content: [{ text: turn.text }], + createdAt: new Date(), + }); + } +``` + +-tab-sep- + +Add enough user and assistant events to reach the configured threshold. Use the session-event request from the quickstart and change `role`, `actorId`, `content`, and `createdAt` for each event. Add these six turns once, using `travel-agent` as the assistant actor and `$OWNER_ID` as the user actor: + +| Role | Content | +|:---|:---| +| `ASSISTANT` | What dates are you traveling? | +| `USER` | I arrive on October 10 and leave on October 18. | +| `ASSISTANT` | Would you like formal or casual restaurants? | +| `USER` | Mostly casual places near public transit. | +| `ASSISTANT` | Do you have a preferred budget? | +| `USER` | About 40 euros per person. | + +Summarization runs in the background after the session reaches the threshold. + +{{< /multitabs >}} + +Run these writes once. In the SDK files, comment out the loop before subsequent runs. Summarization runs in the background. + + +### Retrieve the summarized session + +After a short wait, retrieve the session again: + +{{< multitabs id="session-summary" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + compacted_session = agent_memory.get_session_memory( + session_id=SESSION_ID, + ) + show("Compacted session memory", compacted_session) +``` + +-tab-sep- + +```typescript + const compactedSession = await agentMemory.getSessionMemory(sessionId); + console.log("Compacted session memory:"); + console.dir(compactedSession, { depth: null }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $API_KEY" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/$SESSION_ID" | jq +``` + +{{< /multitabs >}} + +Run the retrieval again after a short wait if `summary` is not present. Compare `summary.text` with the recent events. The summary should preserve earlier trip decisions while recent turns remain available in full. + +> [!NOTE] +> **What to expect:** A `summary` object that preserves details such as Tokyo, Kyoto, the travel dates, and food preferences. `summarizedUpToEventId` identifies the last event covered by the summary, while `events` contains the newer turns that remain in full. The exact summary text can vary. + +See [automatic summarization configuration]({{< relref "/operate/iris/agent-memory/create-service#automatic-summarization" >}}) for details. diff --git a/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md b/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md deleted file mode 100644 index 1fa5988959..0000000000 --- a/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md +++ /dev/null @@ -1,406 +0,0 @@ ---- -alwaysopen: false -categories: -- docs -- develop -- ai -description: Explore session memory, automatic extraction, summarization, custom memory types, and sensitive-data exclusions with the Redis Agent Memory TypeScript SDK. -hideListLinks: true -linktitle: TypeScript SDK quickstart -title: Redis Agent Memory TypeScript SDK quickstart -weight: 7 ---- - -Use this quickstart to follow a travel planning conversation through Redis Agent Memory. You will retrieve the conversation from session memory, recall information extracted in the background, inspect an automatically generated session summary, extract structured travel information, and guide extraction away from sensitive data. - -## Before you begin - -To complete this quickstart, you need: - -{{< embed-md "rc-agent-memory-quickstart-prerequisites.md" >}} - -You also need Node.js and npm. - -## Create a Redis Agent Memory service - -1. Sign in to the [Redis Cloud console](https://cloud.redis.io/). -1. Select **Agent Memory** from the navigation menu. -1. If Redis Cloud displays the public preview terms, review and accept them. -1. Select **Create custom service**. -1. Enter a service name, select an eligible database, and select its `default` user. -1. Under **Memory configuration**, enter these values: - - | Setting | Value | What it controls | - |:--------|:------|:-----------------| - | **Short-term TTL** | `1` day | How long session memory is retained. | - | **Long-term TTL** | `365` days | How long long-term memories are retained. | - | **Extraction cadence** | `1` minute | How often session events are processed for extraction. One minute is for this quickstart; use a longer production interval unless you need rapid extraction. | - | **Automatic summarization** | Enabled | Whether older session events are condensed into a summary. | - | **Summarize after (messages)** | `6` | The event count that triggers summarization. Six is for this quickstart; use a higher production threshold. | - | **Keep most recent (messages)** | `2` | How many recent events remain in full. Two is for this quickstart; retain more in production when recent turns are needed. | - -1. Under **Memory types & extraction**, select **Add type** and configure this custom memory type: - - | Setting | Value | What it controls | - |:--------|:------|:-----------------| - | **Name** | `trip_preference` | The identifier stored in `memoryType` and used in search filters. | - | **Description** | `Structured requirements for a planned trip` | The purpose of the custom memory type. | - | **Extraction prompt** | `Extract trip requirements only when the user states a destination or travel plan. Preserve explicit dietary requirements and food preferences.` | When to create the memory and which information to capture. | - | **Enabled** | Enabled | Whether new memories of this type are extracted. | - -1. Add these custom fields: - - | Field | Type | Description | - |:------|:-----|:------------| - | `destinations` | `list[str]` | Cities or countries the user plans to visit. | - | `travel_period` | `str` | When the user plans to travel. | - | `dietary_requirements` | `list[str]` | Dietary requirements that affect recommendations. | - | `food_preferences` | `list[str]` | Cuisines, flavors, or dining preferences stated by the user. | - -1. Under **Sensitive-data exclusions**, enable **Semantic exclusions** and enter this exclusion prompt: - - ```text - Do not keep passwords, access tokens, recovery codes, payment card information, or booking confirmation codes in long-term memory. - ``` - -1. Select **Create**. -1. Copy the Redis Agent Memory API key and store it securely. - -> [!WARNING] -> Redis Cloud displays the Redis Agent Memory API key only once. If you lose it, [generate a new API key](/content/operate/iris/agent-memory/view-service.md#replace-service-api-key). - -These settings keep the background stages short enough to observe during the quickstart. For screenshots and configuration details, see [create a Redis Agent Memory service](/content/operate/iris/agent-memory/create-service.md). - -> [!WARNING] -> Sensitive-data exclusions guide the extraction model but do not guarantee exclusion. Sensitive session content still reaches the model provider. Exclusions do not apply when an application creates long-term memories directly. - -## Save the connection values - -1. Open the Redis Agent Memory service in the Redis Cloud console. -1. On the **Configuration** tab, copy the **Endpoint** and **Store ID**. -1. Export the API key in your shell: - - ```sh - export API_KEY='' - ``` - -Keep the API key out of source control, application logs, and other unsecured locations. - -## Install the SDK - -```sh -mkdir agent-memory-quickstart -cd agent-memory-quickstart -npm init -y -npm install @redis-iris/agent-memory@0.3.0 -npm install --save-dev tsx -``` - -## Create the client and check the service health - -Create `quickstart.ts` with the following code. Replace `` and `` with the values from Redis Cloud. The endpoint must include `https://`. - -```typescript -import { AgentMemory } from "@redis-iris/agent-memory"; - -const apiKey = process.env.API_KEY; -if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); -} - -const serverURL = ""; -const storeId = ""; -const sessionId = "travel-planning-session"; -const userId = "quickstart-user"; - -const agentMemory = new AgentMemory({ - serverURL, - storeId, - apiKey, -}); - -async function run() { - const health = await agentMemory.health(); - console.log("Service health:"); - console.dir(health, { depth: null }); -} - -run().catch((error) => { - console.error(error); - process.exitCode = 1; -}); -``` - -Run the file: - -```sh -npx tsx quickstart.ts -``` - -A healthy response confirms that the client can reach Redis Agent Memory and authenticate with the API key. The first store request validates the Store ID. - -## Create a namespace - -Add this code after the health check, inside `run`. It creates a personal namespace for the user's travel memories when `NAMESPACE_ID` is not set: - -```typescript - let namespaceId = process.env.NAMESPACE_ID; - if (!namespaceId) { - const createdNamespace = await agentMemory.createNamespace({ - name: "travel", - scope: "PERSONAL", - ownerId: userId, - }); - namespaceId = createdNamespace.namespace.namespaceId; - console.log(`export NAMESPACE_ID="${namespaceId}"`); - } - const namespaceRef = { namespaceId }; -``` - -Run the file once, then run the printed `export` command in your shell before running the file again. Later runs reuse that ID. Creating the same namespace again returns `409 Conflict`. If you lose the ID, use `listNamespaces` with `scope: "PERSONAL"` and `ownerId: userId` to find it. - -Use a fresh `sessionId` if you already ran this quickstart without a namespace. The session events will reference this namespace, and memories extracted from the session will use it. - -## 1. Build conversation context with session memory - -Session memory stores a conversation as an ordered sequence of events. Add the following code after the namespace setup, inside `run`: - -```typescript - const event = await agentMemory.addSessionEvent({ - sessionId, - namespaceRef, - actorId: userId, - role: "USER", - content: [{ - text: "I am visiting Tokyo and Kyoto next month. I am vegetarian and prefer spicy food.", - }], - createdAt: new Date(), - }); - console.log("Created event:"); - console.dir(event, { depth: null }); - - const session = await agentMemory.getSessionMemory(sessionId); - console.log("Session memory:"); - console.dir(session, { depth: null }); -``` - -Run the file again. The session response contains the stored message, its role, actor, and timestamps. An application can retrieve this session before the next agent turn and add the events to the model's context. - -> [!NOTE] -> **What to expect:** The `events` array contains the travel message. Redis Agent Memory adds an `eventId` and `systemTimestamp`, showing that the application can recover the complete event later using only the session ID. - -After the event is stored, comment out its creation and the two lines that print it before subsequent runs. Keep the namespace setup and session retrieval. This avoids adding the same message again. - -## 2. Recall automatically extracted information - -Redis Agent Memory processes session events in the background and creates long term memories for information that may be useful in later conversations. You configured the extraction cadence to one minute when you created the service. You do not need to call a memory creation method. - -Wait at least one minute, then add this search after the session retrieval: - -```typescript - const results = await agentMemory.searchLongTermMemory({ - text: "What dietary requirements and food preferences does the user have?", - filter: { - namespaceRef: { eq: namespaceId }, - ownerId: { - eq: userId, - }, - }, - limit: 5, - }); - console.log("Automatically extracted memories:"); - console.dir(results, { depth: null }); -``` - -Run the file. The `items` array should contain memories derived from the conversation, such as the vegetarian requirement or preference for spicy food. Extraction is asynchronous, so run the search again if the array is empty. - -> [!NOTE] -> **What to expect:** Results similar to `User is a vegetarian` and `User prefers spicy food`. Your application did not create these memories directly. Redis Agent Memory derived them from the session event. The exact text and memory types can vary. - -The extracted memory remains searchable after the session expires, subject to the long term memory TTL. You can change the extraction cadence and both TTLs in the [Redis Agent Memory service configuration](/content/operate/iris/agent-memory/create-service.md#memory-configuration). - -## 3. Keep long conversations concise with automatic summarization - -Automatic summarization condenses older events and retains the most recent events in full. The retrieved session then contains a `summary` object and the recent `events` array, so the application can provide useful history without filling the model's context window with every original message. - -You enabled automatic summarization when you created the service. When the session reaches six events, Redis Agent Memory summarizes the older events and retains the two most recent events in full. - -### Add conversation turns - -Add this code after the first session event to continue the conversation past the configured threshold: - -```typescript - const turns = [ - { role: "ASSISTANT", actorId: "travel-agent", text: "What dates are you traveling?" }, - { role: "USER", actorId: userId, text: "I arrive on October 10 and leave on October 18." }, - { role: "ASSISTANT", actorId: "travel-agent", text: "Would you like formal or casual restaurants?" }, - { role: "USER", actorId: userId, text: "Mostly casual places near public transit." }, - { role: "ASSISTANT", actorId: "travel-agent", text: "Do you have a preferred budget?" }, - { role: "USER", actorId: userId, text: "About 40 euros per person." }, - ] as const; - - for (const turn of turns) { - await agentMemory.addSessionEvent({ - sessionId, - namespaceRef, - actorId: turn.actorId, - role: turn.role, - content: [{ text: turn.text }], - createdAt: new Date(), - }); - } -``` - -Run the code once, then comment out the loop to avoid adding the same turns again. Summarization runs in the background. - -### Retrieve the summarized session - -After a short wait, retrieve the session again: - -```typescript - const compactedSession = await agentMemory.getSessionMemory(sessionId); - console.log("Compacted session memory:"); - console.dir(compactedSession, { depth: null }); -``` - -Run the retrieval again after a short wait if `summary` is not present. Compare `summary.text` with the recent events. The summary should preserve earlier trip decisions while recent turns remain available in full. - -> [!NOTE] -> **What to expect:** A `summary` object that preserves details such as Tokyo, Kyoto, the travel dates, and food preferences. `summarizedUpToEventId` identifies the last event covered by the summary, while `events` contains the newer turns that remain in full. The exact summary text can vary. - -See [automatic summarization configuration](/content/operate/iris/agent-memory/create-service.md#automatic-summarization) for details. - -## 4. Extract business specific data with a custom memory type - -Built in memories preserve generally useful information. Custom memory types let an application extract structured information for its business domain. You configured `trip_preference` when you created the service, so it processed the same travel planning event independently. - -Search for the structured memory: - -```typescript - const customResults = await agentMemory.searchLongTermMemory({ - text: "What are the requirements for the user's trip?", - filter: { - namespaceRef: { eq: namespaceId }, - ownerId: { eq: userId }, - memoryType: { eq: "trip_preference" }, - }, - limit: 5, - }); - console.log("Trip preference memories:"); - console.dir(customResults, { depth: null }); -``` - -The `items` array contains records with `memoryType` set to `trip_preference`. Custom fields are inside each record's `attributes` object. For example, a result can include this excerpt: - -```json -{ - "memoryType": "trip_preference", - "attributes": { - "destinations": ["Tokyo", "Kyoto"], - "travel_period": "next month", - "dietary_requirements": ["vegetarian"], - "food_preferences": ["spicy food"] - } -} -``` - -The values depend on the conversation and extraction model. An application can use `dietary_requirements` to constrain restaurant recommendations, while `food_preferences` helps rank suitable choices. Check that each field is present and has the expected type before using it. - -Add this code after the custom-memory search to collect dietary requirements for the next agent turn: - -```typescript - const dietaryRequirements = new Set(); - if (customResults.items.length === 0) { - console.log("No matching trip preferences yet. Retry after a short wait."); - } - for (const memory of customResults.items) { - const requirements = memory.attributes?.dietary_requirements; - if (Array.isArray(requirements)) { - for (const value of requirements) { - if (typeof value === "string") dietaryRequirements.add(value); - } - } - } - console.log("Restaurant requirements:", [...dietaryRequirements].sort()); -``` - -If results remain empty, check that the type is enabled and that the owner and namespace IDs match the stored records. An empty result does not mean the user has no dietary requirements. - -### Create a custom memory directly - -If your application already has structured trip data, write it directly using the registered `trip_preference` type. This optional example represents data from a form and uses the same namespace: - -```typescript - const directResult = await agentMemory.bulkCreateLongTermMemories({ - memories: [{ - id: "trip-form-1", - text: "The user plans to visit Tokyo and Kyoto next month and requires vegetarian food.", - ownerId: userId, - memoryType: "trip_preference", - namespaceRef, - attributes: { - destinations: ["Tokyo", "Kyoto"], - travel_period: "next month", - dietary_requirements: ["vegetarian"], - food_preferences: ["spicy food"], - }, - }], - }); - console.dir(directResult, { depth: null }); -``` - -Inspect the bulk response for per-record errors. Run the custom-memory search again to retrieve the record. Direct creation does not wait for background extraction and does not apply the extraction prompt or sensitive-data exclusions. Run this write once; the memory ID identifies the record within the store. - -See [custom memory types](/content/operate/iris/agent-memory/create-service.md#custom-memory-types) for configuration requirements and limits. - -## 5. Guide extraction away from sensitive data - -The semantic exclusion prompt tells Redis Agent Memory which information should not be kept in long-term memory. Add an event containing a fictional booking code and information that is safe to retain: - -```typescript - const sensitiveEvent = await agentMemory.addSessionEvent({ - sessionId, - namespaceRef, - actorId: userId, - role: "USER", - content: [{ - text: "I booked Hotel Sakura in Tokyo. For this example, the fictional booking confirmation code is DEMO-7QX9.", - }], - createdAt: new Date(), - }); - console.log("Event with excluded information:"); - console.dir(sensitiveEvent, { depth: null }); -``` - -Run the code once, then comment out the event creation and the two lines that print it. Wait at least one minute and search for the safe hotel information: - -```typescript - const exclusionResults = await agentMemory.searchLongTermMemory({ - text: "Where is the user staying in Tokyo?", - filter: { - namespaceRef: { eq: namespaceId }, - ownerId: { eq: userId }, - }, - limit: 5, - }); - console.log("Memories after semantic exclusion:"); - console.dir(exclusionResults, { depth: null }); -``` - -Inspect the returned memories. They can retain the hotel name, but should not contain `DEMO-7QX9` because the exclusion prompt covers booking confirmation codes. - -> [!NOTE] -> **What to expect:** A memory similar to `User booked Hotel Sakura in Tokyo` without the fictional confirmation code. If the code appears, refine the exclusion prompt and test again. Exclusions remain advisory. - -> [!WARNING] -> Semantic exclusions are advisory and do not guarantee that sensitive information is excluded. Session content still reaches the extraction model provider. Do not use real sensitive data in this exercise. Exclusions do not apply to directly created long-term memories. - -See [sensitive-data exclusions](/content/operate/iris/agent-memory/create-service.md#sensitive-data-exclusions) for configuration details. - -## Next steps - -* Learn how to [organize memories with namespaces]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#organize-memories-with-namespaces" >}}). - -* Review the [TypeScript SDK package and reference](https://www.npmjs.com/package/@redis-iris/agent-memory). -* Try the [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md) or [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md). -* Learn when to [create long term memories directly](/content/develop/ai/context-engine/agent-memory/developer-guide.md#create-long-term-memories). diff --git a/content/embeds/rc-agent-memory-get-started.md b/content/embeds/rc-agent-memory-get-started.md index a91027b30a..d21b18c4fa 100644 --- a/content/embeds/rc-agent-memory-get-started.md +++ b/content/embeds/rc-agent-memory-get-started.md @@ -1,3 +1,3 @@ -Follow the [Redis Cloud setup guide]({{< relref "/operate/iris/agent-memory/create-service" >}}) to create a Redis Agent Memory service. Then use the [REST API quickstart]({{< relref "/develop/ai/context-engine/agent-memory/rest-api-quickstart" >}}) to make your first session-memory and long-term-memory requests. +Follow the [Redis Cloud setup guide]({{< relref "/operate/iris/agent-memory/create-service" >}}) to create a Redis Agent Memory service. Then use the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) to make your first session-memory and long-term-memory requests. After you set up Redis Agent Memory, you can [view and manage your service]({{< relref "/operate/iris/agent-memory/view-service" >}}). diff --git a/content/embeds/rc-agent-memory-quickstart-create-service.md b/content/embeds/rc-agent-memory-quickstart-create-service.md index c813e11be9..c9ab065876 100644 --- a/content/embeds/rc-agent-memory-quickstart-create-service.md +++ b/content/embeds/rc-agent-memory-quickstart-create-service.md @@ -10,33 +10,6 @@ | **Short-term TTL** | `1` day | How long session memory is retained. | | **Long-term TTL** | `365` days | How long long-term memories are retained. | | **Extraction cadence** | `1` minute | How often session events are processed for extraction. One minute is for this quickstart; use a longer production interval unless you need rapid extraction. | - | **Automatic summarization** | Enabled | Whether older session events are condensed into a summary. | - | **Summarize after (messages)** | `6` | The event count that triggers summarization. Six is for this quickstart; use a higher production threshold. | - | **Keep most recent (messages)** | `2` | How many recent events remain in full. Two is for this quickstart; retain more in production when recent turns are needed. | - -1. Under **Memory types & extraction**, select **Add type** and configure this custom memory type: - - | Setting | Value | What it controls | - |:--------|:------|:-----------------| - | **Name** | `trip_preference` | The identifier stored in `memoryType` and used in search filters. | - | **Description** | `Structured requirements for a planned trip` | The purpose of the custom memory type. | - | **Extraction prompt** | `Extract trip requirements only when the user states a destination or travel plan. Preserve explicit dietary requirements and food preferences.` | When to create the memory and which information to capture. | - | **Enabled** | Enabled | Whether new memories of this type are extracted. | - -1. Add these custom fields: - - | Field | Type | Description | - |:------|:-----|:------------| - | `destinations` | `list[str]` | Cities or countries the user plans to visit. | - | `travel_period` | `str` | When the user plans to travel. | - | `dietary_requirements` | `list[str]` | Dietary requirements that affect recommendations. | - | `food_preferences` | `list[str]` | Cuisines, flavors, or dining preferences stated by the user. | - -1. Under **Sensitive-data exclusions**, enable **Semantic exclusions** and enter this exclusion prompt: - - ```text - Do not keep passwords, access tokens, recovery codes, payment card information, or booking confirmation codes in long-term memory. - ``` 1. Select **Create**. 1. Copy the Redis Agent Memory API key and store it securely. @@ -45,8 +18,4 @@ Redis Cloud displays the Redis Agent Memory API key only once. If you lose it, [generate a new API key]({{< relref "/operate/iris/agent-memory/view-service#replace-service-api-key" >}}). {{< /warning >}} -These settings keep the background stages short enough to observe during the quickstart. For screenshots and configuration details, see [create a Redis Agent Memory service]({{< relref "/operate/iris/agent-memory/create-service" >}}). - -{{< warning >}} -Sensitive-data exclusions guide the extraction model but do not guarantee exclusion. Sensitive session content still reaches the model provider. Exclusions do not apply when an application creates long-term memories directly. -{{< /warning >}} +The one-minute extraction interval makes the result easier to observe in this example. For screenshots and configuration details, see [create a Redis Agent Memory service]({{< relref "/operate/iris/agent-memory/create-service" >}}). diff --git a/content/operate/iris/agent-memory/create-service.md b/content/operate/iris/agent-memory/create-service.md index f16b2c6d00..25a5b1c357 100644 --- a/content/operate/iris/agent-memory/create-service.md +++ b/content/operate/iris/agent-memory/create-service.md @@ -53,7 +53,7 @@ This is the only time the value of the user key is available. Save it to a secur If you lose the service key value, you will need to [generate a new service key]({{< relref "/operate/iris/agent-memory/view-service#replace-service-api-key" >}}) to be able to use the Agent Memory API. {{}} - After Redis Cloud creates your service, [continue with the REST API quickstart]({{< relref "/develop/ai/context-engine/agent-memory/rest-api-quickstart" >}}). + After Redis Cloud creates your service, [continue with the quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}). - If you want to customize your Redis Agent Memory service, select **Create custom**. @@ -248,6 +248,6 @@ If an error occurs, verify that your database is active. For help, [contact supp ## Next steps -After Redis Cloud creates your service, [continue with the REST API quickstart]({{< relref "/develop/ai/context-engine/agent-memory/rest-api-quickstart" >}}). +After Redis Cloud creates your service, [continue with the quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}). You can also [view and edit the service]({{< relref "/operate/iris/agent-memory/view-service" >}}). diff --git a/content/operate/iris/agent-memory/self-managed/api-examples.md b/content/operate/iris/agent-memory/self-managed/api-examples.md index f7ebef0a06..0220a8426b 100644 --- a/content/operate/iris/agent-memory/self-managed/api-examples.md +++ b/content/operate/iris/agent-memory/self-managed/api-examples.md @@ -277,7 +277,7 @@ curl -sS -X POST "$DP_URL/v1/stores/$STORE_ID/long-term-memory" \ }')" ``` -See [organize memories with namespaces]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#organize-memories-with-namespaces" >}}) for hierarchy, search, and migration guidance. +See [organize memories with namespaces]({{< relref "/develop/ai/context-engine/agent-memory/namespaces" >}}) for hierarchy, search, and management guidance. ### Search long-term memory diff --git a/content/operate/iris/agent-memory/view-service.md b/content/operate/iris/agent-memory/view-service.md index 0f82fae1e4..2a3b02c8a2 100644 --- a/content/operate/iris/agent-memory/view-service.md +++ b/content/operate/iris/agent-memory/view-service.md @@ -51,7 +51,7 @@ The **General settings** section provides the connection details and general set Select the **Copy** button next to the Store ID and API Base URL to copy them to the clipboard. -Follow the [Redis Agent Memory REST API quickstart]({{< relref "/develop/ai/context-engine/agent-memory/rest-api-quickstart" >}}) to use the connection information and API key. +Follow the [Redis Agent Memory quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) to use the connection information and API key. ### Memory configuration From 7287b0969732c5fbc944018f703cba7cc84594bc Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Tue, 15 Sep 2026 10:43:24 -0700 Subject: [PATCH 03/26] Make Agent Memory guides easier to find --- content/develop/ai/context-engine/agent-memory/_index.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/content/develop/ai/context-engine/agent-memory/_index.md b/content/develop/ai/context-engine/agent-memory/_index.md index 86061424c6..b4ba08e208 100644 --- a/content/develop/ai/context-engine/agent-memory/_index.md +++ b/content/develop/ai/context-engine/agent-memory/_index.md @@ -23,7 +23,14 @@ When enabled, automatic summarization compacts session memory by summarizing old Access Redis Agent Memory through the Python and TypeScript SDKs or its REST API. It works with any agent framework or LLM provider. -Follow the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) with Python, TypeScript, or curl. +
+ {{< image-card image="images/ai-brain.svg" alt="Concepts icon" title="Concepts — Understand the memory model" url="/develop/ai/context-engine/agent-memory/concepts" >}} + {{< image-card image="images/ai-cube.svg" alt="Quickstart icon" title="Quickstart — Make your first requests" url="/develop/ai/context-engine/agent-memory/quickstart" >}} + {{< image-card image="images/ai-lib.svg" alt="Developer guide icon" title="Developer guide — Add memory to your application" url="/develop/ai/context-engine/agent-memory/developer-guide" >}} + {{< image-card image="images/ai-LLM-memory.svg" alt="Sessions icon" title="Sessions — Store conversation events" url="/develop/ai/context-engine/agent-memory/sessions" >}} + {{< image-card image="images/ai-brain-2.svg" alt="Long-term memory icon" title="Long-term memory — Recall information across conversations" url="/develop/ai/context-engine/agent-memory/long-term-memory" >}} + {{< image-card image="images/ai-semantic-routing.svg" alt="Namespaces icon" title="Namespaces — Organize your memories" url="/develop/ai/context-engine/agent-memory/namespaces" >}} +
## Why use Redis Agent Memory? From f408e76e64e1f2e3eb4bf12a1d03fde1dec71b1f Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Wed, 16 Sep 2026 13:03:00 -0700 Subject: [PATCH 04/26] docs: expose namespace routing in the memory API reference --- .../api-reference/openapi-agent-memory.json | 2747 +++++++++++++++-- 1 file changed, 2407 insertions(+), 340 deletions(-) diff --git a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json index bbab8e30d1..e6ee18679c 100644 --- a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json +++ b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json @@ -117,6 +117,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -253,6 +263,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -289,7 +309,7 @@ ] }, "post": { - "description": "Creates long-term memories in bulk.", + "description": "Creates long-term memories in bulk. Records without namespace or namespaceRef use automatic routing when the store's routing policy is enabled. If routing is disabled or no destination qualifies, the record is created without a namespace. An explicit namespaceRef must pass destination admission and is never rerouted. Routing and admission failures are reported per record in errors.", "operationId": "BulkCreateLongTermMemories", "requestBody": { "content": { @@ -387,12 +407,22 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -423,41 +453,42 @@ ] } }, - "/v1/stores/{storeId}/long-term-memory/search": { + "/v1/stores/{storeId}/long-term-memory/move": { "post": { - "description": "Runs a semantic search on long-term memory with filtering options.", - "operationId": "SearchLongTermMemory", + "description": "Moves long-term memories to an explicit namespaceRef or automatically routes each memory when namespaceRef is omitted. If routing is disabled or no destination qualifies, the memory keeps its current placement and receives a per-item error. An explicit destination must pass admission and is never rerouted. Returns 409 when the explicit destination is archived; automatic routing and admission failures are reported per item in errors.", + "operationId": "MoveLongTermMemories", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SearchLongTermMemoryRequestContent" + "$ref": "#/components/schemas/MoveLongTermMemoriesRequestContent" } } - } + }, + "required": true }, "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { "200": { - "description": "SearchLongTermMemory 200 response", + "description": "MoveLongTermMemories 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SearchLongTermMemoryResponseContent" + "$ref": "#/components/schemas/MoveLongTermMemoriesResponseContent" } } } @@ -512,6 +543,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -522,6 +563,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -558,10 +609,19 @@ ] } }, - "/v1/stores/{storeId}/long-term-memory/{memoryId}": { - "get": { - "description": "Returns a long-term memory by its ID.", - "operationId": "GetLongTermMemory", + "/v1/stores/{storeId}/long-term-memory/search": { + "post": { + "description": "Runs a semantic search on long-term memory with filtering options.", + "operationId": "SearchLongTermMemory", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchLongTermMemoryRequestContent" + } + } + } + }, "parameters": [ { "name": "storeId", @@ -575,28 +635,15 @@ "description": "The store instance ID." }, "required": true - }, - { - "name": "memoryId", - "in": "path", - "description": "The memory record ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The memory record ID." - }, - "required": true } ], "responses": { "200": { - "description": "GetLongTermMemory 200 response", + "description": "SearchLongTermMemory 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetLongTermMemoryResponseContent" + "$ref": "#/components/schemas/SearchLongTermMemoryResponseContent" } } } @@ -661,12 +708,22 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -695,19 +752,12 @@ "tags": [ "Long-Term Memory" ] - }, - "patch": { - "description": "Partially updates a long-term memory by its ID.", - "operationId": "UpdateLongTermMemory", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateLongTermMemoryRequestContent" - } - } - } - }, + } + }, + "/v1/stores/{storeId}/long-term-memory/{memoryId}": { + "get": { + "description": "Returns a long-term memory by its ID.", + "operationId": "GetLongTermMemory", "parameters": [ { "name": "storeId", @@ -738,11 +788,11 @@ ], "responses": { "200": { - "description": "UpdateLongTermMemory 200 response", + "description": "GetLongTermMemory 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateLongTermMemoryResponseContent" + "$ref": "#/components/schemas/GetLongTermMemoryResponseContent" } } } @@ -807,6 +857,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -841,21 +901,18 @@ "tags": [ "Long-Term Memory" ] - } - }, - "/v1/stores/{storeId}/long-term-memory/{memoryId}/fields": { + }, "patch": { - "description": "Atomically updates a custom-typed long-term memory's text and/or attributes, scoped to the caller's bound memory type. Used by the custom-extraction worker.", - "operationId": "UpdateLongTermMemoryFields", + "description": "Partially updates a long-term memory by its ID. The merged record must pass admission at its destination. Updates never select a different destination automatically.", + "operationId": "UpdateLongTermMemory", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateLongTermMemoryFieldsRequestContent" + "$ref": "#/components/schemas/UpdateLongTermMemoryRequestContent" } } - }, - "required": true + } }, "parameters": [ { @@ -887,7 +944,14 @@ ], "responses": { "200": { - "description": "UpdateLongTermMemoryFields 200 response" + "description": "UpdateLongTermMemory 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLongTermMemoryResponseContent" + } + } + } }, "400": { "description": "BadRequestError 400 response", @@ -939,12 +1003,42 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -975,10 +1069,20 @@ ] } }, - "/v1/stores/{storeId}/session-memory": { - "get": { - "description": "Returns a paginated list of session IDs for a store.", - "operationId": "ListSessions", + "/v1/stores/{storeId}/long-term-memory/{memoryId}/fields": { + "patch": { + "description": "Atomically updates a custom-typed long-term memory's text and/or attributes, scoped to the caller's bound memory type. Used by the custom-extraction worker. New text must pass destination admission before the update is applied.", + "operationId": "UpdateLongTermMemoryFields", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLongTermMemoryFieldsRequestContent" + } + } + }, + "required": true + }, "parameters": [ { "name": "storeId", @@ -994,59 +1098,22 @@ "required": true }, { - "name": "limit", - "in": "query", - "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", - "schema": { - "type": "integer", - "default": 100, - "maximum": 1000, - "minimum": 1, - "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", - "format": "int32" - } - }, - { - "name": "pageToken", - "in": "query", - "description": "Opaque token from a previous response for the next page.", - "schema": { - "type": "string", - "description": "Opaque token from a previous response for the next page." - } - }, - { - "name": "filterOwnerId", - "in": "query", - "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll.", + "name": "memoryId", + "in": "path", + "description": "The memory record ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll." - } - }, - { - "name": "includeAll", - "in": "query", - "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters.", - "schema": { - "type": "boolean", - "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters." - } + "description": "The memory record ID." + }, + "required": true } ], "responses": { "200": { - "description": "ListSessions 200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListSessionsResponseContent" - } - } - } + "description": "UpdateLongTermMemoryFields 200 response" }, "400": { "description": "BadRequestError 400 response", @@ -1098,22 +1165,32 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", + "409": { + "description": "ConflictError 409 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" } } } }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -1140,46 +1217,36 @@ } }, "tags": [ - "Session Memory" + "Long-Term Memory" ] } }, - "/v1/stores/{storeId}/session-memory/events": { - "post": { - "description": "Appends a single event to a session. Creates the session if it does not exist. If sessionId is omitted, the server generates one.", - "operationId": "AddSessionEvent", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddSessionEventRequestContent" - } - } - }, - "required": true - }, + "/v1/stores/{storeId}/namespace-routing": { + "get": { + "description": "Returns the store's namespace routing policy. An empty disabled policy is returned when none has been saved.", + "operationId": "GetNamespaceRouting", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { - "201": { - "description": "AddSessionEvent 201 response", + "200": { + "description": "GetNamespaceRouting 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddSessionEventResponseContent" + "$ref": "#/components/schemas/GetNamespaceRoutingResponseContent" } } } @@ -1244,6 +1311,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1276,45 +1353,47 @@ } }, "tags": [ - "Session Memory" + "Namespaces" ] - } - }, - "/v1/stores/{storeId}/session-memory/{sessionId}": { - "delete": { - "description": "Deletes the session memory for a session.", - "operationId": "DeleteSessionMemory", + }, + "put": { + "description": "Replaces the store's namespace routing policy. Namespaces remain hierarchy records; routing is an ordered store-level document of plug-in strategies.", + "operationId": "PutNamespaceRouting", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PutNamespaceRoutingRequestContent" + } + } + }, + "required": true + }, "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." - }, - "required": true - }, - { - "name": "sessionId", - "in": "path", - "description": "The session ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { - "204": { - "description": "DeleteSessionMemory 204 response" + "200": { + "description": "PutNamespaceRouting 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PutNamespaceRoutingResponseContent" + } + } + } }, "400": { "description": "BadRequestError 400 response", @@ -1366,6 +1445,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -1376,6 +1465,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1408,56 +1507,90 @@ } }, "tags": [ - "Session Memory" + "Namespaces" ] - }, + } + }, + "/v1/stores/{storeId}/namespaces": { "get": { - "description": "Returns the session memory for a session.", - "operationId": "GetSessionMemory", + "description": "Lists namespace roots or direct children.", + "operationId": "ListNamespaces", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true }, { - "name": "sessionId", - "in": "path", - "description": "The session ID.", + "name": "parentId", + "in": "query", + "description": "List direct children of this namespace. Omit to list roots.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "required": true + "description": "List direct children of this namespace. Omit to list roots." + } }, { - "name": "includeSummarisedEvents", + "name": "scope", "in": "query", - "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage.", + "description": "Restrict root results to one scope. Cannot be combined with parentId.", "schema": { - "type": "boolean", - "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage." + "$ref": "#/components/schemas/NamespaceScope" + } + }, + { + "name": "ownerId", + "in": "query", + "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots." + } + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page." } } ], "responses": { "200": { - "description": "GetSessionMemory 200 response", + "description": "ListNamespaces 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetSessionMemoryResponseContent" + "$ref": "#/components/schemas/ListNamespacesResponseContent" } } } @@ -1522,6 +1655,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1554,58 +1697,46 @@ } }, "tags": [ - "Session Memory" + "Namespaces" ] - } - }, - "/v1/stores/{storeId}/session-memory/{sessionId}/events/{eventId}": { - "delete": { - "description": "Deletes a single event from a session by event ID.", - "operationId": "DeleteSessionEvent", + }, + "post": { + "description": "Creates a namespace resource as a root, direct child, or explicit hierarchy path. Returns 409 when an active or archived namespace already occupies the requested location.", + "operationId": "CreateNamespace", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateNamespaceRequestContent" + } + } + } + }, "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." - }, - "required": true - }, - { - "name": "sessionId", - "in": "path", - "description": "The session ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "required": true - }, - { - "name": "eventId", - "in": "path", - "description": "The event ID.", - "schema": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "The event ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { - "204": { - "description": "DeleteSessionEvent 204 response" + "201": { + "description": "CreateNamespace 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateNamespaceResponseContent" + } + } + } }, "400": { "description": "BadRequestError 400 response", @@ -1657,6 +1788,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -1667,6 +1808,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1699,63 +1850,45 @@ } }, "tags": [ - "Session Memory" + "Namespaces" ] - }, - "get": { - "description": "Returns a single event from a session by event ID.", - "operationId": "GetSessionEvent", + } + }, + "/v1/stores/{storeId}/namespaces/{namespaceId}": { + "delete": { + "description": "Deletes an empty leaf namespace. Returns 409 when the namespace has children or memory placements, or when it changed concurrently.", + "operationId": "DeleteNamespace", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true }, { - "name": "sessionId", + "name": "namespaceId", "in": "path", - "description": "The session ID.", + "description": "Server-generated namespace identifier.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "required": true - }, - { - "name": "eventId", - "in": "path", - "description": "The event ID.", - "schema": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "The event ID." + "description": "Server-generated namespace identifier." }, "required": true } ], "responses": { - "200": { - "description": "GetSessionEvent 200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetSessionEventResponseContent" - } - } - } + "204": { + "description": "DeleteNamespace 204 response" }, "400": { "description": "BadRequestError 400 response", @@ -1807,6 +1940,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -1817,6 +1960,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1849,12 +2002,1275 @@ } }, "tags": [ - "Session Memory" + "Namespaces" ] - } - } - }, - "components": { + }, + "get": { + "description": "Gets one namespace by its canonical ID.", + "operationId": "GetNamespace", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "namespaceId", + "in": "path", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetNamespace 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetNamespaceResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Namespaces" + ] + }, + "patch": { + "description": "Renames or archives a namespace. Returns 409 when the namespace changed concurrently, an active or archived sibling already uses the requested name, or the namespace is archived.", + "operationId": "UpdateNamespace", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateNamespaceRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "namespaceId", + "in": "path", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateNamespace 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateNamespaceResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Namespaces" + ] + } + }, + "/v1/stores/{storeId}/session-memory": { + "get": { + "description": "Returns a paginated list of session IDs for a store.", + "operationId": "ListSessions", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page." + } + }, + { + "name": "filterOwnerId", + "in": "query", + "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll." + } + }, + { + "name": "namespaceRef", + "in": "query", + "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll." + } + }, + { + "name": "includeAll", + "in": "query", + "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters.", + "schema": { + "type": "boolean", + "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters." + } + } + ], + "responses": { + "200": { + "description": "ListSessions 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSessionsResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/session-memory/events": { + "post": { + "description": "Appends a single event to a session. Creates the session if it does not exist. If sessionId is omitted, the server generates one.", + "operationId": "AddSessionEvent", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddSessionEventRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + } + ], + "responses": { + "201": { + "description": "AddSessionEvent 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddSessionEventResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/session-memory/{sessionId}": { + "delete": { + "description": "Deletes the session memory for a session.", + "operationId": "DeleteSessionMemory", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteSessionMemory 204 response" + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + }, + "get": { + "description": "Returns the session memory for a session.", + "operationId": "GetSessionMemory", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + }, + { + "name": "includeSummarisedEvents", + "in": "query", + "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage.", + "schema": { + "type": "boolean", + "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage." + } + } + ], + "responses": { + "200": { + "description": "GetSessionMemory 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSessionMemoryResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/session-memory/{sessionId}/events/{eventId}": { + "delete": { + "description": "Deletes a single event from a session by event ID.", + "operationId": "DeleteSessionEvent", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + }, + { + "name": "eventId", + "in": "path", + "description": "The event ID.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "The event ID." + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteSessionEvent 204 response" + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + }, + "get": { + "description": "Returns a single event from a session by event ID.", + "operationId": "GetSessionEvent", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + }, + { + "name": "eventId", + "in": "path", + "description": "The event ID.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "The event ID." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetSessionEvent 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSessionEventResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + } + }, + "components": { "schemas": { "AddSessionEventRequestContent": { "type": "object", @@ -1878,7 +3294,11 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional namespace for the session, used as the namespace of every long-term memory extracted from it.\n\nLike `ownerId`, this is a session-level value taken from the first event: the value sent with the first event of a session is stored on the session, and values sent with later events are ignored. Omit it to leave extracted memories without a namespace." + "description": "Optional deprecated namespace label for the session. This label does not create or resolve a namespace resource. Do not supply it with namespaceRef.\n\nLike `ownerId`, this is a session-level value taken from the first event: the value sent with the first event of a session is stored on the session, and values sent with later events are ignored. Omit it to leave extracted memories without a namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" }, "role": { "$ref": "#/components/schemas/MessageRole" @@ -2191,6 +3611,45 @@ "id" ] }, + "ConflictErrorResponseContent": { + "type": "object", + "description": "The request conflicts with the current state of the resource.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 409, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/ConflictErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "ConflictErrorType": { + "type": "string", + "description": "Problem type URI for conflict errors.", + "enum": [ + "/errors/conflict" + ] + }, "Content": { "description": "A single content item in a message. Exactly one member is set.", "oneOf": [ @@ -2252,27 +3711,77 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional namespace for logical grouping (1-64 chars, alphanumeric and dashes). Omit to create the record without a namespace. Records created here are explicit writes, so this value is taken as given; extraction instead inherits the source session's namespace." - }, - "topics": { - "type": "array", - "items": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "description": "Topic name (1-100 chars)." - }, - "maxItems": 50, - "description": "Topic tags for categorization (max 50 topics, each 1-100 chars)." - }, - "attributes": { - "$ref": "#/components/schemas/AttributeMap" + "description": "Optional deprecated namespace label. It does not create or resolve a namespace resource. Do not supply it with namespaceRef. If both fields are omitted, the record uses automatic routing when enabled; if routing is disabled or no destination qualifies, it is created without a namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" + }, + "topics": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Topic name (1-100 chars)." + }, + "maxItems": 50, + "description": "Topic tags for categorization (max 50 topics, each 1-100 chars)." + }, + "attributes": { + "$ref": "#/components/schemas/AttributeMap" + } + }, + "required": [ + "id", + "ownerId", + "text" + ] + }, + "CreateNamespaceRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One namespace segment. Use with scope for a root or parentId for a child." + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "A complete hierarchy path to create. Missing segments are created as needed. Mutually exclusive with name and parentId. The path does not encode scope." + }, + "scope": { + "$ref": "#/components/schemas/NamespaceScope" + }, + "parentId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Parent namespace for a direct child." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner for a personal root or path. Required for personal scope; omit for shared roots and children." + } + } + }, + "CreateNamespaceResponseContent": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" } }, "required": [ - "id", - "ownerId", - "text" + "namespace" ] }, "CreatedAtFilter": { @@ -2457,7 +3966,11 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping. Absent when the record has none: either it was written directly without one, or it was extracted from a session that has no namespace." + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" }, "topics": { "type": "array", @@ -2491,6 +4004,28 @@ "updatedAt" ] }, + "GetNamespaceResponseContent": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" + } + }, + "required": [ + "namespace" + ] + }, + "GetNamespaceRoutingResponseContent": { + "type": "object", + "properties": { + "policy": { + "$ref": "#/components/schemas/NamespaceRoutingPolicy" + } + }, + "required": [ + "policy" + ] + }, "GetSessionEventResponseContent": { "type": "object", "description": "Response containing a single requested session event.", @@ -2526,7 +4061,11 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The namespace of the session, set from the namespace of the first event. Absent when the first event did not supply one; long-term memories extracted from this session then carry no namespace." + "description": "Deprecated namespace label for the session, set from the first event. Absent when the session uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" }, "events": { "type": "array", @@ -2564,6 +4103,24 @@ "healthy" ] }, + "ListNamespacesResponseContent": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Namespace" + } + }, + "nextPageToken": { + "type": "string", + "description": "Opaque token for fetching the next page. Omitted when no more results." + } + }, + "required": [ + "items" + ] + }, "ListSessionsResponseContent": { "type": "object", "description": "Paginated list of session IDs for a store.", @@ -2594,6 +4151,20 @@ "total" ] }, + "LlmPromptSpec": { + "type": "object", + "description": "Customer-authored prompt evaluated by the LLM routing strategy.", + "properties": { + "prompt": { + "type": "string", + "maxLength": 2000, + "minLength": 1 + } + }, + "required": [ + "prompt" + ] + }, "LongTermMemoryFilter": { "type": "object", "description": "Grouping of filter conditions for long-term memory search.", @@ -2607,6 +4178,13 @@ "namespace": { "$ref": "#/components/schemas/NamespaceFilter" }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefFilter" + }, + "unnamespaced": { + "type": "boolean", + "description": "When true, matches only records that have neither a deprecated namespace label nor a namespace resource placement. This scope is always combined with every other filter condition. Do not supply namespace or namespaceRef with this field." + }, "topics": { "$ref": "#/components/schemas/TopicsFilter" }, @@ -2616,140 +4194,399 @@ "createdAt": { "$ref": "#/components/schemas/CreatedAtFilter" }, - "attributes": { - "$ref": "#/components/schemas/AttributeFilterMap" + "attributes": { + "$ref": "#/components/schemas/AttributeFilterMap" + } + } + }, + "MatcherKind": { + "type": "string", + "description": "The matching techniques this API supports.", + "enum": [ + "regex" + ] + }, + "MatcherSpec": { + "type": "object", + "description": "How a detector matches, as a tagged union of techniques.\n\nExactly the block naming the declared kind must be present; a block belonging to\nany other kind is rejected rather than guessed at. Further kinds are additive.", + "properties": { + "kind": { + "$ref": "#/components/schemas/MatcherKind" + }, + "regex": { + "$ref": "#/components/schemas/RegexSpec" + } + }, + "required": [ + "kind" + ] + }, + "MemoryFailedDependencyErrorResponseContent": { + "type": "object", + "description": "A resource or model-provider dependency required by Agent Memory could not complete the request.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 424, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/MemoryFailedDependencyErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "MemoryFailedDependencyErrorType": { + "type": "string", + "description": "Problem type URI for Agent Memory failed-dependency errors.", + "enum": [ + "/errors/resource-unavailable", + "/errors/database-out-of-memory", + "/errors/invalid-model-credentials", + "/errors/model-quota-exceeded" + ] + }, + "MemoryRecord": { + "type": "object", + "description": "A long-term memory record.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Unique memory record ID." + }, + "text": { + "type": "string", + "description": "The memory content." + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Type of memory. Built-in or a custom type registered on the store." + }, + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session ID associated with this memory." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner ID associated with this memory." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" + }, + "topics": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Topic name (1-100 chars)." + }, + "maxItems": 50, + "description": "Topic tags for categorization." + }, + "attributes": { + "$ref": "#/components/schemas/AttributeMap" + }, + "createdAt": { + "type": "string", + "description": "Timestamp when the memory was created (UTC).", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "description": "Timestamp when the memory was last updated (UTC).", + "format": "date-time" + } + }, + "required": [ + "createdAt", + "id", + "text", + "updatedAt" + ] + }, + "MemoryTypeFilter": { + "type": "object", + "description": "Filter by memory type. Matching is case-insensitive.", + "properties": { + "eq": { + "type": "string", + "description": "Equals the given value." + }, + "ne": { + "type": "string", + "description": "Not equal to the given value." + }, + "in": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Matches any of the given values." + }, + "all": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Matches all of the given values." + } + } + }, + "MessageRole": { + "type": "string", + "description": "Message role for session events.", + "enum": [ + "USER", + "ASSISTANT", + "SYSTEM" + ] + }, + "MoveLongTermMemoriesRequestContent": { + "type": "object", + "properties": { + "memoryIds": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "maxItems": 100, + "minItems": 1 + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" + } + }, + "required": [ + "memoryIds" + ] + }, + "MoveLongTermMemoriesResponseContent": { + "type": "object", + "properties": { + "moved": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + } + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkOperationError" + } } - } + }, + "required": [ + "moved" + ] }, - "MemoryRecord": { + "Namespace": { "type": "object", - "description": "A long-term memory record.", + "description": "A namespace hierarchy record.", "properties": { - "id": { + "namespaceId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique memory record ID." - }, - "text": { - "type": "string", - "description": "The memory content." + "description": "Server-generated namespace identifier." }, - "memoryType": { + "name": { "type": "string", "maxLength": 64, "minLength": 1, - "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Type of memory. Built-in or a custom type registered on the store." + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One case-sensitive namespace path segment." }, - "sessionId": { + "parentId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session ID associated with this memory." + "description": "Server-generated namespace identifier." }, - "ownerId": { + "rootId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner ID associated with this memory." + "description": "Server-generated namespace identifier." }, - "namespace": { + "ownerId": { "type": "string", - "maxLength": 64, "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping. Absent when the record has none: either it was written directly without one, or it was extracted from a session that has no namespace." + "description": "An opaque application-user ID selected by the platform for a namespace root." }, - "topics": { - "type": "array", - "items": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "description": "Topic name (1-100 chars)." - }, - "maxItems": 50, - "description": "Topic tags for categorization." + "scope": { + "$ref": "#/components/schemas/NamespaceScope" }, - "attributes": { - "$ref": "#/components/schemas/AttributeMap" + "state": { + "$ref": "#/components/schemas/NamespaceState" + }, + "revision": { + "type": "integer", + "format": "int64" }, "createdAt": { "type": "string", - "description": "Timestamp when the memory was created (UTC).", "format": "date-time" }, "updatedAt": { "type": "string", - "description": "Timestamp when the memory was last updated (UTC).", "format": "date-time" + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "Current case-sensitive path, derived from the live hierarchy." } }, "required": [ "createdAt", - "id", - "text", + "name", + "namespaceId", + "path", + "revision", + "rootId", + "scope", + "state", "updatedAt" ] }, - "MemoryTypeFilter": { + "NamespaceFilter": { "type": "object", - "description": "Filter by memory type. Matching is case-insensitive.", + "description": "Filter by namespace. Matching is case-insensitive.\n\nNamespace is optional, so records without one need care. The positive operators (`eq`, `in`, `all`) require the field to be present and therefore never match a record that has no namespace \u2014 including memories extracted from a session that has none. `ne` is a plain negation and *does* match those records, so use `eq` or `in` when you need strict namespace isolation rather than `ne` against the namespaces you want to exclude.\n\nOmit this filter to search across every namespace, and use it to keep recall inside one project, workspace, team, environment, or tenant.", "properties": { "eq": { "type": "string", - "description": "Equals the given value." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Equals the given value.", + "deprecated": true }, "ne": { "type": "string", - "description": "Not equal to the given value." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Not equal to the given value.", + "deprecated": true }, "in": { "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true }, "description": "Matches any of the given values." }, "all": { "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true }, "description": "Matches all of the given values." } } }, - "MessageRole": { - "type": "string", - "description": "Message role for session events.", - "enum": [ - "USER", - "ASSISTANT", - "SYSTEM" - ] - }, - "NamespaceFilter": { + "NamespaceRef": { "type": "object", - "description": "Filter by namespace. Matching is case-insensitive.\n\nNamespace is optional, so records without one need care. The positive operators (`eq`, `in`, `all`) require the field to be present and therefore never match a record that has no namespace — including memories extracted from a session that has none. `ne` is a plain negation and *does* match those records, so use `eq` or `in` when you need strict namespace isolation rather than `ne` against the namespaces you want to exclude.\n\nOmit this filter to search across every namespace, and use it to keep recall inside one project, workspace, team, environment, or tenant.", + "description": "Reference to a namespace resource with its stable identifier and current display data.", "properties": { - "eq": { + "namespaceId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value." + "description": "Stable namespace resource identifier." }, - "ne": { + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Current namespace name." + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "Current namespace path." + } + }, + "required": [ + "name", + "namespaceId", + "path" + ] + }, + "NamespaceRefFilter": { + "type": "object", + "description": "Filter by stable namespace resource identifier. Supports exact or any-of matching and matches only resource-backed records, never deprecated namespace labels.", + "properties": { + "eq": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Not equal to the given value." + "description": "Equals the given value." }, "in": { "type": "array", @@ -2758,22 +4595,104 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping (1-64 chars, alphanumeric and dashes).\n\nThis is the shared namespace format for both session memory and long-term memory: sessions carry it as session-level state, and long-term memory records carry it per record. The same constraints apply everywhere it is used.\n\nNamespace is always optional; a record without one is valid. Applications use it to isolate memories by project, workspace, team, environment, or tenant. See `NamespaceFilter` for how records without a namespace behave under search.\n\nA namespace reaches a record one of two ways. Records written directly carry whatever namespace the caller sets. Records produced by extraction inherit the namespace of the session they were extracted from, which is fixed when that session is created; a session with no namespace yields records with no namespace. Existing records are never backfilled when a namespace is introduced later." + "description": "Server-generated namespace identifier." }, "description": "Matches any of the given values." + } + } + }, + "NamespaceRefInput": { + "type": "object", + "description": "Reference to an existing namespace resource for a write or namespace-scoped operation.", + "properties": { + "namespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable namespace resource identifier." + } + }, + "required": [ + "namespaceId" + ] + }, + "NamespaceRoutingPolicy": { + "type": "object", + "description": "Store-level automatic namespace routing and admission policy. Global rules target shared namespaces only. Accept rules select candidates in list order. All enabled reject rules for a destination apply regardless of their position or how the destination was selected.", + "properties": { + "enabled": { + "type": "boolean" }, - "all": { + "rules": { "type": "array", "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping (1-64 chars, alphanumeric and dashes).\n\nThis is the shared namespace format for both session memory and long-term memory: sessions carry it as session-level state, and long-term memory records carry it per record. The same constraints apply everywhere it is used.\n\nNamespace is always optional; a record without one is valid. Applications use it to isolate memories by project, workspace, team, environment, or tenant. See `NamespaceFilter` for how records without a namespace behave under search.\n\nA namespace reaches a record one of two ways. Records written directly carry whatever namespace the caller sets. Records produced by extraction inherit the namespace of the session they were extracted from, which is fixed when that session is created; a session with no namespace yields records with no namespace. Existing records are never backfilled when a namespace is introduced later." + "$ref": "#/components/schemas/NamespaceRoutingRule" }, - "description": "Matches all of the given values." + "maxItems": 32 } - } + }, + "required": [ + "enabled" + ] + }, + "NamespaceRoutingRule": { + "type": "object", + "description": "One routing or admission rule. action is accept or reject. Strategies run in list order and the first match fires the rule. An accept with no strategies always matches; a reject with no strategies never matches. A matching accept selects a candidate. Every enabled reject for that destination must pass before placement. A veto skips that candidate during automatic routing and fails explicit placement.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Routing rule identifier (1-64 chars). Published in logs, so it is an identifier rather than free text." + }, + "destination": { + "$ref": "#/components/schemas/NamespaceRefInput" + }, + "enabled": { + "type": "boolean" + }, + "action": { + "$ref": "#/components/schemas/RoutingRuleAction" + }, + "strategies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RouteCondition" + }, + "maxItems": 8 + } + }, + "required": [ + "action", + "destination", + "enabled", + "id" + ] + }, + "NamespaceScope": { + "type": "string", + "description": "The root scope of a namespace hierarchy.", + "enum": [ + "PERSONAL", + "SHARED" + ] + }, + "NamespaceState": { + "type": "string", + "description": "Whether a namespace accepts new children and placements.", + "enum": [ + "ACTIVE", + "ARCHIVED" + ] + }, + "NamespaceUpdateState": { + "type": "string", + "description": "The archive-only state transition accepted by UpdateNamespace.", + "enum": [ + "ARCHIVED" + ] }, "NotFoundErrorResponseContent": { "type": "object", @@ -2895,6 +4814,116 @@ "/errors/payload-too-large" ] }, + "PutNamespaceRoutingRequestContent": { + "type": "object", + "properties": { + "policy": { + "$ref": "#/components/schemas/NamespaceRoutingPolicy" + } + }, + "required": [ + "policy" + ] + }, + "PutNamespaceRoutingResponseContent": { + "type": "object", + "properties": { + "policy": { + "$ref": "#/components/schemas/NamespaceRoutingPolicy" + } + }, + "required": [ + "policy" + ] + }, + "RegexSpec": { + "type": "object", + "description": "A regular expression to match against.\n\nEvaluated by RE2, which runs in linear time \u2014 so an expensive pattern is not a\ndenial-of-service risk, but lookaround and backreferences are unavailable.\nRejected on write if it does not compile, or if it can match without consuming\ntext: such a pattern matches at every position, so it would exclude every memory\nthe store writes.", + "properties": { + "pattern": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "The pattern, in RE2 syntax." + } + }, + "required": [ + "pattern" + ] + }, + "ResourceSuspendedErrorResponseContent": { + "type": "object", + "description": "The requested resource exists but is suspended by an administrator.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 423, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/ResourceSuspendedErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "ResourceSuspendedErrorType": { + "type": "string", + "description": "Problem type URI for suspended resources.", + "enum": [ + "/errors/resource-suspended" + ] + }, + "RouteCondition": { + "type": "object", + "description": "One plug-in strategy invocation. Exactly the block naming the declared kind must be present. List order is evaluation order.", + "properties": { + "kind": { + "$ref": "#/components/schemas/RouteStrategyKind" + }, + "llmPrompt": { + "$ref": "#/components/schemas/LlmPromptSpec" + }, + "matcher": { + "$ref": "#/components/schemas/MatcherSpec" + } + }, + "required": [ + "kind" + ] + }, + "RouteStrategyKind": { + "type": "string", + "description": "Routing strategy kinds. Unknown kinds are rejected. New kinds register on the evaluator without changing namespace records.", + "enum": [ + "llm_prompt", + "matcher_spec" + ] + }, + "RoutingRuleAction": { + "type": "string", + "description": "Whether the rule accepts into the destination or rejects that destination.", + "enum": [ + "accept", + "reject" + ] + }, "SearchLongTermMemoryRequestContent": { "type": "object", "description": "Semantic search request for long-term memory records.", @@ -3299,7 +5328,11 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The caller's namespace. The update is rejected with 409 unless the record's stored namespace matches exactly.\n\nOmit it to assert that the record has no namespace — an omitted value matches only records stored without one, it does not mean \"any namespace\". The check is applied atomically with the update, so a record cannot be re-filed into another namespace between the check and the write." + "description": "The caller's current deprecated namespace label. The update is rejected with 409 unless the record still stores that label. Do not supply it with namespaceRef.\n\nOmit both namespace and namespaceRef to assert that the record has no namespace placement. Omission does not mean \"any namespace\".", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" }, "text": { "type": "string", @@ -3345,7 +5378,11 @@ }, "namespace": { "type": "string", - "description": "Updated namespace. When provided, must be 1-64 chars alphanumeric and dashes; omit the field or send an empty string to clear.\n\nThis is the only way to change the namespace of an existing record, including one produced by extraction: a record inherits its session's namespace at creation and is not revised afterwards, so use this to re-file a record or to add a namespace to one created before the session had any." + "description": "Updated deprecated namespace label. When provided, it must be 1-64 alphanumeric characters or dashes. Send an empty string to clear it.\nDo not supply it with namespaceRef.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" }, "ownerId": { "type": "string", @@ -3398,7 +5435,11 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping. Absent when the record has none: either it was written directly without one, or it was extracted from a session that has no namespace." + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" }, "topics": { "type": "array", @@ -3431,6 +5472,32 @@ "text", "updatedAt" ] + }, + "UpdateNamespaceRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One case-sensitive namespace path segment." + }, + "state": { + "$ref": "#/components/schemas/NamespaceUpdateState" + } + } + }, + "UpdateNamespaceResponseContent": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" + } + }, + "required": [ + "namespace" + ] } } } From 1825638aab1b2727737ba831dfe4937ab5c103c5 Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Mon, 28 Sep 2026 20:43:23 -0700 Subject: [PATCH 05/26] docs: explain sortable session listing --- .../api-reference/openapi-agent-memory.json | 349 ++++++++++++++++++ .../agent-memory/rest-api-quickstart.md | 13 + .../agent-memory/self-managed/api-examples.md | 15 + 3 files changed, 377 insertions(+) diff --git a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json index bbab8e30d1..a055ea7b6e 100644 --- a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json +++ b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json @@ -1852,6 +1852,213 @@ "Session Memory" ] } + }, + "/v1/stores/{storeId}/sessions": { + "get": { + "description": "Returns paginated session rows with metadata and caller-selected order. The ID-only ListSessions operation remains unchanged.", + "operationId": "ListSessionSummaries", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page. Bound to the effective filters and sort.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page. Bound to the effective filters and sort." + } + }, + { + "name": "filterOwnerId", + "in": "query", + "description": "Filter sessions by owner. Combined with namespaceRef using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by owner. Combined with namespaceRef using AND. Mutually exclusive with includeAll." + } + }, + { + "name": "namespaceRef", + "in": "query", + "description": "Filter by stable namespace ID. Combined with filterOwnerId using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter by stable namespace ID. Combined with filterOwnerId using AND. Mutually exclusive with includeAll." + } + }, + { + "name": "includeAll", + "in": "query", + "description": "Set to true to list all visible sessions. Required when no filter is given; mutually exclusive with filters.", + "schema": { + "type": "boolean", + "description": "Set to true to list all visible sessions. Required when no filter is given; mutually exclusive with filters." + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Sort field. Defaults to updatedAt.", + "schema": { + "$ref": "#/components/schemas/SessionListSortBy" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort direction. Defaults to desc for timestamps and asc for sessionId.", + "schema": { + "$ref": "#/components/schemas/SessionListSortOrder" + } + } + ], + "responses": { + "200": { + "description": "ListSessionSummaries 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSessionSummariesResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } } }, "components": { @@ -3431,6 +3638,148 @@ "text", "updatedAt" ] + }, + "ResourceSuspendedErrorResponseContent": { + "type": "object", + "description": "The requested resource exists but is suspended by an administrator.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 423, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/ResourceSuspendedErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "ResourceSuspendedErrorType": { + "type": "string", + "description": "Problem type URI for suspended resources.", + "enum": [ + "/errors/resource-suspended" + ] + }, + "ListSessionSummariesResponseContent": { + "type": "object", + "description": "Paginated session rows for a store.", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionListItem" + } + }, + "total": { + "type": "integer", + "format": "int32" + }, + "nextPageToken": { + "type": "string" + } + }, + "required": [ + "items", + "total" + ] + }, + "SessionListItem": { + "type": "object", + "description": "One session row without events or summary text. Owner ID and timestamps are absent when unknown on legacy sessions.", + "properties": { + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Absent for a legacy session that has no recorded owner." + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" + } + }, + "required": [ + "sessionId" + ] + }, + "NamespaceRef": { + "type": "object", + "description": "Reference to a namespace resource with its stable identifier and current display data.", + "properties": { + "namespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable namespace resource identifier." + }, + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Current namespace name." + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "Current namespace path." + } + }, + "required": [ + "name", + "namespaceId", + "path" + ] + }, + "SessionListSortOrder": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "SessionListSortBy": { + "type": "string", + "enum": [ + "sessionId", + "createdAt", + "updatedAt" + ] } } } diff --git a/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md b/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md index 9a5d00feab..a37a75709e 100644 --- a/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md @@ -94,6 +94,19 @@ The `events` array contains the stored message, its role, actor, and timestamps. > [!NOTE] > **What to expect:** The `events` array contains the travel message. Redis Agent Memory adds an `eventId` and `systemTimestamp`, showing that the application can recover the complete event later using only the session ID. +To find this owner's sessions by recent activity, request session rows: + +```sh +curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $API_KEY" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/sessions?filterOwnerId=$OWNER_ID&limit=20" | jq +``` + +The response includes `sessionId` and the available owner, creation, update, +and namespace details. It defaults to newest `updatedAt` first. Use +`nextPageToken` as `pageToken` with the same filter and sort options for the +next page. The existing `/session-memory` list returns session IDs. + ## 2. Recall automatically extracted information Redis Agent Memory processes session events in the background and creates long term memories for information that may be useful in later conversations. You configured the extraction cadence to one minute when you created the service. You do not need to submit a separate memory creation request. diff --git a/content/operate/iris/agent-memory/self-managed/api-examples.md b/content/operate/iris/agent-memory/self-managed/api-examples.md index 6bca9153f7..aab8e1fb06 100644 --- a/content/operate/iris/agent-memory/self-managed/api-examples.md +++ b/content/operate/iris/agent-memory/self-managed/api-examples.md @@ -244,6 +244,21 @@ curl -sS "$DP_URL/v1/stores/$STORE_ID/session-memory?includeAll=true" \ `filterOwnerId` and `includeAll` are mutually exclusive. +The `/session-memory` list returns session IDs. To get session rows ordered by +recent activity, use `/sessions`: + +```bash +curl -sS "$DP_URL/v1/stores/$STORE_ID/sessions?filterOwnerId=user-001&sortBy=updatedAt&sortOrder=desc&limit=20" \ + -H "Authorization: Bearer $RAM_AGENT_KEY" +``` + +Each row includes `sessionId` and, when known, `ownerId`, `createdAt`, +`updatedAt`, and `namespaceRef`. Older sessions can have no owner or timestamps. +The default order is `updatedAt` descending. You can also sort by `sessionId` +or `createdAt`, each in ascending or descending order. Send `nextPageToken` as +`pageToken` with the same filters and sort options to fetch the next page. +The new route uses the same filter and authorization rules as `/session-memory`. + ### Create long-term memories directly ```bash From 9650c36724589c6600a593afc66fc885ded843ce Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Tue, 29 Sep 2026 10:46:59 +0100 Subject: [PATCH 06/26] DOC-7121 Consolidate agent-memory Python/TypeScript SDK quickstarts into one TCE page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Python and TypeScript agent-memory quickstarts were near-duplicate prose with only the code fences differing. Merged them into one page (sdk-quickstart.md) using the clients-example tabbed system, with two new pseudo-client keys ("Agent Memory (Python)"/"Agent Memory (TypeScript)") instead of reusing the real Python/Node.js client keys, so the tabs don't misattribute to redis-py/node-redis. Two build-script gaps surfaced adding the TypeScript source file: build/local_examples.py's EXTENSION_TO_LANGUAGE had no .ts entry at all, and build/components/example.py's PREFIXES dict had no 'typescript' comment style either — no .ts file has ever existed under local_examples/ before this. Both needed fixing together or the .ts source silently fails (first as an "unknown extension" skip, then as a None-content crash further down the pipeline once the language is recognized but has no comment prefix). Modeled the footer-hiding on langcache_sdk's page, but its show_footer="false" parameter turned out to be dead code — clients-example.html only ever checks footer="hide" (confirmed back to the commit that introduced it). langcache's live page is therefore not hiding what it thinks it's hiding. Used the parameter that actually works here; did not fix langcache's page (out of scope for this ticket). The tab's visible title is literally the config.toml client key string (only "Node.js"/"ioredis" get a hardcoded display-name override), so the new keys had to be chosen to read well as tab labels directly, not as short internal IDs. Each clients-example step had to become a fully self-contained snippet (imports + client construction repeated per step) rather than the "keep editing one growing file" narrative the original two pages used, because the shortcode slices only the exact STEP_START/STEP_END range with nothing prepended. Learned: .ts support needs EXTENSION_TO_LANGUAGE and PREFIXES kept in sync; langcache_sdk's show_footer param is dead code Constraint: a clients-example tab's display name is its config.toml key verbatim (no Node.js/ioredis-style override exists for new keys) Rejected: show_footer="false" | dead parameter in clients-example.html, only footer="hide" is read Directive: don't add another .ts-based local example without confirming EXTENSION_TO_LANGUAGE (build/local_examples.py) and PREFIXES (build/components/example.py) both still list it Gaps: agent_memory_sdk.py/.ts are hand-written, not harness-verified — no confirmed access to a running Agent Memory backend Ticket: DOC-7121 Co-Authored-By: Claude Sonnet 5 --- build/components/example.py | 1 + build/local_examples.py | 10 + config.toml | 4 +- .../ai/context-engine/agent-memory/_index.md | 18 +- .../agent-memory/developer-guide.md | 7 +- .../agent-memory/rest-api-quickstart.md | 2 +- ...on-sdk-quickstart.md => sdk-quickstart.md} | 199 +++-------- .../agent-memory/typescript-sdk-quickstart.md | 320 ------------------ .../agent-memory/agent_memory_sdk.py | 277 +++++++++++++++ .../agent-memory/agent_memory_sdk.ts | 282 +++++++++++++++ 10 files changed, 635 insertions(+), 485 deletions(-) rename content/develop/ai/context-engine/agent-memory/{python-sdk-quickstart.md => sdk-quickstart.md} (52%) delete mode 100644 content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md create mode 100644 local_examples/agent-memory/agent_memory_sdk.py create mode 100644 local_examples/agent-memory/agent_memory_sdk.ts diff --git a/build/components/example.py b/build/components/example.py index 32e83d020d..8b7199e68e 100644 --- a/build/components/example.py +++ b/build/components/example.py @@ -24,6 +24,7 @@ PREFIXES = { 'python': '#', 'node.js': '//', + 'typescript': '//', 'ioredis': '//', 'java': '//', 'java-sync': '//', diff --git a/build/local_examples.py b/build/local_examples.py index aa539c5765..b688926af4 100644 --- a/build/local_examples.py +++ b/build/local_examples.py @@ -24,6 +24,7 @@ EXTENSION_TO_LANGUAGE = { '.py': 'python', '.js': 'node.js', + '.ts': 'typescript', '.go': 'go', '.c': 'c', '.h': 'c', @@ -101,13 +102,20 @@ def get_client_name_from_language_and_path(language: str, path: str, For Python (.py) files, override based on path substrings: - If 'redisvl' in path -> RedisVL + - If 'agent-memory' in path -> Agent Memory (Python) - Otherwise -> Python + For TypeScript (.ts) files, override based on path substrings: + - If 'agent-memory' in path -> Agent Memory (TypeScript) + Substring checks are case-sensitive and can appear anywhere in the path. """ if language == 'node.js': if 'ioredis' in path: return 'ioredis' + if language == 'typescript': + if 'agent-memory' in path: + return 'Agent Memory (TypeScript)' if language == 'java': if 'lettuce-sync' in path: return 'Lettuce-Sync' @@ -123,6 +131,8 @@ def get_client_name_from_language_and_path(language: str, path: str, if language == 'python': if 'redisvl' in path: return 'RedisVL' + if 'agent-memory' in path: + return 'Agent Memory (Python)' if language == 'c#': variant = 'Async' if 'async' in path else 'Sync' client = 'NRedisStack' if is_nredisstack_example(content) else 'SE.Redis' diff --git a/config.toml b/config.toml index d4365ed220..edc18ab63d 100644 --- a/config.toml +++ b/config.toml @@ -87,7 +87,7 @@ gitHubRepo = "https://github.com/redis/docs" aiSchemaVersion = 2 # Display and sort order for client examples -clientsExamples = ["Python", "Node.js", "ioredis", "Java-Sync", "Lettuce-Sync", "Java-Async", "Java-Reactive", "Go", "C", "C#-Sync (NRedisStack)", "C#-Async (NRedisStack)", "C#-Sync (SE.Redis)", "C#-Async (SE.Redis)", "RedisVL", "PHP", "Ruby", "Rust-Sync", "Rust-Async"] +clientsExamples = ["Python", "Node.js", "ioredis", "Java-Sync", "Lettuce-Sync", "Java-Async", "Java-Reactive", "Go", "C", "C#-Sync (NRedisStack)", "C#-Async (NRedisStack)", "C#-Sync (SE.Redis)", "C#-Async (SE.Redis)", "RedisVL", "PHP", "Ruby", "Rust-Sync", "Rust-Async", "Agent Memory (Python)", "Agent Memory (TypeScript)"] searchService = "/convai/api/search-service" ratingsService = "/docusight/api/rate/docs" @@ -120,6 +120,8 @@ rdi_current_version = "2.0.0" "Ruby"={quickstartSlug="redis-rb", langId="ruby", clientId="redis-rb", clientName="redis-rb", mappingClientId="redis_rb"} "Rust-Sync"={quickstartSlug="rust", langId="rust", clientId="redis-rs", clientName="redis-rs", mappingClientId="redis_rs_sync"} "Rust-Async"={quickstartSlug="rust", langId="rust", clientId="redis-rs", clientName="redis-rs", mappingClientId="redis_rs_async"} +"Agent Memory (Python)"={quickstartSlug="", langId="python", clientId="agent-memory-python", clientName="Agent Memory (Python)", mappingClientId=""} +"Agent Memory (TypeScript)"={quickstartSlug="", langId="typescript", clientId="agent-memory-typescript", clientName="Agent Memory (TypeScript)", mappingClientId=""} # Mount directories for duplicate content [module] diff --git a/content/develop/ai/context-engine/agent-memory/_index.md b/content/develop/ai/context-engine/agent-memory/_index.md index c1eb7eb183..cac480ff6b 100644 --- a/content/develop/ai/context-engine/agent-memory/_index.md +++ b/content/develop/ai/context-engine/agent-memory/_index.md @@ -23,10 +23,9 @@ When enabled, automatic summarization compacts session memory by summarizing old Access Redis Agent Memory through the Python and TypeScript SDKs or its REST API. It works with any agent framework or LLM provider. -
+
{{< tile-card color="bg-blue-300" title="Overview" description="What's the same and what's different if you already know Redis" url="/develop/ai/context-engine/agent-memory/overview" >}} - {{< tile-card color="bg-redis-yellow-500" title="Python SDK" description="Explore Redis Agent Memory with Python" url="/develop/ai/context-engine/agent-memory/python-sdk-quickstart" >}} - {{< tile-card color="bg-redis-yellow-500" title="TypeScript SDK" description="Explore Redis Agent Memory with TypeScript" url="/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart" >}} + {{< tile-card color="bg-redis-yellow-500" title="SDK quickstart" description="Explore Redis Agent Memory with the Python or TypeScript SDK" url="/develop/ai/context-engine/agent-memory/sdk-quickstart" >}} {{< tile-card color="bg-teal-300" title="REST API" description="Explore Redis Agent Memory with curl" url="/develop/ai/context-engine/agent-memory/rest-api-quickstart" >}}
@@ -117,16 +116,11 @@ Get started with Redis Agent Memory on Redis Cloud or join the private preview f After your Redis Agent Memory service is ready, choose a client. Each quickstart follows the same travel planning scenario through session memory, automatic extraction, summarization, custom memory types, and sensitive-data exclusions. -
-
-

Python SDK

-

Explore the Redis Agent Memory workflow with the Python SDK.

-

}}">Open the Python quickstart

-
+
-

TypeScript SDK

-

Explore the Redis Agent Memory workflow with the TypeScript SDK.

-

}}">Open the TypeScript quickstart

+

Python or TypeScript SDK

+

Explore the Redis Agent Memory workflow with the Python or TypeScript SDK.

+

}}">Open the SDK quickstart

REST API

diff --git a/content/develop/ai/context-engine/agent-memory/developer-guide.md b/content/develop/ai/context-engine/agent-memory/developer-guide.md index f0520ec30d..3f31b0d703 100644 --- a/content/develop/ai/context-engine/agent-memory/developer-guide.md +++ b/content/develop/ai/context-engine/agent-memory/developer-guide.md @@ -26,8 +26,8 @@ Redis Agent Memory can automatically summarize older session events and extract | Client | Use it when | Package and quickstart | |:-------|:------------|:-----------------------| -| Python SDK | Your application or agent uses Python. | Install [`redis-agent-memory`](https://pypi.org/project/redis-agent-memory/) and follow the [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md). | -| TypeScript SDK | Your application or agent uses JavaScript or TypeScript. | Install [`@redis-iris/agent-memory`](https://www.npmjs.com/package/@redis-iris/agent-memory) and follow the [TypeScript SDK quickstart](/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md). | +| Python SDK | Your application or agent uses Python. | Install [`redis-agent-memory`](https://pypi.org/project/redis-agent-memory/) and follow the [SDK quickstart](/content/develop/ai/context-engine/agent-memory/sdk-quickstart.md). | +| TypeScript SDK | Your application or agent uses JavaScript or TypeScript. | Install [`@redis-iris/agent-memory`](https://www.npmjs.com/package/@redis-iris/agent-memory) and follow the [SDK quickstart](/content/develop/ai/context-engine/agent-memory/sdk-quickstart.md). | | REST API | You need language-independent HTTP access or don't want an SDK dependency. | No package required. Follow the [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md). | ## Connect to a Redis Agent Memory service @@ -173,8 +173,7 @@ Follow any of the client quickstarts to create and search long-term memory. For ## References -* [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md) -* [TypeScript SDK quickstart](/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md) +* [SDK quickstart](/content/develop/ai/context-engine/agent-memory/sdk-quickstart.md) * [Python SDK reference](https://pypi.org/project/redis-agent-memory/) * [TypeScript SDK reference](https://www.npmjs.com/package/@redis-iris/agent-memory) * [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md) diff --git a/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md b/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md index 9a5d00feab..519a8330e3 100644 --- a/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md @@ -250,6 +250,6 @@ See [sensitive-data exclusions](/content/operate/iris/agent-memory/create-servic ## Next steps -* Follow the [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md) or [TypeScript SDK quickstart](/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md). +* Follow the [SDK quickstart](/content/develop/ai/context-engine/agent-memory/sdk-quickstart.md). * Learn when to [create long term memories directly](/content/develop/ai/context-engine/agent-memory/developer-guide.md#create-long-term-memories). * Use the [Redis Agent Memory API reference](/content/develop/ai/context-engine/agent-memory/api-reference.md) for endpoint and schema details. diff --git a/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md b/content/develop/ai/context-engine/agent-memory/sdk-quickstart.md similarity index 52% rename from content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md rename to content/develop/ai/context-engine/agent-memory/sdk-quickstart.md index f98b5534ee..2aad46eb60 100644 --- a/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/sdk-quickstart.md @@ -4,11 +4,14 @@ categories: - docs - develop - ai -description: Explore session memory, automatic extraction, summarization, custom memory types, and sensitive-data exclusions with the Redis Agent Memory Python SDK. +description: Explore session memory, automatic extraction, summarization, custom memory types, and sensitive-data exclusions with the Redis Agent Memory Python and TypeScript SDKs. hideListLinks: true -linktitle: Python SDK quickstart -title: Redis Agent Memory Python SDK quickstart +linktitle: SDK quickstart +title: Redis Agent Memory SDK quickstart weight: 6 +aliases: +- /develop/ai/context-engine/agent-memory/python-sdk-quickstart/ +- /develop/ai/context-engine/agent-memory/typescript-sdk-quickstart/ --- Use this quickstart to follow a travel planning conversation through Redis Agent Memory. You will retrieve the conversation from session memory, recall information extracted in the background, inspect an automatically generated session summary, extract structured travel information, and guide extraction away from sensitive data. @@ -19,7 +22,7 @@ To complete this quickstart, you need: {{< embed-md "rc-agent-memory-quickstart-prerequisites.md" >}} -You also need Python 3.10 or later. +You also need Python 3.10 or later, or Node.js and npm. ## Create a Redis Agent Memory service @@ -39,115 +42,75 @@ Keep the API key out of source control, application logs, and other unsecured lo ## Install the SDK +{{< multitabs id="agent-memory-install" + tab1="Python" + tab2="TypeScript" >}} + ```sh python -m pip install redis-agent-memory ``` -## Create the client and check the service health - -Create `quickstart.py` with the following code. Replace `` and `` with the values from Redis Cloud. The endpoint must include `https://`. - -```python -import os -from datetime import datetime, timezone +-tab-sep- -from redis_agent_memory import AgentMemory, models - - -ENDPOINT = "" -STORE_ID = "" -SESSION_ID = "travel-planning-session" -USER_ID = "quickstart-user" +```sh +mkdir agent-memory-quickstart +cd agent-memory-quickstart +npm init -y +npm install @redis-iris/agent-memory +npm install --save-dev tsx +``` +{{< /multitabs >}} -def show(label, response): - print(f"{label}:") - print(response.model_dump_json(by_alias=True, indent=2)) +## Create the client and check the service health +Create `quickstart.py` or `quickstart.ts` with the following code. Replace `` and `` with the values from Redis Cloud. The endpoint must include `https://`. -def main(): - with AgentMemory( - ENDPOINT, - store_id=STORE_ID, - api_key=os.environ["API_KEY"], - ) as agent_memory: - health = agent_memory.health() - show("Service health", health) +{{< clients-example set="agent_memory_sdk" step="connect_health_check" footer="hide" description="Construct the client and check that Redis Agent Memory is reachable" difficulty="beginner" >}}{{< /clients-example >}} +Run the file: -if __name__ == "__main__": - main() +```sh +python quickstart.py ``` -Run the file: +or: ```sh -python quickstart.py +npx tsx quickstart.ts ``` A healthy response confirms that the client can reach Redis Agent Memory and authenticate with the API key. The first store request validates the Store ID. ## 1. Build conversation context with session memory -Session memory stores a conversation as an ordered sequence of events. Add the following code after the health check, inside the `with` block: - -```python - event = agent_memory.add_session_event( - session_id=SESSION_ID, - actor_id=USER_ID, - role=models.MessageRole.USER, - content=[models.Text( - text=( - "I am visiting Tokyo and Kyoto next month. " - "I am vegetarian and prefer spicy food." - ), - )], - created_at=datetime.now(timezone.utc), - ) - show("Created event", event) - - session = agent_memory.get_session_memory( - session_id=SESSION_ID, - ) - show("Session memory", session) -``` +Session memory stores a conversation as an ordered sequence of events. -Run the file again. The session response contains the stored message, its role, actor, and timestamps. An application can retrieve this session before the next agent turn and add the events to the model's context. +{{< clients-example set="agent_memory_sdk" step="add_session_event" footer="hide" description="Add a session event, then retrieve the session" difficulty="beginner" >}}{{< /clients-example >}} + +Run the example. The session response contains the stored message, its role, actor, and timestamps. An application can retrieve this session before the next agent turn and add the events to the model's context. > [!NOTE] > **What to expect:** The `events` array contains the travel message. Redis Agent Memory adds an `eventId` and `systemTimestamp`, showing that the application can recover the complete event later using only the session ID. -After the event is stored, comment out the call to `add_session_event` before subsequent runs to avoid adding the same message again. +Run this example only once. Running it again adds the same message a second time. ## 2. Recall automatically extracted information Redis Agent Memory processes session events in the background and creates long term memories for information that may be useful in later conversations. You configured the extraction cadence to one minute when you created the service. You do not need to call a memory creation method. -Wait at least one minute, then add this search after the session retrieval: - -```python - results = agent_memory.search_long_term_memory( - request={ - "text": "What dietary requirements and food preferences does the user have?", - "filter_": { - "owner_id": { - "eq": USER_ID, - } - }, - "limit": 5, - }, - ) - show("Automatically extracted memories", results) -``` +Wait at least one minute after completing the previous step, then run this search: -Run the file. The `items` array should contain memories derived from the conversation, such as the vegetarian requirement or preference for spicy food. Extraction is asynchronous, so run the search again if the array is empty. +{{< clients-example set="agent_memory_sdk" step="search_long_term_memory_basic" footer="hide" description="Search long-term memory for automatically extracted information" difficulty="beginner" >}}{{< /clients-example >}} + +The `items` array should contain memories derived from the conversation, such as the vegetarian requirement or preference for spicy food. Extraction is asynchronous, so run the search again if the array is empty. > [!NOTE] > **What to expect:** Results similar to `User is a vegetarian` and `User prefers spicy food`. Your application did not create these memories directly. Redis Agent Memory derived them from the session event. The exact text and memory types can vary. The extracted memory remains searchable after the session expires, subject to the long term memory TTL. You can change the extraction cadence and both TTLs in the [Redis Agent Memory service configuration](/content/operate/iris/agent-memory/create-service.md#memory-configuration). -The Python SDK uses snake case for method arguments and request fields. Serialized API requests and responses use camel case. +The Python SDK uses snake case for method arguments and request fields. The TypeScript SDK uses camel case. Serialized API requests and responses always use camel case. ## 3. Keep long conversations concise with automatic summarization @@ -157,40 +120,17 @@ You enabled automatic summarization when you created the service. When the sessi ### Add conversation turns -Add this code after the first session event to continue the conversation past the configured threshold: - -```python - turns = [ - (models.MessageRole.ASSISTANT, "What dates are you traveling?"), - (models.MessageRole.USER, "I arrive on October 10 and leave on October 18."), - (models.MessageRole.ASSISTANT, "Would you like formal or casual restaurants?"), - (models.MessageRole.USER, "Mostly casual places near public transit."), - (models.MessageRole.ASSISTANT, "Do you have a preferred budget?"), - (models.MessageRole.USER, "About 40 euros per person."), - ] - - for role, text in turns: - agent_memory.add_session_event( - session_id=SESSION_ID, - actor_id=USER_ID if role == models.MessageRole.USER else "travel-agent", - role=role, - content=[models.Text(text=text)], - created_at=datetime.now(timezone.utc), - ) -``` +Continue the conversation past the configured threshold: + +{{< clients-example set="agent_memory_sdk" step="add_conversation_turns" footer="hide" description="Add several more session events to trigger summarization" difficulty="beginner" >}}{{< /clients-example >}} -Run the code once, then comment out the loop to avoid adding the same turns again. Summarization runs in the background. +Run this example only once. Summarization runs in the background. ### Retrieve the summarized session After a short wait, retrieve the session again: -```python - compacted_session = agent_memory.get_session_memory( - session_id=SESSION_ID, - ) - show("Compacted session memory", compacted_session) -``` +{{< clients-example set="agent_memory_sdk" step="retrieve_session_summary" footer="hide" description="Retrieve the session after it has been summarized" difficulty="beginner" >}}{{< /clients-example >}} Run the retrieval again after a short wait if `summary` is not present. Compare `summary.text` with the recent events. The summary should preserve earlier trip decisions while recent turns remain available in full. @@ -205,19 +145,7 @@ Built in memories preserve generally useful information. Custom memory types let Search for the structured memory: -```python - custom_results = agent_memory.search_long_term_memory( - request={ - "text": "What are the requirements for the user's trip?", - "filter_": { - "owner_id": {"eq": USER_ID}, - "memory_type": {"eq": "trip_preference"}, - }, - "limit": 5, - }, - ) - show("Trip preference memories", custom_results) -``` +{{< clients-example set="agent_memory_sdk" step="search_long_term_memory_custom_type" footer="hide" description="Search long-term memory filtered to a custom memory type" difficulty="intermediate" >}}{{< /clients-example >}} The result uses `trip_preference` as its `memoryType` and contains travel information extracted from the conversation. The exact text and returned fields depend on the conversation, extraction model, and client. @@ -230,42 +158,19 @@ See [custom memory types](/content/operate/iris/agent-memory/create-service.md#c The semantic exclusion prompt tells Redis Agent Memory which information should not be kept in long-term memory. Add an event containing a fictional booking code and information that is safe to retain: -```python - sensitive_event = agent_memory.add_session_event( - session_id=SESSION_ID, - actor_id=USER_ID, - role=models.MessageRole.USER, - content=[models.Text( - text=( - "I booked Hotel Sakura in Tokyo. For this example, " - "the fictional booking confirmation code is DEMO-7QX9." - ), - )], - created_at=datetime.now(timezone.utc), - ) - show("Event with excluded information", sensitive_event) -``` +{{< clients-example set="agent_memory_sdk" step="add_session_event_sensitive" footer="hide" description="Add a session event containing a fictional sensitive booking code" difficulty="intermediate" >}}{{< /clients-example >}} -Run the code once, then comment out the call to `add_session_event`. Wait at least one minute and search for the safe hotel information: - -```python - exclusion_results = agent_memory.search_long_term_memory( - request={ - "text": "Where is the user staying in Tokyo?", - "filter_": { - "owner_id": {"eq": USER_ID}, - }, - "limit": 5, - }, - ) - show("Memories after semantic exclusion", exclusion_results) -``` +Run this example only once. Wait at least one minute, then search for the safe hotel information: + +{{< clients-example set="agent_memory_sdk" step="search_long_term_memory_exclusion" footer="hide" description="Search long-term memory to confirm the sensitive value was excluded" difficulty="intermediate" >}}{{< /clients-example >}} Inspect the returned memories. They can retain the hotel name, but should not contain `DEMO-7QX9` because the exclusion prompt covers booking confirmation codes. > [!NOTE] > **What to expect:** A memory similar to `User booked Hotel Sakura in Tokyo` without the fictional confirmation code. If the code appears, refine the exclusion prompt and test again. Exclusions remain advisory. +  + > [!WARNING] > Semantic exclusions are advisory and do not guarantee that sensitive information is excluded. Session content still reaches the extraction model provider. Do not use real sensitive data in this exercise. Exclusions do not apply to directly created long-term memories. @@ -273,6 +178,6 @@ See [sensitive-data exclusions](/content/operate/iris/agent-memory/create-servic ## Next steps -* Review the [Python SDK package and reference](https://pypi.org/project/redis-agent-memory/). -* Try the [TypeScript SDK quickstart](/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md) or [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md). +* Review the [Python SDK package and reference](https://pypi.org/project/redis-agent-memory/) or the [TypeScript SDK package and reference](https://www.npmjs.com/package/@redis-iris/agent-memory). +* Try the [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md). * Learn when to [create long term memories directly](/content/develop/ai/context-engine/agent-memory/developer-guide.md#create-long-term-memories). diff --git a/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md b/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md deleted file mode 100644 index e4e513a8d1..0000000000 --- a/content/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart.md +++ /dev/null @@ -1,320 +0,0 @@ ---- -alwaysopen: false -categories: -- docs -- develop -- ai -description: Explore session memory, automatic extraction, summarization, custom memory types, and sensitive-data exclusions with the Redis Agent Memory TypeScript SDK. -hideListLinks: true -linktitle: TypeScript SDK quickstart -title: Redis Agent Memory TypeScript SDK quickstart -weight: 7 ---- - -Use this quickstart to follow a travel planning conversation through Redis Agent Memory. You will retrieve the conversation from session memory, recall information extracted in the background, inspect an automatically generated session summary, extract structured travel information, and guide extraction away from sensitive data. - -## Before you begin - -To complete this quickstart, you need: - -{{< embed-md "rc-agent-memory-quickstart-prerequisites.md" >}} - -You also need Node.js and npm. - -## Create a Redis Agent Memory service - -1. Sign in to the [Redis Cloud console](https://cloud.redis.io/). -1. Select **Agent Memory** from the navigation menu. -1. If Redis Cloud displays the public preview terms, review and accept them. -1. Select **Create custom service**. -1. Enter a service name, select an eligible database, and select its `default` user. -1. Under **Memory configuration**, enter these values: - - | Setting | Value | What it controls | - |:--------|:------|:-----------------| - | **Short-term TTL** | `1` day | How long session memory is retained. | - | **Long-term TTL** | `365` days | How long long-term memories are retained. | - | **Extraction cadence** | `1` minute | How often session events are processed for extraction. One minute is for this quickstart; use a longer production interval unless you need rapid extraction. | - | **Automatic summarization** | Enabled | Whether older session events are condensed into a summary. | - | **Summarize after (messages)** | `6` | The event count that triggers summarization. Six is for this quickstart; use a higher production threshold. | - | **Keep most recent (messages)** | `2` | How many recent events remain in full. Two is for this quickstart; retain more in production when recent turns are needed. | - -1. Under **Memory types & extraction**, select **Add type** and configure this custom memory type: - - | Setting | Value | What it controls | - |:--------|:------|:-----------------| - | **Name** | `trip_preference` | The identifier stored in `memoryType` and used in search filters. | - | **Description** | `Structured requirements for a planned trip` | The purpose of the custom memory type. | - | **Extraction prompt** | `Extract trip requirements only when the user states a destination or travel plan. Preserve explicit dietary requirements and food preferences.` | When to create the memory and which information to capture. | - | **Enabled** | Enabled | Whether new memories of this type are extracted. | - -1. Add these custom fields: - - | Field | Type | Description | - |:------|:-----|:------------| - | `destinations` | `list[str]` | Cities or countries the user plans to visit. | - | `travel_period` | `str` | When the user plans to travel. | - | `dietary_requirements` | `list[str]` | Dietary requirements that affect recommendations. | - | `food_preferences` | `list[str]` | Cuisines, flavors, or dining preferences stated by the user. | - -1. Under **Sensitive-data exclusions**, enable **Semantic exclusions** and enter this exclusion prompt: - - ```text - Do not keep passwords, access tokens, recovery codes, payment card information, or booking confirmation codes in long-term memory. - ``` - -1. Select **Create**. -1. Copy the Redis Agent Memory API key and store it securely. - -> [!WARNING] -> Redis Cloud displays the Redis Agent Memory API key only once. If you lose it, [generate a new API key](/content/operate/iris/agent-memory/view-service.md#replace-service-api-key). - -These settings keep the background stages short enough to observe during the quickstart. For screenshots and configuration details, see [create a Redis Agent Memory service](/content/operate/iris/agent-memory/create-service.md). - -> [!WARNING] -> Sensitive-data exclusions guide the extraction model but do not guarantee exclusion. Sensitive session content still reaches the model provider. Exclusions do not apply when an application creates long-term memories directly. - -## Save the connection values - -1. Open the Redis Agent Memory service in the Redis Cloud console. -1. On the **Configuration** tab, copy the **Endpoint** and **Store ID**. -1. Export the API key in your shell: - - ```sh - export API_KEY='' - ``` - -Keep the API key out of source control, application logs, and other unsecured locations. - -## Install the SDK - -```sh -mkdir agent-memory-quickstart -cd agent-memory-quickstart -npm init -y -npm install @redis-iris/agent-memory -npm install --save-dev tsx -``` - -## Create the client and check the service health - -Create `quickstart.ts` with the following code. Replace `` and `` with the values from Redis Cloud. The endpoint must include `https://`. - -```typescript -import { AgentMemory } from "@redis-iris/agent-memory"; - -const apiKey = process.env.API_KEY; -if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); -} - -const serverURL = ""; -const storeId = ""; -const sessionId = "travel-planning-session"; -const userId = "quickstart-user"; - -const agentMemory = new AgentMemory({ - serverURL, - storeId, - apiKey, -}); - -async function run() { - const health = await agentMemory.health(); - console.log("Service health:"); - console.dir(health, { depth: null }); -} - -run().catch((error) => { - console.error(error); - process.exitCode = 1; -}); -``` - -Run the file: - -```sh -npx tsx quickstart.ts -``` - -A healthy response confirms that the client can reach Redis Agent Memory and authenticate with the API key. The first store request validates the Store ID. - -## 1. Build conversation context with session memory - -Session memory stores a conversation as an ordered sequence of events. Add the following code after the health check, inside `run`: - -```typescript - const event = await agentMemory.addSessionEvent({ - sessionId, - actorId: userId, - role: "USER", - content: [{ - text: "I am visiting Tokyo and Kyoto next month. I am vegetarian and prefer spicy food.", - }], - createdAt: new Date(), - }); - console.log("Created event:"); - console.dir(event, { depth: null }); - - const session = await agentMemory.getSessionMemory(sessionId); - console.log("Session memory:"); - console.dir(session, { depth: null }); -``` - -Run the file again. The session response contains the stored message, its role, actor, and timestamps. An application can retrieve this session before the next agent turn and add the events to the model's context. - -> [!NOTE] -> **What to expect:** The `events` array contains the travel message. Redis Agent Memory adds an `eventId` and `systemTimestamp`, showing that the application can recover the complete event later using only the session ID. - -After the event is stored, comment out the call to `addSessionEvent` before subsequent runs to avoid adding the same message again. - -## 2. Recall automatically extracted information - -Redis Agent Memory processes session events in the background and creates long term memories for information that may be useful in later conversations. You configured the extraction cadence to one minute when you created the service. You do not need to call a memory creation method. - -Wait at least one minute, then add this search after the session retrieval: - -```typescript - const results = await agentMemory.searchLongTermMemory({ - text: "What dietary requirements and food preferences does the user have?", - filter: { - ownerId: { - eq: userId, - }, - }, - limit: 5, - }); - console.log("Automatically extracted memories:"); - console.dir(results, { depth: null }); -``` - -Run the file. The `items` array should contain memories derived from the conversation, such as the vegetarian requirement or preference for spicy food. Extraction is asynchronous, so run the search again if the array is empty. - -> [!NOTE] -> **What to expect:** Results similar to `User is a vegetarian` and `User prefers spicy food`. Your application did not create these memories directly. Redis Agent Memory derived them from the session event. The exact text and memory types can vary. - -The extracted memory remains searchable after the session expires, subject to the long term memory TTL. You can change the extraction cadence and both TTLs in the [Redis Agent Memory service configuration](/content/operate/iris/agent-memory/create-service.md#memory-configuration). - -## 3. Keep long conversations concise with automatic summarization - -Automatic summarization condenses older events and retains the most recent events in full. The retrieved session then contains a `summary` object and the recent `events` array, so the application can provide useful history without filling the model's context window with every original message. - -You enabled automatic summarization when you created the service. When the session reaches six events, Redis Agent Memory summarizes the older events and retains the two most recent events in full. - -### Add conversation turns - -Add this code after the first session event to continue the conversation past the configured threshold: - -```typescript - const turns = [ - { role: "ASSISTANT", actorId: "travel-agent", text: "What dates are you traveling?" }, - { role: "USER", actorId: userId, text: "I arrive on October 10 and leave on October 18." }, - { role: "ASSISTANT", actorId: "travel-agent", text: "Would you like formal or casual restaurants?" }, - { role: "USER", actorId: userId, text: "Mostly casual places near public transit." }, - { role: "ASSISTANT", actorId: "travel-agent", text: "Do you have a preferred budget?" }, - { role: "USER", actorId: userId, text: "About 40 euros per person." }, - ] as const; - - for (const turn of turns) { - await agentMemory.addSessionEvent({ - sessionId, - actorId: turn.actorId, - role: turn.role, - content: [{ text: turn.text }], - createdAt: new Date(), - }); - } -``` - -Run the code once, then comment out the loop to avoid adding the same turns again. Summarization runs in the background. - -### Retrieve the summarized session - -After a short wait, retrieve the session again: - -```typescript - const compactedSession = await agentMemory.getSessionMemory(sessionId); - console.log("Compacted session memory:"); - console.dir(compactedSession, { depth: null }); -``` - -Run the retrieval again after a short wait if `summary` is not present. Compare `summary.text` with the recent events. The summary should preserve earlier trip decisions while recent turns remain available in full. - -> [!NOTE] -> **What to expect:** A `summary` object that preserves details such as Tokyo, Kyoto, the travel dates, and food preferences. `summarizedUpToEventId` identifies the last event covered by the summary, while `events` contains the newer turns that remain in full. The exact summary text can vary. - -See [automatic summarization configuration](/content/operate/iris/agent-memory/create-service.md#automatic-summarization) for details. - -## 4. Extract business specific data with a custom memory type - -Built in memories preserve generally useful information. Custom memory types let an application extract structured information for its business domain. You configured `trip_preference` when you created the service, so it processed the same travel planning event independently. - -Search for the structured memory: - -```typescript - const customResults = await agentMemory.searchLongTermMemory({ - text: "What are the requirements for the user's trip?", - filter: { - ownerId: { eq: userId }, - memoryType: { eq: "trip_preference" }, - }, - limit: 5, - }); - console.log("Trip preference memories:"); - console.dir(customResults, { depth: null }); -``` - -The result uses `trip_preference` as its `memoryType` and contains travel information extracted from the conversation. The exact text and returned fields depend on the conversation, extraction model, and client. - -> [!NOTE] -> **What to expect:** A result with `memoryType` set to `trip_preference` that combines the destinations, travel period, and dietary preferences. This shows that the custom type processed the same conversation independently from the built-in memory types. - -See [custom memory types](/content/operate/iris/agent-memory/create-service.md#custom-memory-types) for configuration requirements and limits. - -## 5. Guide extraction away from sensitive data - -The semantic exclusion prompt tells Redis Agent Memory which information should not be kept in long-term memory. Add an event containing a fictional booking code and information that is safe to retain: - -```typescript - const sensitiveEvent = await agentMemory.addSessionEvent({ - sessionId, - actorId: userId, - role: "USER", - content: [{ - text: "I booked Hotel Sakura in Tokyo. For this example, the fictional booking confirmation code is DEMO-7QX9.", - }], - createdAt: new Date(), - }); - console.log("Event with excluded information:"); - console.dir(sensitiveEvent, { depth: null }); -``` - -Run the code once, then comment out the call to `addSessionEvent`. Wait at least one minute and search for the safe hotel information: - -```typescript - const exclusionResults = await agentMemory.searchLongTermMemory({ - text: "Where is the user staying in Tokyo?", - filter: { - ownerId: { eq: userId }, - }, - limit: 5, - }); - console.log("Memories after semantic exclusion:"); - console.dir(exclusionResults, { depth: null }); -``` - -Inspect the returned memories. They can retain the hotel name, but should not contain `DEMO-7QX9` because the exclusion prompt covers booking confirmation codes. - -> [!NOTE] -> **What to expect:** A memory similar to `User booked Hotel Sakura in Tokyo` without the fictional confirmation code. If the code appears, refine the exclusion prompt and test again. Exclusions remain advisory. - -> [!WARNING] -> Semantic exclusions are advisory and do not guarantee that sensitive information is excluded. Session content still reaches the extraction model provider. Do not use real sensitive data in this exercise. Exclusions do not apply to directly created long-term memories. - -See [sensitive-data exclusions](/content/operate/iris/agent-memory/create-service.md#sensitive-data-exclusions) for configuration details. - -## Next steps - -* Review the [TypeScript SDK package and reference](https://www.npmjs.com/package/@redis-iris/agent-memory). -* Try the [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md) or [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md). -* Learn when to [create long term memories directly](/content/develop/ai/context-engine/agent-memory/developer-guide.md#create-long-term-memories). diff --git a/local_examples/agent-memory/agent_memory_sdk.py b/local_examples/agent-memory/agent_memory_sdk.py new file mode 100644 index 0000000000..4f635d1a6f --- /dev/null +++ b/local_examples/agent-memory/agent_memory_sdk.py @@ -0,0 +1,277 @@ +# EXAMPLE: agent_memory_sdk + +# STEP_START connect_health_check +import os + +from redis_agent_memory import AgentMemory + +ENDPOINT = "" +STORE_ID = "" + + +def check_health(): + with AgentMemory( + ENDPOINT, + store_id=STORE_ID, + api_key=os.environ["API_KEY"], + ) as agent_memory: + health = agent_memory.health() + print("Service health:") + print(health.model_dump_json(by_alias=True, indent=2)) + + +if __name__ == "__main__": + check_health() +# STEP_END + +# STEP_START add_session_event +import os +from datetime import datetime, timezone + +from redis_agent_memory import AgentMemory, models + +ENDPOINT = "" +STORE_ID = "" +SESSION_ID = "travel-planning-session" +USER_ID = "quickstart-user" + + +def build_conversation_context(): + with AgentMemory( + ENDPOINT, + store_id=STORE_ID, + api_key=os.environ["API_KEY"], + ) as agent_memory: + event = agent_memory.add_session_event( + session_id=SESSION_ID, + actor_id=USER_ID, + role=models.MessageRole.USER, + content=[models.Text( + text=( + "I am visiting Tokyo and Kyoto next month. " + "I am vegetarian and prefer spicy food." + ), + )], + created_at=datetime.now(timezone.utc), + ) + print("Created event:") + print(event.model_dump_json(by_alias=True, indent=2)) + + session = agent_memory.get_session_memory( + session_id=SESSION_ID, + ) + print("Session memory:") + print(session.model_dump_json(by_alias=True, indent=2)) + + +if __name__ == "__main__": + build_conversation_context() +# STEP_END + +# STEP_START search_long_term_memory_basic +import os + +from redis_agent_memory import AgentMemory + +ENDPOINT = "" +STORE_ID = "" +USER_ID = "quickstart-user" + + +def recall_extracted_memory(): + with AgentMemory( + ENDPOINT, + store_id=STORE_ID, + api_key=os.environ["API_KEY"], + ) as agent_memory: + results = agent_memory.search_long_term_memory( + request={ + "text": "What dietary requirements and food preferences does the user have?", + "filter_": { + "owner_id": { + "eq": USER_ID, + } + }, + "limit": 5, + }, + ) + print("Automatically extracted memories:") + print(results.model_dump_json(by_alias=True, indent=2)) + + +if __name__ == "__main__": + recall_extracted_memory() +# STEP_END + +# STEP_START add_conversation_turns +import os +from datetime import datetime, timezone + +from redis_agent_memory import AgentMemory, models + +ENDPOINT = "" +STORE_ID = "" +SESSION_ID = "travel-planning-session" +USER_ID = "quickstart-user" + + +def add_conversation_turns(): + turns = [ + (models.MessageRole.ASSISTANT, "What dates are you traveling?"), + (models.MessageRole.USER, "I arrive on October 10 and leave on October 18."), + (models.MessageRole.ASSISTANT, "Would you like formal or casual restaurants?"), + (models.MessageRole.USER, "Mostly casual places near public transit."), + (models.MessageRole.ASSISTANT, "Do you have a preferred budget?"), + (models.MessageRole.USER, "About 40 euros per person."), + ] + + with AgentMemory( + ENDPOINT, + store_id=STORE_ID, + api_key=os.environ["API_KEY"], + ) as agent_memory: + for role, text in turns: + agent_memory.add_session_event( + session_id=SESSION_ID, + actor_id=USER_ID if role == models.MessageRole.USER else "travel-agent", + role=role, + content=[models.Text(text=text)], + created_at=datetime.now(timezone.utc), + ) + + +if __name__ == "__main__": + add_conversation_turns() +# STEP_END + +# STEP_START retrieve_session_summary +import os + +from redis_agent_memory import AgentMemory + +ENDPOINT = "" +STORE_ID = "" +SESSION_ID = "travel-planning-session" + + +def retrieve_session_summary(): + with AgentMemory( + ENDPOINT, + store_id=STORE_ID, + api_key=os.environ["API_KEY"], + ) as agent_memory: + compacted_session = agent_memory.get_session_memory( + session_id=SESSION_ID, + ) + print("Compacted session memory:") + print(compacted_session.model_dump_json(by_alias=True, indent=2)) + + +if __name__ == "__main__": + retrieve_session_summary() +# STEP_END + +# STEP_START search_long_term_memory_custom_type +import os + +from redis_agent_memory import AgentMemory + +ENDPOINT = "" +STORE_ID = "" +USER_ID = "quickstart-user" + + +def search_custom_memory_type(): + with AgentMemory( + ENDPOINT, + store_id=STORE_ID, + api_key=os.environ["API_KEY"], + ) as agent_memory: + custom_results = agent_memory.search_long_term_memory( + request={ + "text": "What are the requirements for the user's trip?", + "filter_": { + "owner_id": {"eq": USER_ID}, + "memory_type": {"eq": "trip_preference"}, + }, + "limit": 5, + }, + ) + print("Trip preference memories:") + print(custom_results.model_dump_json(by_alias=True, indent=2)) + + +if __name__ == "__main__": + search_custom_memory_type() +# STEP_END + +# STEP_START add_session_event_sensitive +import os +from datetime import datetime, timezone + +from redis_agent_memory import AgentMemory, models + +ENDPOINT = "" +STORE_ID = "" +SESSION_ID = "travel-planning-session" +USER_ID = "quickstart-user" + + +def add_sensitive_event(): + with AgentMemory( + ENDPOINT, + store_id=STORE_ID, + api_key=os.environ["API_KEY"], + ) as agent_memory: + sensitive_event = agent_memory.add_session_event( + session_id=SESSION_ID, + actor_id=USER_ID, + role=models.MessageRole.USER, + content=[models.Text( + text=( + "I booked Hotel Sakura in Tokyo. For this example, " + "the fictional booking confirmation code is DEMO-7QX9." + ), + )], + created_at=datetime.now(timezone.utc), + ) + print("Event with excluded information:") + print(sensitive_event.model_dump_json(by_alias=True, indent=2)) + + +if __name__ == "__main__": + add_sensitive_event() +# STEP_END + +# STEP_START search_long_term_memory_exclusion +import os + +from redis_agent_memory import AgentMemory + +ENDPOINT = "" +STORE_ID = "" +USER_ID = "quickstart-user" + + +def search_with_exclusion(): + with AgentMemory( + ENDPOINT, + store_id=STORE_ID, + api_key=os.environ["API_KEY"], + ) as agent_memory: + exclusion_results = agent_memory.search_long_term_memory( + request={ + "text": "Where is the user staying in Tokyo?", + "filter_": { + "owner_id": {"eq": USER_ID}, + }, + "limit": 5, + }, + ) + print("Memories after semantic exclusion:") + print(exclusion_results.model_dump_json(by_alias=True, indent=2)) + + +if __name__ == "__main__": + search_with_exclusion() +# STEP_END diff --git a/local_examples/agent-memory/agent_memory_sdk.ts b/local_examples/agent-memory/agent_memory_sdk.ts new file mode 100644 index 0000000000..e285f8aa0c --- /dev/null +++ b/local_examples/agent-memory/agent_memory_sdk.ts @@ -0,0 +1,282 @@ +// EXAMPLE: agent_memory_sdk + +// STEP_START connect_health_check +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function checkHealth() { + const apiKey = process.env.API_KEY; + if (!apiKey) { + throw new Error("Set the API_KEY environment variable."); + } + + const agentMemory = new AgentMemory({ + serverURL: "", + storeId: "", + apiKey, + }); + + const health = await agentMemory.health(); + console.log("Service health:"); + console.dir(health, { depth: null }); +} + +checkHealth().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +// STEP_END + +// STEP_START add_session_event +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function buildConversationContext() { + const apiKey = process.env.API_KEY; + if (!apiKey) { + throw new Error("Set the API_KEY environment variable."); + } + + const sessionId = "travel-planning-session"; + const userId = "quickstart-user"; + + const agentMemory = new AgentMemory({ + serverURL: "", + storeId: "", + apiKey, + }); + + const event = await agentMemory.addSessionEvent({ + sessionId, + actorId: userId, + role: "USER", + content: [{ + text: "I am visiting Tokyo and Kyoto next month. I am vegetarian and prefer spicy food.", + }], + createdAt: new Date(), + }); + console.log("Created event:"); + console.dir(event, { depth: null }); + + const session = await agentMemory.getSessionMemory(sessionId); + console.log("Session memory:"); + console.dir(session, { depth: null }); +} + +buildConversationContext().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +// STEP_END + +// STEP_START search_long_term_memory_basic +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function recallExtractedMemory() { + const apiKey = process.env.API_KEY; + if (!apiKey) { + throw new Error("Set the API_KEY environment variable."); + } + + const userId = "quickstart-user"; + + const agentMemory = new AgentMemory({ + serverURL: "", + storeId: "", + apiKey, + }); + + const results = await agentMemory.searchLongTermMemory({ + text: "What dietary requirements and food preferences does the user have?", + filter: { + ownerId: { + eq: userId, + }, + }, + limit: 5, + }); + console.log("Automatically extracted memories:"); + console.dir(results, { depth: null }); +} + +recallExtractedMemory().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +// STEP_END + +// STEP_START add_conversation_turns +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function addConversationTurns() { + const apiKey = process.env.API_KEY; + if (!apiKey) { + throw new Error("Set the API_KEY environment variable."); + } + + const sessionId = "travel-planning-session"; + const userId = "quickstart-user"; + + const agentMemory = new AgentMemory({ + serverURL: "", + storeId: "", + apiKey, + }); + + const turns = [ + { role: "ASSISTANT", actorId: "travel-agent", text: "What dates are you traveling?" }, + { role: "USER", actorId: userId, text: "I arrive on October 10 and leave on October 18." }, + { role: "ASSISTANT", actorId: "travel-agent", text: "Would you like formal or casual restaurants?" }, + { role: "USER", actorId: userId, text: "Mostly casual places near public transit." }, + { role: "ASSISTANT", actorId: "travel-agent", text: "Do you have a preferred budget?" }, + { role: "USER", actorId: userId, text: "About 40 euros per person." }, + ] as const; + + for (const turn of turns) { + await agentMemory.addSessionEvent({ + sessionId, + actorId: turn.actorId, + role: turn.role, + content: [{ text: turn.text }], + createdAt: new Date(), + }); + } +} + +addConversationTurns().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +// STEP_END + +// STEP_START retrieve_session_summary +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function retrieveSessionSummary() { + const apiKey = process.env.API_KEY; + if (!apiKey) { + throw new Error("Set the API_KEY environment variable."); + } + + const sessionId = "travel-planning-session"; + + const agentMemory = new AgentMemory({ + serverURL: "", + storeId: "", + apiKey, + }); + + const compactedSession = await agentMemory.getSessionMemory(sessionId); + console.log("Compacted session memory:"); + console.dir(compactedSession, { depth: null }); +} + +retrieveSessionSummary().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +// STEP_END + +// STEP_START search_long_term_memory_custom_type +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function searchCustomMemoryType() { + const apiKey = process.env.API_KEY; + if (!apiKey) { + throw new Error("Set the API_KEY environment variable."); + } + + const userId = "quickstart-user"; + + const agentMemory = new AgentMemory({ + serverURL: "", + storeId: "", + apiKey, + }); + + const customResults = await agentMemory.searchLongTermMemory({ + text: "What are the requirements for the user's trip?", + filter: { + ownerId: { eq: userId }, + memoryType: { eq: "trip_preference" }, + }, + limit: 5, + }); + console.log("Trip preference memories:"); + console.dir(customResults, { depth: null }); +} + +searchCustomMemoryType().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +// STEP_END + +// STEP_START add_session_event_sensitive +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function addSensitiveEvent() { + const apiKey = process.env.API_KEY; + if (!apiKey) { + throw new Error("Set the API_KEY environment variable."); + } + + const sessionId = "travel-planning-session"; + const userId = "quickstart-user"; + + const agentMemory = new AgentMemory({ + serverURL: "", + storeId: "", + apiKey, + }); + + const sensitiveEvent = await agentMemory.addSessionEvent({ + sessionId, + actorId: userId, + role: "USER", + content: [{ + text: "I booked Hotel Sakura in Tokyo. For this example, the fictional booking confirmation code is DEMO-7QX9.", + }], + createdAt: new Date(), + }); + console.log("Event with excluded information:"); + console.dir(sensitiveEvent, { depth: null }); +} + +addSensitiveEvent().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +// STEP_END + +// STEP_START search_long_term_memory_exclusion +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function searchWithExclusion() { + const apiKey = process.env.API_KEY; + if (!apiKey) { + throw new Error("Set the API_KEY environment variable."); + } + + const userId = "quickstart-user"; + + const agentMemory = new AgentMemory({ + serverURL: "", + storeId: "", + apiKey, + }); + + const exclusionResults = await agentMemory.searchLongTermMemory({ + text: "Where is the user staying in Tokyo?", + filter: { + ownerId: { eq: userId }, + }, + limit: 5, + }); + console.log("Memories after semantic exclusion:"); + console.dir(exclusionResults, { depth: null }); +} + +searchWithExclusion().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +// STEP_END From 6d65f5f7c34e092227e3aefcb4e84903b7269c1c Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Tue, 15 Sep 2026 16:04:31 -0700 Subject: [PATCH 07/26] docs: make namespace session discovery visible --- .../api-reference/openapi-agent-memory.json | 12 +++++++ .../context-engine/agent-memory/sessions.md | 34 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json index f14395130b..514f56fa26 100644 --- a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json +++ b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json @@ -2051,6 +2051,18 @@ "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll." } }, + { + "name": "namespaceRef", + "in": "query", + "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll." + } + }, { "name": "includeAll", "in": "query", diff --git a/content/develop/ai/context-engine/agent-memory/sessions.md b/content/develop/ai/context-engine/agent-memory/sessions.md index ccb2221038..4f1d5fd86a 100644 --- a/content/develop/ai/context-engine/agent-memory/sessions.md +++ b/content/develop/ai/context-engine/agent-memory/sessions.md @@ -32,6 +32,40 @@ Each stored event can include: Before an agent turn, retrieve the session by `sessionId` and provide the relevant events to the agent. Use these events as conversation context for the model. +## List sessions + +Use `GET /v1/stores/{storeId}/session-memory` to find session IDs by owner or namespace. Reuse the curl connection values from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#save-the-connection-values" >}}). + +To list sessions in one namespace, set `NAMESPACE_ID` to the stable ID returned when you [create a namespace]({{< relref "/develop/ai/context-engine/agent-memory/namespaces#create-a-personal-namespace" >}}): + +```sh +curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $API_KEY" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory?namespaceRef=$NAMESPACE_ID" | jq +``` + +`namespaceRef` matches the namespace ID exactly, including case. It excludes descendant namespaces, sessions with only a deprecated `namespace` label, and sessions without a namespace resource. + +Add `filterOwnerId` to return sessions that match both the namespace and the owner: + +```sh +curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $API_KEY" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory?namespaceRef=$NAMESPACE_ID&filterOwnerId=$OWNER_ID" | jq +``` + +You can also use `filterOwnerId` alone. To list all sessions, omit both filters and set `includeAll=true`: + +```sh +curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $API_KEY" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory?includeAll=true" | jq +``` + +Do not combine `includeAll=true` with either filter. A request without a filter must set `includeAll=true`. + +The response contains session IDs in `items` and the number of matching sessions in `total`. If the response includes `nextPageToken`, pass it as `pageToken` with the same filters to fetch the next page. See the [List Sessions API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}#operation/ListSessions) for pagination limits. + ## Configure session retention The session-memory time to live (TTL) controls how long sessions remain available. Configure it according to the retention requirements of your application. When a session expires, its events are no longer available through session-memory retrieval. From 8f1a9dffaa6a2f4129917822900d52a230b07849 Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Fri, 9 Oct 2026 16:46:41 -0700 Subject: [PATCH 08/26] Explain how to use memory before asking readers to act --- .../agent-memory/developer-guide.md | 85 +++++++++++++++---- .../agent-memory/interactive-demo.md | 2 +- .../context-engine/agent-memory/overview.md | 4 +- .../context-engine/agent-memory/quickstart.md | 21 ++++- .../context-engine/agent-memory/sessions.md | 34 ++++++-- 5 files changed, 118 insertions(+), 28 deletions(-) diff --git a/content/develop/ai/context-engine/agent-memory/developer-guide.md b/content/develop/ai/context-engine/agent-memory/developer-guide.md index a3d42e4b61..eb5aaf5bb3 100644 --- a/content/develop/ai/context-engine/agent-memory/developer-guide.md +++ b/content/develop/ai/context-engine/agent-memory/developer-guide.md @@ -11,25 +11,36 @@ title: Redis Agent Memory developer guide weight: 6 --- -Use session memory and long-term memory in your agent's request cycle. Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first to verify the connection and make your first requests. For the memory model, see [Concepts]({{< relref "/develop/ai/context-engine/agent-memory/concepts" >}}). +Your application brings two kinds of memory to a conversation: the messages exchanged so far and information worth remembering from earlier conversations. Redis Agent Memory stores both. Your application retrieves them, passes them to the model, and saves each new turn. + +This guide uses a travel assistant to show where those requests belong in your application. Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first to create a service and send your first requests. For how the two kinds of memory work, see the [overview]({{< relref "/develop/ai/context-engine/agent-memory/overview" >}}). ## Configure the application -Keep the service endpoint, Store ID, and application programming interface (API) key in application configuration. Load the key from a secret store or environment variable. The quickstart shows client initialization for Python, TypeScript, and curl. +Every client needs the service endpoint, Store ID, and application programming interface (API) key. The endpoint tells the client where to connect. The Store ID selects the memory store, and the key authenticates the request. + +1. In the Redis Cloud console, open your Agent Memory service and select **Configuration**. +1. Copy the **Endpoint** and **Store ID** into your application's configuration. +1. Load the API key you saved during service creation from a secret store or environment variable. Keep it out of source control and browser code. +1. Create the client as shown in [Create the client and check the service health]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#create-the-client-and-check-the-service-health" >}}). Use the Python software development kit (SDK) for Python applications, the TypeScript SDK for JavaScript or TypeScript, or the REST API for other languages. -Choose identifiers your application can reuse: +## Identify users and conversations + +Redis Agent Memory uses IDs to keep conversations and owners distinct. Choose them in your application, then reuse them in memory requests. For a travel assistant: | Identifier | Application choice | |:-----------|:-------------------| -| `sessionId` | Use one ID for a conversation. Reuse it on later turns and use a new ID for a new conversation. | -| `actorId` | Identify who produced each event, such as the user or assistant. | -| `ownerId` | Use a stable user or entity ID when creating and searching long-term memories. | +| `sessionId` | Generate a universally unique identifier (UUID) when the user starts a new chat and save it with the chat record. Reuse it for every turn in that chat. A new chat gets a new ID. | +| `actorId` | Use the signed-in user's account ID for user messages and an agent ID, such as `travel-agent`, for assistant messages. | +| `ownerId` | Use the same account ID when creating and searching that user's long-term memories. Reuse it across chats so the assistant can recall earlier preferences. | + +For example, the quickstart uses `quickstart-user` for the user's actor and owner IDs, and `travel-planning-session` for one conversation. These fixed values let you repeat the example. In an application, use IDs from your account and chat records. IDs must follow the format in the [API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}). -Resolve the user and their allowed sessions in your application before making memory requests. Build recall filters from that identity. Search filters do not replace application access checks. +Before retrieving a chat, check in your application's account data that the signed-in user can access it. Set the memory search's owner filter from that authenticated account. For example, a request from `user-42` must not be able to select `user-99` as its owner. A search filter limits results; it does not check who signed in. ## Build context for each agent turn -For example, a user returns to a travel agent and asks for restaurants in Kyoto. The application needs the current conversation and any relevant preferences from earlier conversations. +Suppose a user returns to the travel assistant and asks, "Where should I eat in Kyoto?" The current conversation may contain their travel dates. Long-term memory may contain their vegetarian diet from an earlier chat. The model needs both to give a useful answer. 1. Retrieve session memory with the conversation's `sessionId`. 1. Search long-term memory using the user's request and an `ownerId` filter. @@ -37,17 +48,61 @@ For example, a user returns to a travel agent and asks for restaurants in Kyoto. 1. Call the model to produce the assistant's response. 1. Store the new user and assistant messages as session events, with their roles and actor IDs. -Include the current user message once. If your application stores it before retrieval, avoid adding a second copy to the model context. Store the assistant message after it has been generated. +The following Python example prepares that context. It uses the client, `SESSION_ID`, and `USER_ID` from the quickstart. Add it inside the `with` block in `main`, after the health check, and run `python quickstart.py`. It reads existing memory and prints the context without adding an event. + +```python + import json + + question = "Where should I eat in Kyoto?" + session = agent_memory.get_session_memory(session_id=SESSION_ID) + memories = agent_memory.search_long_term_memory( + request={ + "text": question, + "filter_": {"owner_id": {"eq": USER_ID}}, + "limit": 5, + }, + ) + + context = { + "session_summary": session.summary.text if session.summary else None, + "recent_events": [ + event.model_dump(mode="json", by_alias=True) + for event in session.events + ], + "relevant_memories": [memory.text for memory in memories.items], + "current_message": question, + } + print(json.dumps(context, indent=2)) +``` -Select relevant memories and recent conversation history that fit your model's context window. Use the session summary when automatic summarization is enabled. See [Sessions]({{< relref "/develop/ai/context-engine/agent-memory/sessions" >}}) for event fields, retention, and summarization. +Pass this information to your model through your application's existing model call. Keep conversation roles and treat recalled memories as context, not as instructions. For a complete application example, use the [AI agent builder]({{< relref "/develop/ai/agent-builder" >}}) and select **Redis Iris Conversational Assistant**. + +After the model responds, use the [session-event request]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#1-build-conversation-context-with-session-memory" >}}) to save each new message. Save the question with role `USER` and the user's actor ID. Save the answer with role `ASSISTANT` and actor ID `travel-agent`. Both use the same session ID. + +In this sequence, the question appears once in `current_message` because it has not been saved yet. If you save it before retrieving the session, it will already be in `recent_events`; omit `current_message` in that case. + +The context must fit the model's input limit. Count tokens with your model provider's tokenizer and leave room for the answer. The example limits recall to five memories. If the prompt is still too large, use fewer results or enable [session summarization]({{< relref "/develop/ai/context-engine/agent-memory/sessions#configure-automatic-summarization" >}}). When summarization is enabled, session retrieval returns the summary and recent events separately; the example includes both. ## Handle long-term recall -Automatic extraction runs in the background. A successful event write does not mean that a long-term memory is ready to search. Use the current message and session history to answer the current turn; allow later turns to recall extracted information. +Extraction runs in the background. Saving "I am vegetarian" as a session event makes the message available in session memory, but an extracted preference may not be searchable yet. The context example includes the session events, so the model can use that statement on the next turn without waiting for extraction. + +When a search returns no matches, `items` is an empty array. In the example, `relevant_memories` becomes `[]`; the session history and current question remain available. If you expect a memory but it does not appear: + +1. Open the service's **Configuration** tab in Redis Cloud and check **Extraction cadence** under **Memory configuration**. Wait at least that interval after adding the event, then search again. +1. If you expect a custom memory type, check that it is enabled under **Memory types & extraction**. Check that its extraction prompt covers the information in the event. +1. Compare the request's `ownerId`, `memoryType`, and namespace filters with the IDs and type used for the memory. Start with the owner filter alone to check whether an added filter excludes it. + +See [View and edit service configuration]({{< relref "/operate/iris/agent-memory/view-service#configuration-tab" >}}) for the settings. Extraction uses a model, so it may not create a memory for every statement. -An empty search result means that no memories matched the request. It can also occur while extraction is pending. Continue with the available conversation context. If expected memories remain absent, check the extraction settings and search filters. +A failed request is different from a successful search with no matches. Handle the returned error before you use the result: -Treat a failed request separately from an empty result. Decide whether your application can answer with reduced context or needs to ask the user to retry. Check the API response before retrying: correct authentication and request errors first. Before retrying a write after a timeout, check whether it succeeded to avoid duplicate events or records. +| Failure | Application response | +|:--------|:---------------------| +| Authentication error | Check the configured API key. Retrying with the same invalid key will not help. | +| Invalid request | Read the error details and correct the field or filter before retrying. | +| Search timeout | If the assistant can answer from the current conversation, continue without recalled memories. Otherwise, tell the user that memory is unavailable and ask them to retry. | +| Event-write timeout | Retrieve the session and check whether the event was stored before sending it again. Compare its actor, timestamp, and content. Repeating a write can create a duplicate. | ### Create long-term memories @@ -73,13 +128,13 @@ See [Namespaces]({{< relref "/develop/ai/context-engine/agent-memory/namespaces" ## Exclude sensitive data from automatic extraction -Sensitive-data exclusions keep specified information out of long-term memory during automatic extraction. Enable the controls your store needs: +Sensitive-data exclusions keep specified information out of long-term memory during automatic extraction. A store's exclusions policy combines three mechanisms that you enable independently: * **Built-in detectors:** Validated patterns for common identifiers, such as payment card numbers, email addresses, phone numbers, Internet Protocol (IP) addresses, and US Social Security numbers. Select the ones a store should apply. * **Custom detectors:** Regular expressions you write yourself, for identifiers specific to your domain such as tenant IDs or internal reference numbers. * **Semantic exclusions:** A plain-language exclusion prompt describing what must not be kept, such as passwords, access tokens, recovery codes, payment card information, or booking confirmation codes. Use it for concepts a pattern cannot express. -For each detector, choose an action: redact the matched text and keep the rest, or drop the memory. When a memory matches several detectors that choose different actions, the memory is dropped. +Every detector chooses what happens to a memory it matches: redact the matched text and keep the rest, or drop the memory. When a memory matches several detectors that choose different actions, the memory is dropped. Exclusions apply to automatic extraction from session events. They do not apply when an application creates long-term memories directly, and session memory itself is never altered. diff --git a/content/develop/ai/context-engine/agent-memory/interactive-demo.md b/content/develop/ai/context-engine/agent-memory/interactive-demo.md index e24a472693..8ac5be43e6 100644 --- a/content/develop/ai/context-engine/agent-memory/interactive-demo.md +++ b/content/develop/ai/context-engine/agent-memory/interactive-demo.md @@ -33,4 +33,4 @@ Try different messages to see how they change what the service remembers. Open * ## Next steps - [Create an Agent Memory service](/content/operate/iris/agent-memory/create-service.md) on Redis Cloud. -- Follow the [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md) or the [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md) to try the same flow with a real service. +- Follow the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) to try the same example with Python, TypeScript, or curl against a real service. diff --git a/content/develop/ai/context-engine/agent-memory/overview.md b/content/develop/ai/context-engine/agent-memory/overview.md index 1fd87a79e0..1885e33da2 100644 --- a/content/develop/ai/context-engine/agent-memory/overview.md +++ b/content/develop/ai/context-engine/agent-memory/overview.md @@ -76,6 +76,6 @@ Both paths are supported. Use direct writes for bulk imports or external knowled ## Next steps -- Try the [Agent Memory interactive demo](content/develop/ai/context-engine/agent-memory/interactive-demo.md) to watch session memory and long-term memory change during a conversation. +- Try the [Agent Memory interactive demo]({{< relref "/develop/ai/context-engine/agent-memory/interactive-demo" >}}) to watch session memory and long-term memory change during a conversation. - [Developer guide]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide" >}}) to connect an application and start writing session events. -- [Python SDK quickstart]({{< relref "/develop/ai/context-engine/agent-memory/python-sdk-quickstart" >}}), [TypeScript SDK quickstart]({{< relref "/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart" >}}), or [REST API quickstart]({{< relref "/develop/ai/context-engine/agent-memory/rest-api-quickstart" >}}) to see session memory, extraction, and summarization in action. +- [Quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) to store a conversation and search extracted memories with Python, TypeScript, or curl. Then [try automatic summarization]({{< relref "/develop/ai/context-engine/agent-memory/sessions#try-automatic-summarization" >}}) to see how older messages become a summary. diff --git a/content/develop/ai/context-engine/agent-memory/quickstart.md b/content/develop/ai/context-engine/agent-memory/quickstart.md index 44dada86ad..bb43b43a29 100644 --- a/content/develop/ai/context-engine/agent-memory/quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/quickstart.md @@ -206,11 +206,17 @@ curl --fail-with-body --silent --show-error \ A healthy response confirms that the client can reach Redis Agent Memory and authenticate with the API key. The first store request validates the Store ID. -For Python, add subsequent snippets inside the `with` block in `main`. For TypeScript, add them inside `run`. After each step, run `python quickstart.py` or `npx tsx quickstart.ts`. With curl, run each command in the same shell. Run writes once; comment out completed writes and their output statements before rerunning an SDK file. +The next steps extend the client you just created: + +* **Python:** Add each Python snippet inside the `with` block in `main`, after the health check. Keep the indentation shown in the snippet. +* **TypeScript:** Add each TypeScript snippet inside `run`, after the health check and before the closing `}`. +* **curl:** Run each command in the shell where you exported the connection values. ## 1. Build conversation context with session memory -Session memory stores a conversation as an ordered sequence of events. Add a user message after the health check, then retrieve the session: +Session memory is the conversation transcript. Each message is an event, stored with the conversation's session ID and the sender's actor ID. The first request saves a user's travel plans. The second retrieves the conversation so you can check that the message was stored. + +1. Copy the code for your chosen client. For Python or TypeScript, add it after the health check in your quickstart file. For curl, run the two commands in order. {{< multitabs id="session-events" tab1="Python" tab2="TypeScript" tab3="curl" >}} @@ -290,12 +296,19 @@ curl --fail-with-body --silent --show-error \ {{< /multitabs >}} -Run the example. The session response contains the stored message, its role, actor, and timestamps. An application can retrieve this session before the next agent turn and add the events to the model's context. +2. For Python, save the file and run `python quickstart.py`. For TypeScript, save the file and run `npx tsx quickstart.ts`. +3. Find the `events` array in the session response. Check that it contains the travel message, the `USER` role, and the `quickstart-user` actor ID. > [!NOTE] > **What to expect:** The `events` array contains the travel message. Redis Agent Memory adds an `eventId` and `systemTimestamp`, showing that the application can recover the complete event later using only the session ID. -Run the event write once. In the SDK files, comment out event creation and its output statements before subsequent runs. Keep the session retrieval. +4. Before the next step, comment out the event write in your SDK file so that rerunning it does not save the same message again: + + * **Python:** Comment out the `event = agent_memory.add_session_event(...)` call, including all its argument lines, and `show("Created event", event)`. Add `#` at the start of each line. + * **TypeScript:** Comment out the `const event = await agentMemory.addSessionEvent({...});` call and the two lines that print the created event. Add `//` at the start of each line. + * **curl:** Do not repeat the `POST` command. You can repeat the retrieval command. + +Keep the session retrieval in the file. The next step adds a search after it. ## 2. Recall automatically extracted information diff --git a/content/develop/ai/context-engine/agent-memory/sessions.md b/content/develop/ai/context-engine/agent-memory/sessions.md index 4f1d5fd86a..a96544357b 100644 --- a/content/develop/ai/context-engine/agent-memory/sessions.md +++ b/content/develop/ai/context-engine/agent-memory/sessions.md @@ -11,11 +11,21 @@ title: Sessions weight: 10 --- -Use a stable `sessionId` to store a conversation as an ordered sequence of events. Add an event for each user, assistant, or system message that your application needs to retain. +Session memory holds a conversation's transcript. Your application saves each message as an event, then retrieves the transcript when the user sends the next message. This gives the agent the history it needs to continue the conversation. + +For example, a travel assistant can retrieve the cities a user mentioned earlier in the chat instead of asking again. Each chat has a session ID (`sessionId`) that ties its events together. The same ID lets your application retrieve the conversation later. + +This page covers adding events, retrieving a conversation, and finding session IDs by owner or namespace. It also explains two settings that control the history: retention determines how long a session stays available, and summarization condenses older messages so they take less space in the model's context window. ## Add session events -Each stored event can include: +A session event represents one message from a user, assistant, or system. To add one, send `POST /v1/stores/{storeId}/session-memory/events`, or call `add_session_event` in Python or `addSessionEvent` in TypeScript. + +1. Choose the conversation's session ID and the sender's actor ID. See [Identify users and conversations]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#identify-users-and-conversations" >}}) for examples. +1. Set the message's `role`, `content`, and `createdAt` timestamp. +1. Send the request using the [quickstart's session-event example]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#1-build-conversation-context-with-session-memory" >}}). Repeat it for each new message, using the same session ID throughout the conversation. + +Redis Agent Memory adds an event ID and the time it stored the message. Each stored event can include: | Field | Purpose | |:------|:--------| @@ -30,11 +40,19 @@ Each stored event can include: ## Retrieve conversation context -Before an agent turn, retrieve the session by `sessionId` and provide the relevant events to the agent. Use these events as conversation context for the model. +When the next message arrives, retrieve the session by its ID before calling the model. For example, reuse the curl connection values from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#save-the-connection-values" >}}) and run: + +```sh +curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $API_KEY" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/$SESSION_ID" | jq +``` + +The `events` array contains the retained conversation messages. If older events have been summarized, the response also includes `summary.text`. Pass both to the model with the current user message. See [Build context for each agent turn]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#build-context-for-each-agent-turn" >}}) for an example. ## List sessions -Use `GET /v1/stores/{storeId}/session-memory` to find session IDs by owner or namespace. Reuse the curl connection values from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#save-the-connection-values" >}}). +To let a user reopen an earlier conversation, first find its session ID. `GET /v1/stores/{storeId}/session-memory` lists IDs by owner or namespace; it does not return the conversation events. Reuse the curl connection values from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#save-the-connection-values" >}}). To list sessions in one namespace, set `NAMESPACE_ID` to the stable ID returned when you [create a namespace]({{< relref "/develop/ai/context-engine/agent-memory/namespaces#create-a-personal-namespace" >}}): @@ -68,13 +86,17 @@ The response contains session IDs in `items` and the number of matching sessions ## Configure session retention -The session-memory time to live (TTL) controls how long sessions remain available. Configure it according to the retention requirements of your application. When a session expires, its events are no longer available through session-memory retrieval. +The session-memory time to live (TTL) controls how long sessions remain available. Choose a duration that lets users resume conversations for as long as your application needs. When a session expires, its events are no longer available through session-memory retrieval. + +In the Redis Cloud console, open your service's **Configuration** tab and select **Edit**. Set **Short-term TTL** under **Memory configuration**. See [memory configuration]({{< relref "/operate/iris/agent-memory/create-service#memory-configuration" >}}) to configure the session-memory TTL in Redis Cloud. ## Configure automatic summarization -Automatic summarization limits the amount of conversation history that must be added to a model's context window. Configure: +As a conversation grows, its messages take more space in the model's context window. Automatic summarization replaces older messages with a shorter summary while keeping recent messages in full. + +In the Redis Cloud console, open your service's **Configuration** tab and select **Edit**. Under **Memory configuration**, enable **Automatic summarization** and set: * **Summarize after:** The number of messages a session can contain before older messages are summarized. * **Keep most recent:** The number of recent messages that remain available in full. From 64d3c58b63c432ea74b8e9bb00ff0bf8a4cc054f Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Thu, 15 Oct 2026 10:23:57 -0700 Subject: [PATCH 09/26] Distinguish memory ownership from authenticated access --- .../agent-memory/developer-guide.md | 32 ++++++--- .../agent-memory/long-term-memory.md | 2 + .../context-engine/agent-memory/namespaces.md | 4 +- .../context-engine/agent-memory/sessions.md | 31 +++++---- .../self-managed/authentication.md | 66 +++++++++++++++++++ .../self-managed/configuration.md | 3 + 6 files changed, 117 insertions(+), 21 deletions(-) diff --git a/content/develop/ai/context-engine/agent-memory/developer-guide.md b/content/develop/ai/context-engine/agent-memory/developer-guide.md index eb5aaf5bb3..e2cddc8d88 100644 --- a/content/develop/ai/context-engine/agent-memory/developer-guide.md +++ b/content/develop/ai/context-engine/agent-memory/developer-guide.md @@ -26,17 +26,33 @@ Every client needs the service endpoint, Store ID, and application programming i ## Identify users and conversations -Redis Agent Memory uses IDs to keep conversations and owners distinct. Choose them in your application, then reuse them in memory requests. For a travel assistant: +Use one stable account ID for the current user across conversations. Get it from your application's authenticated identity, such as a verified identity-provider claim. Do not generate a new user ID for each chat or trust an owner ID supplied by a browser. This ID determines whose personal memories the application can return. -| Identifier | Application choice | -|:-----------|:-------------------| -| `sessionId` | Generate a universally unique identifier (UUID) when the user starts a new chat and save it with the chat record. Reuse it for every turn in that chat. A new chat gets a new ID. | -| `actorId` | Use the signed-in user's account ID for user messages and an agent ID, such as `travel-agent`, for assistant messages. | -| `ownerId` | Use the same account ID when creating and searching that user's long-term memories. Reuse it across chats so the assistant can recall earlier preferences. | +Some IDs come from your application; others come from Redis Agent Memory: -For example, the quickstart uses `quickstart-user` for the user's actor and owner IDs, and `travel-planning-session` for one conversation. These fixed values let you repeat the example. In an application, use IDs from your account and chat records. IDs must follow the format in the [API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}). +| Identifier | Who supplies it | How to use it | +|:-----------|:----------------|:--------------| +| `sessionId` | Your application, or the server if you omit it when adding the first event. | Save the returned `event.sessionId` and reuse it for later turns. A new chat needs a new ID. | +| `actorId` | Your application, on every session event. | Use the current user's stable ID on the first event. Later messages can use an assistant ID, such as `travel-agent`. | +| Session `ownerId` | The server copies it from the first event's `actorId`. | There is no separate `ownerId` input on a session-event request. Later actors do not change the session owner. | +| Long-term memory `ownerId` | Your application for direct creation; extraction inherits the session owner. | Use the current user's stable ID for personal memories and owner filters. | +| `eventId` | The server. | Save it from the event response if you need to refer to that event. | +| Long-term memory `id` | Your application for direct creation; the server during extraction. | Supply a stable record ID when creating a memory directly. | +| `namespaceId` | The server when it creates a namespace. | Save it and use it in `namespaceRef`. Supply the user's `ownerId` when creating a personal namespace. | -Before retrieving a chat, check in your application's account data that the signed-in user can access it. Set the memory search's owner filter from that authenticated account. For example, a request from `user-42` must not be able to select `user-99` as its owner. A search filter limits results; it does not check who signed in. +For example, the quickstart uses `quickstart-user` as the user's ID and `travel-planning-session` for one conversation. Replace the user ID with your authenticated account ID in an application. IDs must follow the format in the [API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}). + +On-prem deployments can also enforce ownership on the server. When enabled, the first session event's `actorId` and any `ownerId` you supply must match the authenticated User ID, including case. Later events in an accessible session can use a different actor. See [Server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement) for configuration, identity requirements, and the limits of ordinary agent keys. + +### Authorize every memory request + +Unless server-side ownership enforcement is enabled, your application must decide which memories the user can access. Do this in trusted application code, such as your backend, before sending a request to Redis Agent Memory. Store access alone does not restrict a caller to one user's memories. + +* For personal searches, set the `ownerId` filter from the authenticated user's ID. For session listing, use `filterOwnerId`. A request from `user-42` must not be able to select `user-99` as its owner. +* For shared memories, check which shared namespaces that user can access, then restrict the query to those namespace IDs. Do not add a personal owner filter to that shared query: it would hide shared memories written by other users. +* For requests that retrieve or change a record by ID, check that the record belongs to the user or to a shared namespace they can access. These endpoints do not all accept an owner filter; a session or memory ID is not proof of permission. + +Use the authenticated user on every request to choose the owner filter or the permitted shared namespaces. Search filters limit results; they do not authenticate the user. For examples, see [Namespaces](/content/develop/ai/context-engine/agent-memory/namespaces.md). ## Build context for each agent turn diff --git a/content/develop/ai/context-engine/agent-memory/long-term-memory.md b/content/develop/ai/context-engine/agent-memory/long-term-memory.md index 7789c9557b..a5c40386e4 100644 --- a/content/develop/ai/context-engine/agent-memory/long-term-memory.md +++ b/content/develop/ai/context-engine/agent-memory/long-term-memory.md @@ -17,6 +17,8 @@ Long-term memory stores information that remains useful beyond one conversation. ## Memory fields +When you create a memory directly, you must supply `id`, `text`, and `ownerId`. The server sets `createdAt` and `updatedAt`. During automatic extraction, the server creates the memory ID and content and copies the session owner into `ownerId`. Use that same stable user ID when querying personal memories; see [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). + A long-term memory record includes: | Field | Purpose | diff --git a/content/develop/ai/context-engine/agent-memory/namespaces.md b/content/develop/ai/context-engine/agent-memory/namespaces.md index de6a314f9c..0ad0684934 100644 --- a/content/develop/ai/context-engine/agent-memory/namespaces.md +++ b/content/develop/ai/context-engine/agent-memory/namespaces.md @@ -36,7 +36,9 @@ Pass `namespaceRef: {"namespaceId": ""}` when you start a session. For direct long-term memory creation, set `namespaceRef` on each record. To move existing records, use `MoveLongTermMemories` with their IDs and the destination `namespaceRef`. Check both `moved` and `errors` in the response before treating a batch as complete. -Search with `filter.namespaceRef.eq` for one namespace or `filter.namespaceRef.in` for several IDs. These filters match exact namespace IDs; they do not expand a parent into its descendants. List the children and include their IDs when you need to search several levels of a hierarchy. Keep the `ownerId` filter when recalling one user's memories. Namespace scope and search filters do not replace your application's access checks. +Search with `filter.namespaceRef.eq` for one namespace or `filter.namespaceRef.in` for several IDs. These filters match exact namespace IDs; they do not expand a parent into its descendants. List the children and include their IDs when you need to search several levels of a hierarchy. Keep the `ownerId` filter when recalling one user's memories. + +Without server-side ownership enforcement, your application must check that the current user can access each namespace before querying it. With enforcement enabled, personal namespace access follows the root owner's authenticated User ID. Shared namespaces are accessible to callers with the required store permission; enforcement does not add team membership checks. If a shared namespace is only for one team, enforce that rule in your application or use separate stores and grants. See [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). ## Manage namespaces diff --git a/content/develop/ai/context-engine/agent-memory/sessions.md b/content/develop/ai/context-engine/agent-memory/sessions.md index a96544357b..c5853c3da0 100644 --- a/content/develop/ai/context-engine/agent-memory/sessions.md +++ b/content/develop/ai/context-engine/agent-memory/sessions.md @@ -21,22 +21,25 @@ This page covers adding events, retrieving a conversation, and finding session I A session event represents one message from a user, assistant, or system. To add one, send `POST /v1/stores/{storeId}/session-memory/events`, or call `add_session_event` in Python or `addSessionEvent` in TypeScript. -1. Choose the conversation's session ID and the sender's actor ID. See [Identify users and conversations]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#identify-users-and-conversations" >}}) for examples. +1. Set the first event's `actorId` to the current user's stable, authenticated ID. The server uses it as the session owner. Later events can identify other actors, such as the assistant. +1. Supply a `sessionId`, or omit it to let the server create one. Save the returned `event.sessionId` for later requests. See [Identify users and conversations]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#identify-users-and-conversations" >}}) for examples. 1. Set the message's `role`, `content`, and `createdAt` timestamp. 1. Send the request using the [quickstart's session-event example]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#1-build-conversation-context-with-session-memory" >}}). Repeat it for each new message, using the same session ID throughout the conversation. -Redis Agent Memory adds an event ID and the time it stored the message. Each stored event can include: +Redis Agent Memory adds the event ID and storage timestamp. The event fields are: -| Field | Purpose | -|:------|:--------| -| `eventId` | Server-generated identifier for the event. | -| `sessionId` | Session that contains the event. | -| `actorId` | User, agent, or other actor that produced the event. | -| `role` | Role of the message in the conversation. | -| `content` | Message content, including its text. | -| `createdAt` | Time the event occurred in the application. | -| `systemTimestamp` | Time Redis Agent Memory stored the event. | -| `metadata` | Optional application-specific information associated with the event. | +| Field | Who supplies it | Purpose | +|:------|:----------------|:--------| +| `eventId` | Server-generated. | Identifies the event. | +| `sessionId` | Client-supplied, or server-generated when omitted. | Identifies the session that contains the event. | +| `actorId` | Required from the client. | Identifies the user, agent, or other actor that produced the event. | +| `role` | Required from the client. | Role of the message in the conversation. | +| `content` | Required from the client. | Message content, including its text. | +| `createdAt` | Required from the client. | Time the event occurred in the application. | +| `systemTimestamp` | Server-generated. | Time Redis Agent Memory stored the event. | +| `metadata` | Optional from the client. | Application-specific information associated with the event. | + +The session's `ownerId` is returned when you retrieve the session; it is not a session-event input. With [server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement), the first event's `actorId` must match the authenticated User ID. ## Retrieve conversation context @@ -50,10 +53,14 @@ curl --fail-with-body --silent --show-error \ The `events` array contains the retained conversation messages. If older events have been summarized, the response also includes `summary.text`. Pass both to the model with the current user message. See [Build context for each agent turn]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#build-context-for-each-agent-turn" >}}) for an example. +Without server-side ownership enforcement, your application must check that the current user can access this session before retrieving it. The request selects a session by ID; it does not include an owner filter. + ## List sessions To let a user reopen an earlier conversation, first find its session ID. `GET /v1/stores/{storeId}/session-memory` lists IDs by owner or namespace; it does not return the conversation events. Reuse the curl connection values from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#save-the-connection-values" >}}). +For a user's personal conversations, set `filterOwnerId` to that user's authenticated ID. A namespace-only query is suitable for shared conversations only after your application checks access to that namespace. Reserve `includeAll=true` for callers your application authorizes to list the whole store. See [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). + To list sessions in one namespace, set `NAMESPACE_ID` to the stable ID returned when you [create a namespace]({{< relref "/develop/ai/context-engine/agent-memory/namespaces#create-a-personal-namespace" >}}): ```sh diff --git a/content/operate/iris/agent-memory/self-managed/authentication.md b/content/operate/iris/agent-memory/self-managed/authentication.md index a9431450c0..70dfcc2f22 100644 --- a/content/operate/iris/agent-memory/self-managed/authentication.md +++ b/content/operate/iris/agent-memory/self-managed/authentication.md @@ -135,6 +135,72 @@ Operation mapping: | `write` | Add/delete session events, delete session memory, set session summary, set session property, create/update/delete long-term memory. | {{< /table-scrollable >}} +## Server-side ownership enforcement + +Store grants let a caller use a store. Ownership enforcement adds a check within that store: a caller can access their own personal records and shared records. This option is available for on-prem deployments and is disabled by default. + +With enforcement disabled, `ownerId` is client-supplied information, not an access check. Your application must authenticate the user and restrict every request to that user's personal memories or shared memories they can access. For searches and session lists, derive the owner filter from the authenticated user. For direct-ID requests, check access before making the request. See [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). + +### Match stored owners to authenticated users + +Use one stable user ID across your application and its identity provider (IdP). It must meet the identifier format in the [API reference](/content/develop/ai/context-engine/agent-memory/api-reference.md). Enforcement compares stored owners with the authenticated User ID, including case. It does not take the current user's identity from an `ownerId` field in the request. + +The source of that authenticated ID depends on the credential: + +| Credential | Source of the authenticated User ID | +| --- | --- | +| Agent key with a user binding | The user ID associated with the key. The client cannot change it by sending a different owner ID. | +| Agent key without a user binding | No User ID. With enforcement enabled, it can access shared records but cannot access personal records or create a record for a named owner. | +| OpenID Connect (OIDC) token | The verified token claim selected by `auth.oidc.user_id_claim`, which defaults to `sub`. Claim mapping alone does not grant store access. | + +There are two limits to the current identity setup: + +* The Identity Service's `POST /v1/api-keys` endpoint does not accept a user binding or copy the creator's User ID into the key. The key created in the [deployment walkthrough](/content/operate/iris/agent-memory/self-managed/deploy.md#7-mint-an-agent-key) is unbound. Configuring an IdP claim does not bind that key to a user. +* Direct OIDC authentication in the Redis Agent Memory Data Plane is a preview. It validates tokens but does not yet resolve the subject's store grants. A valid token alone therefore receives `403 Forbidden` for store operations. + +Before enabling enforcement for personal memories, confirm that your deployment can supply an authenticated User ID. The standard key-minting walkthrough alone does not meet this requirement. + +For example, if the authenticated User ID is `user-42`: + +| Operation | Required owner value | +| --- | --- | +| Start a session | Set the first event's `actorId` to `user-42`. The server copies it to the session's `ownerId`; the event request has no separate owner field. | +| Add to an accessible session | You can use another `actorId`, such as `travel-agent`, for an assistant message. This does not change the session owner. | +| Create a long-term memory directly | Set `ownerId` to `user-42`. This applies even when placing the record in a shared namespace. | +| Create a personal namespace root | Set `ownerId` to `user-42`. Child namespaces inherit the root owner. | + +Changing a long-term memory's owner also requires the new `ownerId` to match the authenticated User ID. An attempt to assign another user as owner returns `403 Forbidden`. + +### Enable ownership enforcement + +For an existing store, first confirm that its namespace placement index migration is complete. Compare the owners of existing personal sessions, memories, and namespace roots with the authenticated user IDs your deployment supplies. The setting does not rewrite stored owners. A mismatch, or a missing owner, makes those personal records inaccessible to ordinary callers after enforcement is enabled. + +In chart-rendered Helm values, set: + +```yaml +memory: + ownership: + enabled: true +``` + +In a bring-your-own Data Plane configuration file, use the same setting at the root: + +```yaml +ownership: + enabled: true +``` + +The environment variable `MEM_OWNERSHIP_ENABLED` overrides the file setting. Its default is `false`. Apply the configuration to the Data Plane servers, then verify access with two different users: + +1. Confirm each user can retrieve their own personal records and search for them. +1. Confirm neither user can retrieve the other's personal records by ID or find them by changing an owner filter. +1. Confirm both users can access shared records when their store grants allow it. +1. Confirm an unbound agent key cannot read personal records or create a session with a named user as its first actor. + +Search and list operations apply ownership restrictions in addition to client filters. An owner filter cannot expand access. Direct-ID requests for another user's personal record return `404 Not Found`. + +Shared scope grants access to all callers with the required store permission. It does not restrict access to a particular team. Keep team membership checks in your application, or separate teams into stores with different grants. Configured worker identities retain access across owners for background work; do not give worker credentials to applications. + ## Worker callbacks Redis Agent Memory workers consume background jobs and call the Data Plane to read diff --git a/content/operate/iris/agent-memory/self-managed/configuration.md b/content/operate/iris/agent-memory/self-managed/configuration.md index 4daff23f61..3a732021b0 100644 --- a/content/operate/iris/agent-memory/self-managed/configuration.md +++ b/content/operate/iris/agent-memory/self-managed/configuration.md @@ -41,6 +41,7 @@ Data Plane settings go under `memory`, or at the top level of a BYO Data Plane c | `databases` | Required. Store Redis, as `databases."1".urls`. | | `embedding.models.default_embedding_model`, `embedding.models.dimensions` | Required. The embedding model and its vector size. The Control Plane's `embedding.dimensions` must match. | | `auth` | Data Plane authentication. Defaults to `agent_key`. | +| `ownership.enabled` | Restricts personal records to their authenticated owner while allowing shared records. Defaults to `false`. Requires a matching authenticated User ID for personal access; ordinary unbound agent keys do not provide one. See [Server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement) before enabling it. | | `inference_providers` | Required when any `llm` block is set. Each entry holds a provider's `endpoint`. | | `server` | Data Plane bind address and port. | | `license.license_path` | Path where the license Secret is mounted. | @@ -56,6 +57,8 @@ entry. The endpoint goes in `inference_providers..endpoint`, not in the `l In the chart-rendered configuration, the chart writes the agent-key introspection settings for you. Leave `auth.method` unset to keep the `agent_key` default. +For ownership enforcement, use `memory.ownership.enabled` in Helm values or `ownership.enabled` in a BYO Data Plane configuration file. `MEM_OWNERSHIP_ENABLED` overrides the file setting. Enabling this option does not bind agent keys to users or rewrite existing owners. + ## Bring-your-own (BYO) configuration With BYO configuration, you supply each complete configuration file in your own Secret, and the From 9ff123836da26654df131411bc54a1ec35961495 Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Wed, 21 Oct 2026 12:18:54 -0700 Subject: [PATCH 10/26] Show how to list sessions with the beta SDKs --- .../ai/context-engine/agent-memory/_index.md | 2 + .../api-reference/openapi-agent-memory.json | 8602 ++++++++++++++--- .../agent-memory/list-sessions.md | 118 + .../agent-memory/rest-api-quickstart.md | 4 +- 4 files changed, 7392 insertions(+), 1334 deletions(-) create mode 100644 content/develop/ai/context-engine/agent-memory/list-sessions.md diff --git a/content/develop/ai/context-engine/agent-memory/_index.md b/content/develop/ai/context-engine/agent-memory/_index.md index 92a828ae16..2c0f278f0f 100644 --- a/content/develop/ai/context-engine/agent-memory/_index.md +++ b/content/develop/ai/context-engine/agent-memory/_index.md @@ -140,3 +140,5 @@ After your Redis Agent Memory service is ready, choose a client. Each quickstart For shared integration concepts, identifiers, and authentication, see the [Redis Agent Memory developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md). To generate a complete conversational agent that uses Agent Memory, open the [AI agent builder](/content/develop/ai/agent-builder/_index.md) and select **Redis Iris Conversational Assistant**. + +To build a conversation list with recent activity and pagination, see [List and sort sessions](/content/develop/ai/context-engine/agent-memory/list-sessions.md). diff --git a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json index a055ea7b6e..c3095f3194 100644 --- a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json +++ b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json @@ -6,53 +6,50 @@ "description": "API for storing, retrieving, and searching Redis Agent Memory session memory and long-term memory.\n\nAuthentication depends on your deployment. Use the credential issued for your Redis Agent Memory service or store. For self-managed deployments, see the self-managed Redis Agent Memory guide for authentication options.\n" }, "paths": { - "/health": { - "get": { - "description": "Return information about the operational status of the service.", - "operationId": "Health", - "responses": { - "200": { - "description": "Health 200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HealthResponseContent" - } + "/v1/stores/{storeId}/document-ingestions": { + "post": { + "description": "Submit one revisioned document for extraction.", + "operationId": "CreateDocumentIngestion", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateDocumentIngestionRequestContent" } } - } + }, + "required": true }, - "tags": [ - "Service Health" - ] - } - }, - "/v1/stores/{storeId}/health": { - "get": { - "description": "Returns read-only health for applicable store-scoped RAM features.", - "operationId": "StoreHealth", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { - "200": { - "description": "StoreHealth 200 response", + "202": { + "description": "CreateDocumentIngestion 202 response", + "headers": { + "Location": { + "schema": { + "type": "string" + }, + "required": true + } + }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StoreHealthResponseContent" + "$ref": "#/components/schemas/CreateDocumentIngestionResponseContent" } } } @@ -107,6 +104,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -117,12 +124,32 @@ } } }, + "422": { + "description": "IngestionHookStoppedError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookStoppedErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -146,49 +173,58 @@ } } } + }, + "503": { + "description": "IngestionHookRetryError 503 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookRetryErrorResponseContent" + } + } + } } - }, - "tags": [ - "Store Health" - ] + } } }, - "/v1/stores/{storeId}/long-term-memory": { - "delete": { - "description": "Deletes long-term memories in bulk by their IDs.", - "operationId": "BulkDeleteLongTermMemories", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BulkDeleteLongTermMemoriesRequestContent" - } - } - }, - "required": true - }, + "/v1/stores/{storeId}/document-ingestions/{ingestionId}": { + "get": { + "operationId": "GetDocumentIngestion", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "ingestionId", + "in": "path", + "description": "Server-generated document ingestion identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." }, "required": true } ], "responses": { "200": { - "description": "BulkDeleteLongTermMemories 200 response", + "description": "GetDocumentIngestion 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BulkDeleteLongTermMemoriesResponseContent" + "$ref": "#/components/schemas/GetDocumentIngestionResponseContent" } } } @@ -243,12 +279,12 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", + "423": { + "description": "ResourceSuspendedError 423 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" } } } @@ -263,16 +299,6 @@ } } }, - "429": { - "description": "TooManyRequestsError 429 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" - } - } - } - }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -283,46 +309,63 @@ } } } - }, - "tags": [ - "Long-Term Memory" - ] - }, - "post": { - "description": "Creates long-term memories in bulk.", - "operationId": "BulkCreateLongTermMemories", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BulkCreateLongTermMemoriesRequestContent" - } - } - }, - "required": true - }, + } + } + }, + "/v1/stores/{storeId}/document-ingestions/{ingestionId}/outputs": { + "get": { + "operationId": "ListDocumentIngestionOutputs", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "ingestionId", + "in": "path", + "description": "Server-generated document ingestion identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." }, "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } } ], "responses": { - "201": { - "description": "BulkCreateLongTermMemories 201 response", + "200": { + "description": "ListDocumentIngestionOutputs 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BulkCreateLongTermMemoriesResponseContent" + "$ref": "#/components/schemas/ListDocumentIngestionOutputsResponseContent" } } } @@ -377,12 +420,12 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", + "423": { + "description": "ResourceSuspendedError 423 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" } } } @@ -397,16 +440,6 @@ } } }, - "429": { - "description": "TooManyRequestsError 429 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" - } - } - } - }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -417,25 +450,13 @@ } } } - }, - "tags": [ - "Long-Term Memory" - ] + } } }, - "/v1/stores/{storeId}/long-term-memory/search": { - "post": { - "description": "Runs a semantic search on long-term memory with filtering options.", - "operationId": "SearchLongTermMemory", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SearchLongTermMemoryRequestContent" - } - } - } - }, + "/v1/stores/{storeId}/health": { + "get": { + "description": "Returns read-only health for applicable store-scoped RAM features.", + "operationId": "StoreHealth", "parameters": [ { "name": "storeId", @@ -453,11 +474,11 @@ ], "responses": { "200": { - "description": "SearchLongTermMemory 200 response", + "description": "StoreHealth 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SearchLongTermMemoryResponseContent" + "$ref": "#/components/schemas/StoreHealthResponseContent" } } } @@ -522,6 +543,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -554,49 +585,54 @@ } }, "tags": [ - "Long-Term Memory" + "Store Health" ] } }, - "/v1/stores/{storeId}/long-term-memory/{memoryId}": { + "/v1/stores/{storeId}/ingestion-hooks/events": { "get": { - "description": "Returns a long-term memory by its ID.", - "operationId": "GetLongTermMemory", + "description": "List hook decisions in stable newest-first order. Pages can be empty after owner filtering; continue while nextCursor is present. Internal run reservations are omitted.", + "operationId": "ListIngestionHookStatuses", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true }, { - "name": "memoryId", - "in": "path", - "description": "The memory record ID.", + "name": "limit", + "in": "query", "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The memory record ID." - }, - "required": true - } - ], - "responses": { - "200": { - "description": "GetLongTermMemory 200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetLongTermMemoryResponseContent" + "type": "integer", + "default": 20, + "maximum": 100, + "minimum": 1, + "format": "int32" + } + }, + { + "name": "cursor", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ListIngestionHookStatuses 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListIngestionHookStatusesResponseContent" } } } @@ -631,52 +667,22 @@ } } }, - "404": { - "description": "NotFoundError 404 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotFoundErrorResponseContent" - } - } - } - }, - "408": { - "description": "TimeoutError 408 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TimeoutErrorResponseContent" - } - } - } - }, - "413": { - "description": "PayloadTooLargeError 413 response", + "423": { + "description": "ResourceSuspendedError 423 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" } } } }, "424": { - "description": "FailedDependencyError 424 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" - } - } - } - }, - "429": { - "description": "TooManyRequestsError 429 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -693,56 +699,44 @@ } }, "tags": [ - "Long-Term Memory" + "Ingestion Hooks" ] - }, - "patch": { - "description": "Partially updates a long-term memory by its ID.", - "operationId": "UpdateLongTermMemory", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateLongTermMemoryRequestContent" - } - } - } - }, + } + }, + "/v1/stores/{storeId}/ingestion-hooks/events/{eventId}": { + "get": { + "description": "Read a hook decision and application status. Requires authenticated store access and, when enforced, matching owner scope. Content and secrets are never returned.", + "operationId": "GetIngestionHookStatus", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true }, { - "name": "memoryId", + "name": "eventId", "in": "path", - "description": "The memory record ID.", "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The memory record ID." + "type": "string" }, "required": true } ], "responses": { "200": { - "description": "UpdateLongTermMemory 200 response", + "description": "GetIngestionHookStatus 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateLongTermMemoryResponseContent" + "$ref": "#/components/schemas/GetIngestionHookStatusResponseContent" } } } @@ -787,42 +781,22 @@ } } }, - "408": { - "description": "TimeoutError 408 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TimeoutErrorResponseContent" - } - } - } - }, - "413": { - "description": "PayloadTooLargeError 413 response", + "423": { + "description": "ResourceSuspendedError 423 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" } } } }, "424": { - "description": "FailedDependencyError 424 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" - } - } - } - }, - "429": { - "description": "TooManyRequestsError 429 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -839,19 +813,19 @@ } }, "tags": [ - "Long-Term Memory" + "Ingestion Hooks" ] } }, - "/v1/stores/{storeId}/long-term-memory/{memoryId}/fields": { - "patch": { - "description": "Atomically updates a custom-typed long-term memory's text and/or attributes, scoped to the caller's bound memory type. Used by the custom-extraction worker.", - "operationId": "UpdateLongTermMemoryFields", + "/v1/stores/{storeId}/long-term-memory": { + "delete": { + "description": "Deletes long-term memories in bulk by their IDs.", + "operationId": "BulkDeleteLongTermMemories", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateLongTermMemoryFieldsRequestContent" + "$ref": "#/components/schemas/BulkDeleteLongTermMemoriesRequestContent" } } }, @@ -870,24 +844,18 @@ "description": "The store instance ID." }, "required": true - }, - { - "name": "memoryId", - "in": "path", - "description": "The memory record ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The memory record ID." - }, - "required": true } ], "responses": { "200": { - "description": "UpdateLongTermMemoryFields 200 response" + "description": "BulkDeleteLongTermMemories 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkDeleteLongTermMemoriesResponseContent" + } + } + } }, "400": { "description": "BadRequestError 400 response", @@ -939,6 +907,26 @@ } } }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -973,12 +961,20 @@ "tags": [ "Long-Term Memory" ] - } - }, - "/v1/stores/{storeId}/session-memory": { - "get": { - "description": "Returns a paginated list of session IDs for a store.", - "operationId": "ListSessions", + }, + "post": { + "description": "Creates long-term memories in bulk. Records without namespace or namespaceRef stay un-namespaced. Automatic routing does not place them in a shared namespace. An explicit namespaceRef must pass destination admission and is never rerouted. Admission failures are reported per record in errors.", + "operationId": "BulkCreateLongTermMemories", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkCreateLongTermMemoriesRequestContent" + } + } + }, + "required": true + }, "parameters": [ { "name": "storeId", @@ -992,58 +988,15 @@ "description": "The store instance ID." }, "required": true - }, - { - "name": "limit", - "in": "query", - "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", - "schema": { - "type": "integer", - "default": 100, - "maximum": 1000, - "minimum": 1, - "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", - "format": "int32" - } - }, - { - "name": "pageToken", - "in": "query", - "description": "Opaque token from a previous response for the next page.", - "schema": { - "type": "string", - "description": "Opaque token from a previous response for the next page." - } - }, - { - "name": "filterOwnerId", - "in": "query", - "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll." - } - }, - { - "name": "includeAll", - "in": "query", - "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters.", - "schema": { - "type": "boolean", - "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters." - } } ], "responses": { - "200": { - "description": "ListSessions 200 response", + "201": { + "description": "BulkCreateLongTermMemories 201 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListSessionsResponseContent" + "$ref": "#/components/schemas/BulkCreateLongTermMemoriesResponseContent" } } } @@ -1108,12 +1061,32 @@ } } }, + "422": { + "description": "IngestionHookStoppedError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookStoppedErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -1137,22 +1110,32 @@ } } } - } - }, - "tags": [ - "Session Memory" - ] + }, + "503": { + "description": "IngestionHookRetryError 503 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookRetryErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Long-Term Memory" + ] } }, - "/v1/stores/{storeId}/session-memory/events": { + "/v1/stores/{storeId}/long-term-memory/move": { "post": { - "description": "Appends a single event to a session. Creates the session if it does not exist. If sessionId is omitted, the server generates one.", - "operationId": "AddSessionEvent", + "description": "Moves long-term memories to an explicit namespaceRef, or automatically routes a memory that is already in a shared namespace when namespaceRef is omitted. Personal and un-namespaced memories are not automatic-routing candidates and keep their current placement with a per-item error. If routing is disabled or no shared destination qualifies, the memory also keeps its current placement and receives a per-item error. An explicit destination must pass admission and is never rerouted. Returns 409 when the explicit destination is archived; automatic routing and admission failures are reported per item in errors.", + "operationId": "MoveLongTermMemories", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddSessionEventRequestContent" + "$ref": "#/components/schemas/MoveLongTermMemoriesRequestContent" } } }, @@ -1162,24 +1145,24 @@ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { - "201": { - "description": "AddSessionEvent 201 response", + "200": { + "description": "MoveLongTermMemories 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddSessionEventResponseContent" + "$ref": "#/components/schemas/MoveLongTermMemoriesResponseContent" } } } @@ -1234,6 +1217,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -1244,6 +1237,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1276,14 +1279,23 @@ } }, "tags": [ - "Session Memory" + "Long-Term Memory" ] } }, - "/v1/stores/{storeId}/session-memory/{sessionId}": { - "delete": { - "description": "Deletes the session memory for a session.", - "operationId": "DeleteSessionMemory", + "/v1/stores/{storeId}/long-term-memory/search": { + "post": { + "description": "Runs a semantic search on long-term memory with filtering options.", + "operationId": "SearchLongTermMemory", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchLongTermMemoryRequestContent" + } + } + } + }, "parameters": [ { "name": "storeId", @@ -1297,24 +1309,18 @@ "description": "The store instance ID." }, "required": true - }, - { - "name": "sessionId", - "in": "path", - "description": "The session ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "required": true } ], "responses": { - "204": { - "description": "DeleteSessionMemory 204 response" + "200": { + "description": "SearchLongTermMemory 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchLongTermMemoryResponseContent" + } + } + } }, "400": { "description": "BadRequestError 400 response", @@ -1376,12 +1382,22 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -1408,12 +1424,14 @@ } }, "tags": [ - "Session Memory" + "Long-Term Memory" ] - }, + } + }, + "/v1/stores/{storeId}/long-term-memory/{memoryId}": { "get": { - "description": "Returns the session memory for a session.", - "operationId": "GetSessionMemory", + "description": "Returns a long-term memory by its ID.", + "operationId": "GetLongTermMemory", "parameters": [ { "name": "storeId", @@ -1429,35 +1447,26 @@ "required": true }, { - "name": "sessionId", + "name": "memoryId", "in": "path", - "description": "The session ID.", + "description": "The memory record ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." + "description": "The memory record ID." }, "required": true - }, - { - "name": "includeSummarisedEvents", - "in": "query", - "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage.", - "schema": { - "type": "boolean", - "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage." - } } ], "responses": { "200": { - "description": "GetSessionMemory 200 response", + "description": "GetLongTermMemory 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetSessionMemoryResponseContent" + "$ref": "#/components/schemas/GetLongTermMemoryResponseContent" } } } @@ -1522,6 +1531,16 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { "description": "FailedDependencyError 424 response", "content": { @@ -1554,14 +1573,21 @@ } }, "tags": [ - "Session Memory" + "Long-Term Memory" ] - } - }, - "/v1/stores/{storeId}/session-memory/{sessionId}/events/{eventId}": { - "delete": { - "description": "Deletes a single event from a session by event ID.", - "operationId": "DeleteSessionEvent", + }, + "patch": { + "description": "Partially updates a long-term memory by its ID. The merged record must pass admission at its destination. Updates never select a different destination automatically.", + "operationId": "UpdateLongTermMemory", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLongTermMemoryRequestContent" + } + } + } + }, "parameters": [ { "name": "storeId", @@ -1577,35 +1603,29 @@ "required": true }, { - "name": "sessionId", + "name": "memoryId", "in": "path", - "description": "The session ID.", + "description": "The memory record ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "required": true - }, - { - "name": "eventId", - "in": "path", - "description": "The event ID.", - "schema": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "The event ID." + "description": "The memory record ID." }, "required": true } ], "responses": { - "204": { - "description": "DeleteSessionEvent 204 response" + "200": { + "description": "UpdateLongTermMemory 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLongTermMemoryResponseContent" + } + } + } }, "400": { "description": "BadRequestError 400 response", @@ -1657,6 +1677,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -1667,12 +1697,22 @@ } } }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -1699,12 +1739,24 @@ } }, "tags": [ - "Session Memory" + "Long-Term Memory" ] - }, - "get": { - "description": "Returns a single event from a session by event ID.", - "operationId": "GetSessionEvent", + } + }, + "/v1/stores/{storeId}/long-term-memory/{memoryId}/fields": { + "patch": { + "description": "Atomically updates a custom-typed long-term memory's text and/or attributes, scoped to the caller's bound memory type. Used by the custom-extraction worker. New text must pass destination admission before the update is applied.", + "operationId": "UpdateLongTermMemoryFields", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLongTermMemoryFieldsRequestContent" + } + } + }, + "required": true + }, "parameters": [ { "name": "storeId", @@ -1720,42 +1772,22 @@ "required": true }, { - "name": "sessionId", + "name": "memoryId", "in": "path", - "description": "The session ID.", + "description": "The memory record ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "required": true - }, - { - "name": "eventId", - "in": "path", - "description": "The event ID.", - "schema": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "The event ID." + "description": "The memory record ID." }, "required": true } ], "responses": { "200": { - "description": "GetSessionEvent 200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetSessionEventResponseContent" - } - } - } + "description": "UpdateLongTermMemoryFields 200 response" }, "400": { "description": "BadRequestError 400 response", @@ -1807,22 +1839,32 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", + "409": { + "description": "ConflictError 409 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" } } } }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -1849,14 +1891,14 @@ } }, "tags": [ - "Session Memory" + "Long-Term Memory" ] } }, - "/v1/stores/{storeId}/sessions": { + "/v1/stores/{storeId}/long-term-memory/{memoryId}/sources": { "get": { - "description": "Returns paginated session rows with metadata and caller-selected order. The ID-only ListSessions operation remains unchanged.", - "operationId": "ListSessionSummaries", + "description": "Pages source links for one long-term memory. Tokens bind this query and expire.", + "operationId": "ListMemorySourceLinks", "parameters": [ { "name": "storeId", @@ -1871,85 +1913,203 @@ }, "required": true }, + { + "name": "memoryId", + "in": "path", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "required": true + }, { "name": "limit", "in": "query", - "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", "schema": { "type": "integer", - "default": 100, - "maximum": 1000, + "maximum": 100, "minimum": 1, - "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", "format": "int32" } }, { "name": "pageToken", "in": "query", - "description": "Opaque token from a previous response for the next page. Bound to the effective filters and sort.", "schema": { - "type": "string", - "description": "Opaque token from a previous response for the next page. Bound to the effective filters and sort." + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ListMemorySourceLinks 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListMemorySourceLinksResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } } }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Long-Term Memory" + ] + } + }, + "/v1/stores/{storeId}/long-term-memory/{memoryId}/sources/{sourceId}/evidence": { + "get": { + "description": "Pages extraction evidence for one source of a memory, in append order. Tokens bind the store, current owner, memory and source, and expire.", + "operationId": "ListMemorySourceEvidence", + "parameters": [ { - "name": "filterOwnerId", - "in": "query", - "description": "Filter sessions by owner. Combined with namespaceRef using AND. Mutually exclusive with includeAll.", + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Filter sessions by owner. Combined with namespaceRef using AND. Mutually exclusive with includeAll." - } + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true }, { - "name": "namespaceRef", - "in": "query", - "description": "Filter by stable namespace ID. Combined with filterOwnerId using AND. Mutually exclusive with includeAll.", + "name": "memoryId", + "in": "path", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Filter by stable namespace ID. Combined with filterOwnerId using AND. Mutually exclusive with includeAll." - } + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "required": true }, { - "name": "includeAll", - "in": "query", - "description": "Set to true to list all visible sessions. Required when no filter is given; mutually exclusive with filters.", + "name": "sourceId", + "in": "path", + "description": "Stable source identifier for one revisioned origin in a store.", "schema": { - "type": "boolean", - "description": "Set to true to list all visible sessions. Required when no filter is given; mutually exclusive with filters." - } + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable source identifier for one revisioned origin in a store." + }, + "required": true }, { - "name": "sortBy", + "name": "limit", "in": "query", - "description": "Sort field. Defaults to updatedAt.", "schema": { - "$ref": "#/components/schemas/SessionListSortBy" + "type": "integer", + "maximum": 100, + "minimum": 1, + "format": "int32" } }, { - "name": "sortOrder", + "name": "pageToken", "in": "query", - "description": "Sort direction. Defaults to desc for timestamps and asc for sessionId.", "schema": { - "$ref": "#/components/schemas/SessionListSortOrder" + "type": "string" } } ], "responses": { "200": { - "description": "ListSessionSummaries 200 response", + "description": "ListMemorySourceEvidence 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListSessionSummariesResponseContent" + "$ref": "#/components/schemas/ListMemorySourceEvidenceResponseContent" } } } @@ -2004,16 +2164,6 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" - } - } - } - }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -2034,16 +2184,6 @@ } } }, - "429": { - "description": "TooManyRequestsError 429 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" - } - } - } - }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -2056,151 +2196,5363 @@ } }, "tags": [ - "Session Memory" + "Long-Term Memory" ] } - } - }, - "components": { - "schemas": { - "AddSessionEventRequestContent": { - "type": "object", - "properties": { - "sessionId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional session ID. If omitted, the server generates a unique ID." + }, + "/v1/stores/{storeId}/namespace-routing": { + "get": { + "description": "Returns the store's namespace routing policy. An empty disabled policy is returned when none has been saved.", + "operationId": "GetNamespaceRouting", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetNamespaceRouting 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetNamespaceRoutingResponseContent" + } + } + } }, - "actorId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant." + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Namespaces" + ] + }, + "put": { + "description": "Replaces the store's namespace routing policy. Namespaces remain hierarchy records; routing is an ordered store-level document of plug-in strategies.", + "operationId": "PutNamespaceRouting", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PutNamespaceRoutingRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "PutNamespaceRouting 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PutNamespaceRoutingResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Namespaces" + ] + } + }, + "/v1/stores/{storeId}/namespaces": { + "get": { + "description": "Lists namespace roots or direct children.", + "operationId": "ListNamespaces", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "parentId", + "in": "query", + "description": "List direct children of this namespace. Omit to list roots.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "List direct children of this namespace. Omit to list roots." + } + }, + { + "name": "scope", + "in": "query", + "description": "Restrict root results to one scope. Cannot be combined with parentId.", + "schema": { + "$ref": "#/components/schemas/NamespaceScope" + } + }, + { + "name": "ownerId", + "in": "query", + "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots." + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Field to sort by. Defaults to id. Name sorts by the indexed display name using Redis text collation, not the namespace path. Equal values are ordered by ID ascending.", + "schema": { + "$ref": "#/components/schemas/NamespaceSortBy" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort direction. Defaults to asc. Use the same sort options for each page; concurrent changes can shift offset-based pages.", + "schema": { + "$ref": "#/components/schemas/NamespaceSortOrder" + } + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page." + } + } + ], + "responses": { + "200": { + "description": "ListNamespaces 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListNamespacesResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Namespaces" + ] + }, + "post": { + "description": "Creates a namespace resource as a root, direct child, or explicit hierarchy path. Returns 409 when an active or archived namespace already occupies the requested location.", + "operationId": "CreateNamespace", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateNamespaceRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateNamespace 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateNamespaceResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Namespaces" + ] + } + }, + "/v1/stores/{storeId}/namespaces/{namespaceId}": { + "delete": { + "description": "Deletes an empty leaf namespace. Returns 409 when the namespace has children or memory placements, or when it changed concurrently.", + "operationId": "DeleteNamespace", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "namespaceId", + "in": "path", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteNamespace 204 response" + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Namespaces" + ] + }, + "get": { + "description": "Gets one namespace by its canonical ID.", + "operationId": "GetNamespace", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "namespaceId", + "in": "path", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetNamespace 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetNamespaceResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Namespaces" + ] + }, + "patch": { + "description": "Renames or archives a namespace. Returns 409 when the namespace changed concurrently, an active or archived sibling already uses the requested name, or the namespace is archived.", + "operationId": "UpdateNamespace", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateNamespaceRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "namespaceId", + "in": "path", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateNamespace 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateNamespaceResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Namespaces" + ] + } + }, + "/v1/stores/{storeId}/session-memory": { + "get": { + "description": "Returns a paginated list of session IDs for a store.", + "operationId": "ListSessions", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page." + } + }, + { + "name": "filterOwnerId", + "in": "query", + "description": "Filter sessions by owner. The owner of a session is the actorId of its first event. Matching is exact and case-sensitive. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by owner. The owner of a session is the actorId of its first event. Matching is exact and case-sensitive. Mutually exclusive with includeAll." + } + }, + { + "name": "namespaceRef", + "in": "query", + "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll." + } + }, + { + "name": "includeAll", + "in": "query", + "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters.", + "schema": { + "type": "boolean", + "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters." + } + } + ], + "responses": { + "200": { + "description": "ListSessions 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSessionsResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/session-memory/events": { + "post": { + "description": "Appends a single event to a session. Creates the session if it does not exist. If sessionId is omitted, the server generates one.", + "operationId": "AddSessionEvent", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddSessionEventRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + } + ], + "responses": { + "201": { + "description": "AddSessionEvent 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddSessionEventResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "422": { + "description": "IngestionHookStoppedError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookStoppedErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + }, + "503": { + "description": "IngestionHookRetryError 503 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookRetryErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/session-memory/{sessionId}": { + "delete": { + "description": "Deletes the session memory for a session.", + "operationId": "DeleteSessionMemory", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteSessionMemory 204 response" + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + }, + "get": { + "description": "Returns the session memory for a session.", + "operationId": "GetSessionMemory", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + }, + { + "name": "includeSummarisedEvents", + "in": "query", + "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage.", + "schema": { + "type": "boolean", + "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage." + } + } + ], + "responses": { + "200": { + "description": "GetSessionMemory 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSessionMemoryResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/session-memory/{sessionId}/events/{eventId}": { + "delete": { + "description": "Deletes a single event from a session by event ID.", + "operationId": "DeleteSessionEvent", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + }, + { + "name": "eventId", + "in": "path", + "description": "The event ID.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "The event ID." + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteSessionEvent 204 response" + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + }, + "get": { + "description": "Returns a single event from a session by event ID.", + "operationId": "GetSessionEvent", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + }, + { + "name": "eventId", + "in": "path", + "description": "The event ID.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "The event ID." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetSessionEvent 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSessionEventResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/sessions": { + "get": { + "description": "Returns paginated session rows with metadata and caller-selected order. The ID-only ListSessions operation remains unchanged.", + "operationId": "ListSessionSummaries", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page. Bound to the effective filters and sort.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page. Bound to the effective filters and sort." + } + }, + { + "name": "filterOwnerId", + "in": "query", + "description": "Filter sessions by owner. The owner of a session is the actorId of its first event. Matching is exact and case-sensitive. Combined with namespaceRef using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by owner. The owner of a session is the actorId of its first event. Matching is exact and case-sensitive. Combined with namespaceRef using AND. Mutually exclusive with includeAll." + } + }, + { + "name": "namespaceRef", + "in": "query", + "description": "Filter by stable namespace ID. Combined with filterOwnerId using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter by stable namespace ID. Combined with filterOwnerId using AND. Mutually exclusive with includeAll." + } + }, + { + "name": "includeAll", + "in": "query", + "description": "Set to true to list all visible sessions. Required when no filter is given; mutually exclusive with filters.", + "schema": { + "type": "boolean", + "description": "Set to true to list all visible sessions. Required when no filter is given; mutually exclusive with filters." + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Sort field. Defaults to updatedAt.", + "schema": { + "$ref": "#/components/schemas/SessionListSortBy" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort direction. Defaults to desc for timestamps and asc for sessionId.", + "schema": { + "$ref": "#/components/schemas/SessionListSortOrder" + } + } + ], + "responses": { + "200": { + "description": "ListSessionSummaries 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSessionSummariesResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/sources/{sourceId}/memories": { + "get": { + "description": "Pages memories linked to one source. A source link does not grant access to a memory in another owner or namespace.", + "operationId": "ListSourceMemories", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "sourceId", + "in": "path", + "description": "Stable source identifier for one revisioned origin in a store.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable source identifier for one revisioned origin in a store." + }, + "required": true + }, + { + "name": "ownerId", + "in": "query", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ListSourceMemories 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSourceMemoriesResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Long-Term Memory" + ] + } + }, + "/v1/stores/{storeId}/summary-views": { + "get": { + "description": "Lists Summary View definitions and high-level state. Partition values are not included.", + "operationId": "ListSummaryViews", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 100, + "maximum": 100, + "minimum": 1, + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ListSummaryViews 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSummaryViewsResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + }, + "post": { + "description": "Creates a Summary View definition and asynchronously enqueues its initial reconciliation.", + "operationId": "CreateSummaryView", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSummaryViewRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateSummaryView 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "SummaryViewQuotaError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewQuotaErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + } + }, + "/v1/stores/{storeId}/summary-views/{viewId}": { + "delete": { + "description": "Marks a Summary View deleting and asynchronously removes its schedule and materializations in bounded batches. Repeated requests are idempotent.", + "operationId": "DeleteSummaryView", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + } + ], + "responses": { + "202": { + "description": "DeleteSummaryView 202 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + }, + "get": { + "description": "Returns one Summary View definition without partition values.", + "operationId": "GetSummaryView", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetSummaryView 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + }, + "patch": { + "description": "Updates mutable Summary View fields. Source is immutable. Material changes increment definitionVersion; name- or description-only changes do not enqueue refresh work.", + "operationId": "UpdateSummaryView", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSummaryViewRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateSummaryView 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "SummaryViewQuotaError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewQuotaErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + } + }, + "/v1/stores/{storeId}/summary-views/{viewId}/refresh": { + "post": { + "description": "Asynchronously enqueues one exact partition refresh or a complete view reconciliation. It never performs generation synchronously.", + "operationId": "RefreshSummaryView", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RefreshSummaryViewRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + } + ], + "responses": { + "202": { + "description": "RefreshSummaryView 202 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RefreshSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "SummaryViewQuotaError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewQuotaErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + } + }, + "/v1/stores/{storeId}/summary-views/{viewId}/result": { + "get": { + "description": "Returns one authorized exact Summary View partition after validating its contributing source records. Invalid output is withheld while repair is pending.", + "operationId": "GetSummaryViewResult", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + }, + { + "name": "ownerId", + "in": "query", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + } + }, + { + "name": "sessionId", + "in": "query", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + } + }, + { + "name": "namespaceRef", + "in": "query", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + } + }, + { + "name": "memoryType", + "in": "query", + "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, `message`, and `session_summary_view`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes.\n", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, `message`, and `session_summary_view`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes." + } + }, + { + "name": "includeSourceMemoryIds", + "in": "query", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "GetSummaryViewResult 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSummaryViewResultResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + } + } + }, + "components": { + "schemas": { + "AddSessionEventRequestContent": { + "type": "object", + "properties": { + "idempotencyKey": { + "type": "string", + "maxLength": 128, + "minLength": 1, + "pattern": "^[ -~]+$", + "description": "Required when the store selects session.append input hooks. Reuse this key only for an identical request; accepted retries reuse the saved decision and event identity." + }, + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Optional session ID. If omitted, the server generates a unique ID." + }, + "actorId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Optional deprecated namespace label for the session. This label does not create or resolve a namespace resource. Do not supply it with namespaceRef.\n\nLike `ownerId`, this is a session-level value taken from the first event: the value sent with the first event of a session is stored on the session, and values sent with later events are ignored. Omit it to leave extracted memories without a namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" + }, + "role": { + "$ref": "#/components/schemas/MessageRole" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Content" + }, + "description": "The message content as an array of typed content parts." + }, + "createdAt": { + "type": "string", + "description": "Client-supplied timestamp for when the event happened (UTC).", + "format": "date-time" + }, + "metadata": { + "description": "Optional metadata as any valid JSON value." + } + }, + "required": [ + "actorId", + "content", + "createdAt", + "role" + ] + }, + "AddSessionEventResponseContent": { + "type": "object", + "description": "Response returned after appending a session event.", + "properties": { + "event": { + "$ref": "#/components/schemas/SessionEvent" + } + }, + "required": [ + "event" + ] + }, + "AttributeFilterClause": { + "description": "Filter clause for a single record attribute. Exactly one member is set; the member must match the attribute's declared field type.", + "oneOf": [ + { + "type": "object", + "title": "string", + "properties": { + "string": { + "type": "string", + "description": "Match a string attribute equal to this value." + } + }, + "required": [ + "string" + ] + }, + { + "type": "object", + "title": "number", + "properties": { + "number": { + "type": "number", + "description": "Match a numeric attribute equal to this value.", + "format": "double" + } + }, + "required": [ + "number" + ] + }, + { + "type": "object", + "title": "boolean", + "properties": { + "boolean": { + "type": "boolean", + "description": "Match a boolean attribute equal to this value." + } + }, + "required": [ + "boolean" + ] + }, + { + "type": "object", + "title": "list", + "properties": { + "list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Match a list attribute that contains all of these values." + } + }, + "required": [ + "list" + ] + } + ] + }, + "AttributeFilterMap": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/AttributeFilterClause" + }, + "description": "Per-attribute filter clauses keyed by the custom memory type's field name." + }, + "AttributeMap": { + "type": "object", + "additionalProperties": {}, + "description": "Structured record attributes: a map from the custom memory type's field name to that field's value. Values are arbitrary JSON matching the field's declared type (str, int, float, bool, list[str], list[float], or object)." + }, + "AuthenticationErrorResponseContent": { + "type": "object", + "description": "Authentication credentials are missing, malformed, or invalid.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 401, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/AuthenticationErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "AuthenticationErrorType": { + "type": "string", + "description": "Problem type URI for authentication errors.", + "enum": [ + "/errors/authentication-failed" + ] + }, + "BadRequestErrorResponseContent": { + "type": "object", + "description": "Request validation or input decoding failed.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 400, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/BadRequestErrorType" + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FieldError" + }, + "description": "Optional field-level validation errors." + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "BadRequestErrorType": { + "type": "string", + "description": "Problem type URI for bad request errors.", + "enum": [ + "/errors/invalid-data" + ] + }, + "BulkCreateLongTermMemoriesRequestContent": { + "type": "object", + "description": "Bulk create request for long-term memory records.", + "properties": { + "idempotencyKey": { + "type": "string", + "maxLength": 128, + "minLength": 1, + "pattern": "^[ -~]+$", + "description": "Required when the memory.create input hook is selected. Reuse the same key and original request to resume its saved decision." + }, + "memories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateMemoryRecord" + }, + "maxItems": 100, + "minItems": 1, + "description": "List of memory records to create." + } + }, + "required": [ + "memories" + ] + }, + "BulkCreateLongTermMemoriesResponseContent": { + "type": "object", + "description": "Bulk create result for long-term memory records.", + "properties": { + "created": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "description": "IDs of successfully created memory records. Always returned; empty when no records were created." + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkOperationError" + }, + "description": "Errors for memory records that failed to be created." + } + }, + "required": [ + "created" + ] + }, + "BulkDeleteLongTermMemoriesRequestContent": { + "type": "object", + "description": "Bulk delete request for long-term memory records.", + "properties": { + "memoryIds": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "maxItems": 100, + "minItems": 1, + "description": "List of memory record IDs to delete (1-100 IDs)." + } + }, + "required": [ + "memoryIds" + ] + }, + "BulkDeleteLongTermMemoriesResponseContent": { + "type": "object", + "description": "Bulk delete result for long-term memory records.", + "properties": { + "deleted": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "description": "IDs of successfully deleted memory records. Always returned; empty when no records were deleted." + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkOperationError" + }, + "description": "Errors for memory records that failed to be deleted." + } + }, + "required": [ + "deleted" + ] + }, + "BulkOperationError": { + "type": "object", + "description": "Error detail for a single item in a bulk operation.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record ID of the item that failed." + }, + "error": { + "type": "string", + "description": "Error message describing the failure." + } + }, + "required": [ + "error", + "id" + ] + }, + "ConflictErrorResponseContent": { + "type": "object", + "description": "The request conflicts with the current state of the resource.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 409, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/ConflictErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "ConflictErrorType": { + "type": "string", + "description": "Problem type URI for conflict errors.", + "enum": [ + "/errors/conflict" + ] + }, + "Content": { + "description": "A single content item in a message. Exactly one member is set.", + "oneOf": [ + { + "type": "object", + "title": "text", + "properties": { + "text": { + "type": "string", + "description": "Text content." + } + }, + "required": [ + "text" + ] + } + ] + }, + "CreateDocumentIngestionRequestContent": { + "type": "object", + "properties": { + "documentId": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "Customer-controlled logical document ID. Opaque and case-sensitive." + }, + "revision": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "Customer-controlled immutable revision token. Opaque and case-sensitive." + }, + "content": { + "type": "string", + "format": "byte" + }, + "mediaType": { + "$ref": "#/components/schemas/DocumentMediaType" + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + }, + "extractorId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Extractor identifier (1-64 chars, alphanumeric and dashes)." + }, + "idempotencyKey": { + "type": "string", + "maxLength": 128, + "minLength": 1, + "description": "Customer idempotency key for one document submission." + } + }, + "required": [ + "content", + "documentId", + "extractorId", + "idempotencyKey", + "mediaType", + "ownerId", + "revision" + ] + }, + "CreateDocumentIngestionResponseContent": { + "type": "object", + "properties": { + "ingestionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." + }, + "status": { + "$ref": "#/components/schemas/DocumentIngestionStatus" + } + }, + "required": [ + "ingestionId", + "status" + ] + }, + "CreateMemoryRecord": { + "type": "object", + "description": "A memory record to create.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Client-provided memory record ID for idempotent creation." + }, + "text": { + "type": "string", + "maxLength": 50000, + "minLength": 1, + "description": "The memory content (1-50000 chars)." + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Type of memory. Built-in or a custom type registered on the store." + }, + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session ID associated with this memory (1-64 chars, alphanumeric and dashes)." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner ID associated with this memory (1-64 chars, alphanumeric and dashes)." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Optional deprecated namespace label. It does not create or resolve a namespace resource. Do not supply it with namespaceRef. If both fields are omitted, the record is created without a namespace. Automatic routing does not place an un-namespaced record in a shared namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" + }, + "topics": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Topic name (1-100 chars)." + }, + "maxItems": 50, + "description": "Topic tags for categorization (max 50 topics, each 1-100 chars)." + }, + "attributes": { + "$ref": "#/components/schemas/AttributeMap" + } + }, + "required": [ + "id", + "ownerId", + "text" + ] + }, + "CreateNamespaceRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One namespace segment. Use with scope for a root or parentId for a child." + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "A complete hierarchy path to create. Missing segments are created as needed. Mutually exclusive with name and parentId. The path does not encode scope." + }, + "scope": { + "$ref": "#/components/schemas/NamespaceScope" + }, + "parentId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Parent namespace for a direct child." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner for a personal root or path. Required for personal scope; omit for shared roots and children." + } + } + }, + "CreateNamespaceResponseContent": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" + } + }, + "required": [ + "namespace" + ] + }, + "CreateSummaryViewRequestContent": { + "type": "object", + "properties": { + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + } + }, + "required": [ + "groupBy", + "name", + "output", + "refreshPolicy", + "source" + ] + }, + "CreateSummaryViewResponseContent": { + "type": "object", + "description": "An owned Summary View definition. Results are separate derived state and never enter long-term-memory search.", + "properties": { + "id": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Immutable canonical user ID assigned by the server. Clients cannot set ownership." + }, + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "createdAt", + "definitionVersion", + "filterOp", + "groupBy", + "id", + "name", + "output", + "ownerId", + "refreshPolicy", + "source", + "state", + "updatedAt", + "visibility" + ] + }, + "CreatedAtFilter": { + "type": "object", + "description": "Filter by creation timestamp.", + "properties": { + "gt": { + "type": "string", + "description": "After the given timestamp.", + "format": "date-time" + }, + "gte": { + "type": "string", + "description": "At or after the given timestamp.", + "format": "date-time" + }, + "lt": { + "type": "string", + "description": "Before the given timestamp.", + "format": "date-time" + }, + "lte": { + "type": "string", + "description": "At or before the given timestamp.", + "format": "date-time" + }, + "eq": { + "type": "string", + "description": "Exactly at the given timestamp.", + "format": "date-time" + } + } + }, + "DeleteSummaryViewResponseContent": { + "type": "object", + "properties": { + "viewId": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + } + }, + "required": [ + "state", + "viewId" + ] + }, + "DocumentIngestionCleanupState": { + "type": "string", + "enum": [ + "pending", + "deleted", + "failed" + ] + }, + "DocumentIngestionProcessingState": { + "type": "string", + "enum": [ + "queued", + "running", + "succeeded", + "failed", + "partial" + ] + }, + "DocumentIngestionRecord": { + "type": "object", + "properties": { + "ingestionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." + }, + "status": { + "$ref": "#/components/schemas/DocumentIngestionStatus" + } + }, + "required": [ + "ingestionId", + "status" + ] + }, + "DocumentIngestionStatus": { + "type": "object", + "properties": { + "processing": { + "$ref": "#/components/schemas/DocumentIngestionProcessingState" + }, + "cleanup": { + "$ref": "#/components/schemas/DocumentIngestionCleanupState" + }, + "extractorId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Extractor identifier (1-64 chars, alphanumeric and dashes)." + }, + "extractorRevision": { + "type": "integer", + "format": "int64" + }, + "completedChunks": { + "type": "integer", + "format": "int32" + }, + "totalChunks": { + "type": "integer", + "format": "int32" + }, + "createdCount": { + "type": "integer", + "format": "int32" + }, + "filteredCount": { + "type": "integer", + "format": "int32" + }, + "error": { + "type": "string" + }, + "retentionDeadline": { + "type": "number", + "format": "double" + }, + "deletedAt": { + "type": "number", + "format": "double" + } + }, + "required": [ + "cleanup", + "processing" + ] + }, + "DocumentMediaType": { + "type": "string", + "enum": [ + "text/plain", + "text/markdown" + ] + }, + "DocumentSourceIdentity": { + "type": "object", + "properties": { + "documentId": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "Customer-controlled logical document ID. Opaque and case-sensitive." + }, + "revision": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "Customer-controlled immutable revision token. Opaque and case-sensitive." + } + }, + "required": [ + "documentId", + "revision" + ] + }, + "EvidenceProducer": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 128, + "minLength": 1 + } + }, + "required": [ + "name" + ] + }, + "FailedDependencyErrorResponseContent": { + "type": "object", + "description": "A dependent resource required to process the request is unavailable or unhealthy.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 424, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/FailedDependencyErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "FailedDependencyErrorType": { + "type": "string", + "description": "Problem type URI for failed-dependency errors.", + "enum": [ + "/errors/resource-unavailable", + "/errors/database-out-of-memory" + ] + }, + "FieldError": { + "type": "object", + "description": "Validation error details for a single request field.", + "properties": { + "field": { + "type": "string", + "description": "Name of the invalid request field." + }, + "rule": { + "type": "string", + "description": "Validation rule that was violated." + }, + "message": { + "type": "string", + "description": "Human-readable validation error message." + } + }, + "required": [ + "field", + "message", + "rule" + ] + }, + "FilterConjunction": { + "type": "string", + "description": "Filter conjunction mode.", + "enum": [ + "all", + "any" + ] + }, + "ForbiddenErrorResponseContent": { + "type": "object", + "description": "The caller is authenticated but not allowed to access the requested resource.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 403, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/ForbiddenErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "ForbiddenErrorType": { + "type": "string", + "description": "Problem type URI for authorization errors.", + "enum": [ + "/errors/insufficient-permissions" + ] + }, + "GetDocumentIngestionResponseContent": { + "type": "object", + "properties": { + "ingestion": { + "$ref": "#/components/schemas/DocumentIngestionRecord" + } + }, + "required": [ + "ingestion" + ] + }, + "GetIngestionHookStatusResponseContent": { + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/IngestionHookStatus" + } + }, + "required": [ + "status" + ] + }, + "GetLongTermMemoryResponseContent": { + "type": "object", + "description": "A long-term memory record.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Unique memory record ID." + }, + "text": { + "type": "string", + "description": "The memory content." + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Type of memory. Built-in or a custom type registered on the store." + }, + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session ID associated with this memory." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner ID associated with this memory." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" + }, + "topics": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Topic name (1-100 chars)." + }, + "maxItems": 50, + "description": "Topic tags for categorization." + }, + "attributes": { + "$ref": "#/components/schemas/AttributeMap" + }, + "createdAt": { + "type": "string", + "description": "Timestamp when the memory was created (UTC).", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "description": "Timestamp when the memory was last updated (UTC).", + "format": "date-time" + }, + "sources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemorySourceLink" + }, + "description": "Generic source links for this memory. Document sources include the customer document ID and revision that produced the memory." + }, + "sourcesNextPageToken": { + "type": "string", + "description": "Opaque token for additional source links when this response truncated attribution. Absent when every link is included." + }, + "documentManaged": { + "type": "boolean", + "description": "True when this memory was created by document ingestion and is protected from generic overwrite." + } + }, + "required": [ + "createdAt", + "id", + "text", + "updatedAt" + ] + }, + "GetNamespaceResponseContent": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" + } + }, + "required": [ + "namespace" + ] + }, + "GetNamespaceRoutingResponseContent": { + "type": "object", + "properties": { + "policy": { + "$ref": "#/components/schemas/NamespaceRoutingPolicy" + } + }, + "required": [ + "policy" + ] + }, + "GetSessionEventResponseContent": { + "type": "object", + "description": "Response containing a single requested session event.", + "properties": { + "event": { + "$ref": "#/components/schemas/SessionEvent" + } + }, + "required": [ + "event" + ] + }, + "GetSessionMemoryResponseContent": { + "type": "object", + "description": "Session memory state for a session.", + "properties": { + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The owner of the session, set from the actorId of the first event." }, "namespace": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional namespace for the session, used as the namespace of every long-term memory extracted from it.\n\nLike `ownerId`, this is a session-level value taken from the first event: the value sent with the first event of a session is stored on the session, and values sent with later events are ignored. Omit it to leave extracted memories without a namespace." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated namespace label for the session, set from the first event. Absent when the session uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" + }, + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionEvent" + }, + "description": "Conversation events retained for this session. When summarization is enabled, events already covered by the summary are excluded unless `includeSummarisedEvents` is set; the compacted summary is returned separately in `summary`." + }, + "summary": { + "$ref": "#/components/schemas/SessionSummary" + } + }, + "required": [ + "events", + "ownerId", + "sessionId" + ] + }, + "GetSummaryViewResponseContent": { + "type": "object", + "description": "An owned Summary View definition. Results are separate derived state and never enter long-term-memory search.", + "properties": { + "id": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Immutable canonical user ID assigned by the server. Clients cannot set ownership." + }, + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "createdAt", + "definitionVersion", + "filterOp", + "groupBy", + "id", + "name", + "output", + "ownerId", + "refreshPolicy", + "source", + "state", + "updatedAt", + "visibility" + ] + }, + "GetSummaryViewResultResponseContent": { + "type": "object", + "properties": { + "viewId": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "scope": { + "$ref": "#/components/schemas/SummaryViewScope" + }, + "status": { + "$ref": "#/components/schemas/SummaryViewResultStatus" + }, + "stale": { + "type": "boolean" + }, + "format": { + "$ref": "#/components/schemas/SummaryViewOutputFormat" + }, + "value": { + "$ref": "#/components/schemas/SummaryViewResultValue" + }, + "sourceMemoryCount": { + "type": "integer", + "minimum": 0, + "description": "Number of matching source memories. When selection stops early, a separate count query measures the matching total without reading the remaining source pages. Concurrent writes can change the source set between queries; the count is never less than the number of records already observed.", + "format": "int64" + }, + "summarizedMemoryCount": { + "type": "integer", + "minimum": 0, + "description": "Number of matching source memories included in the generated value after bounded deterministic selection.", + "format": "int64" + }, + "truncated": { + "type": "boolean", + "description": "Combined signal for sourcesOmitted or sourceContentTruncated." + }, + "sourcesOmitted": { + "type": "boolean", + "description": "True when matching source memories were excluded from generation, including when further source pages were not read after reaching an input budget." + }, + "sourceContentTruncated": { + "type": "boolean", + "description": "True when content from an included source memory was clipped to fit a source or generation input budget. This can be true even when sourceMemoryCount equals summarizedMemoryCount." + }, + "sourceMemoryIds": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "description": "The memory IDs selected for the materialized value. Included only when requested with includeSourceMemoryIds=true." + }, + "sourceUpdatedAt": { + "type": "string", + "format": "date-time" + }, + "computedAt": { + "type": "string", + "format": "date-time" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "errorCode": { + "$ref": "#/components/schemas/SummaryViewResultErrorCode" + } + }, + "required": [ + "definitionVersion", + "format", + "scope", + "sourceContentTruncated", + "sourceMemoryCount", + "sourcesOmitted", + "stale", + "status", + "summarizedMemoryCount", + "truncated", + "viewId" + ] + }, + "HookStatusSource": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "IngestionHookRetryErrorResponseContent": { + "type": "object", + "description": "A saved ingestion hook decision is waiting for its next bounded attempt.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." }, - "role": { - "$ref": "#/components/schemas/MessageRole" + "status": { + "type": "integer", + "default": 503, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/IngestionHookRetryErrorType" + }, + "retryAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "retryAt", + "status", + "title", + "type" + ] + }, + "IngestionHookRetryErrorType": { + "type": "string", + "enum": [ + "/errors/ingestion-hook-retry" + ] + }, + "IngestionHookStatus": { + "type": "object", + "description": "Compact hook metadata, retained for seven days after completion or expiry. Application state is independent of the decision state; earlier writes may have completed before a later failure.", + "properties": { + "eventId": { + "type": "string" + }, + "ingestionOperationId": { + "type": "string" + }, + "phase": { + "type": "string" + }, + "path": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/HookStatusSource" + }, + "configRevision": { + "type": "integer", + "format": "int64" + }, + "state": { + "type": "string" + }, + "applyState": { + "type": "string" + }, + "attempts": { + "type": "integer", + "format": "int32" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "deadline": { + "type": "string", + "format": "date-time" + }, + "nextAttemptAt": { + "type": "string", + "format": "date-time" + }, + "lastError": { + "type": "string" + }, + "applied": { + "type": "integer", + "format": "int32" + }, + "filtered": { + "type": "integer", + "format": "int32" + }, + "conflicts": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "applied", + "applyState", + "attempts", + "configRevision", + "conflicts", + "createdAt", + "deadline", + "eventId", + "filtered", + "ingestionOperationId", + "ownerId", + "path", + "phase", + "source", + "state", + "updatedAt" + ] + }, + "IngestionHookStoppedErrorResponseContent": { + "type": "object", + "description": "The ingestion hook stopped this operation without applying unapproved content.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 422, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/IngestionHookStoppedErrorType" + }, + "state": { + "$ref": "#/components/schemas/IngestionHookStoppedState" + } + }, + "required": [ + "state", + "status", + "title", + "type" + ] + }, + "IngestionHookStoppedErrorType": { + "type": "string", + "enum": [ + "/errors/ingestion-hook-stopped" + ] + }, + "IngestionHookStoppedState": { + "type": "string", + "enum": [ + "rejected", + "failed", + "expired", + "cancelled", + "conflict" + ] + }, + "ListDocumentIngestionOutputsResponseContent": { + "type": "object", + "properties": { + "memoryIds": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + } + } + }, + "required": [ + "memoryIds" + ] + }, + "ListIngestionHookStatusesResponseContent": { + "type": "object", + "properties": { + "statuses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IngestionHookStatus" + } + }, + "nextCursor": { + "type": "string" + } + }, + "required": [ + "statuses" + ] + }, + "ListMemorySourceEvidenceResponseContent": { + "type": "object", + "properties": { + "source": { + "$ref": "#/components/schemas/MemorySourceLink" + } + }, + "required": [ + "source" + ] + }, + "ListMemorySourceLinksResponseContent": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemorySourceLink" + } + }, + "nextPageToken": { + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "ListNamespacesResponseContent": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Namespace" + } + }, + "nextPageToken": { + "type": "string", + "description": "Opaque token for fetching the next page. Omitted when no more results." + } + }, + "required": [ + "items" + ] + }, + "ListSessionSummariesResponseContent": { + "type": "object", + "description": "Paginated session rows for a store.", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionListItem" + } + }, + "total": { + "type": "integer", + "format": "int32" + }, + "nextPageToken": { + "type": "string" + } + }, + "required": [ + "items", + "total" + ] + }, + "ListSessionsResponseContent": { + "type": "object", + "description": "Paginated list of session IDs for a store.", + "properties": { + "items": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + }, + "description": "List of session IDs. Always returned; empty when the store has no sessions." + }, + "total": { + "type": "integer", + "description": "Total number of matching sessions.", + "format": "int32" }, - "content": { + "nextPageToken": { + "type": "string", + "description": "Opaque token for fetching the next page. Omitted when no more results." + } + }, + "required": [ + "items", + "total" + ] + }, + "ListSourceMemoriesResponseContent": { + "type": "object", + "properties": { + "items": { "type": "array", "items": { - "$ref": "#/components/schemas/Content" - }, - "description": "The message content as an array of typed content parts." + "$ref": "#/components/schemas/MemoryRecord" + } }, - "createdAt": { - "type": "string", - "description": "Client-supplied timestamp for when the event happened (UTC).", - "format": "date-time" + "nextPageToken": { + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "ListSummaryViewsResponseContent": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryView" + } }, - "metadata": { - "description": "Optional metadata as any valid JSON value." + "nextPageToken": { + "type": "string" } }, "required": [ - "actorId", - "content", - "createdAt", - "role" + "items" ] }, - "AddSessionEventResponseContent": { + "LlmPromptSpec": { "type": "object", - "description": "Response returned after appending a session event.", + "description": "Customer-authored prompt evaluated by the LLM routing strategy.", "properties": { - "event": { - "$ref": "#/components/schemas/SessionEvent" + "prompt": { + "type": "string", + "maxLength": 2000, + "minLength": 1 } }, "required": [ - "event" + "prompt" ] }, - "AttributeFilterClause": { - "description": "Filter clause for a single record attribute. Exactly one member is set; the member must match the attribute's declared field type.", - "oneOf": [ - { - "type": "object", - "title": "string", - "properties": { - "string": { - "type": "string", - "description": "Match a string attribute equal to this value." - } - }, - "required": [ - "string" - ] + "LongTermMemoryFilter": { + "type": "object", + "description": "Grouping of filter conditions for long-term memory search.", + "properties": { + "sessionId": { + "$ref": "#/components/schemas/SessionIdFilter" }, - { - "type": "object", - "title": "number", - "properties": { - "number": { - "type": "number", - "description": "Match a numeric attribute equal to this value.", - "format": "double" - } - }, - "required": [ - "number" - ] + "ownerId": { + "$ref": "#/components/schemas/OwnerIdFilter" }, - { - "type": "object", - "title": "boolean", - "properties": { - "boolean": { - "type": "boolean", - "description": "Match a boolean attribute equal to this value." - } - }, - "required": [ - "boolean" - ] + "namespace": { + "$ref": "#/components/schemas/NamespaceFilter" }, - { - "type": "object", - "title": "list", - "properties": { - "list": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Match a list attribute that contains all of these values." - } - }, - "required": [ - "list" - ] + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefFilter" + }, + "unnamespaced": { + "type": "boolean", + "description": "When true, matches only records that have neither a deprecated namespace label nor a namespace resource placement. This scope is always combined with every other filter condition. Do not supply namespace or namespaceRef with this field." + }, + "topics": { + "$ref": "#/components/schemas/TopicsFilter" + }, + "memoryType": { + "$ref": "#/components/schemas/MemoryTypeFilter" + }, + "createdAt": { + "$ref": "#/components/schemas/CreatedAtFilter" + }, + "attributes": { + "$ref": "#/components/schemas/AttributeFilterMap" } + } + }, + "LongTermMemorySearchSortBy": { + "type": "string", + "description": "Timestamp field used to order long-term memory search results.", + "enum": [ + "createdAt", + "updatedAt" ] }, - "AttributeFilterMap": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/AttributeFilterClause" - }, - "description": "Per-attribute filter clauses keyed by the custom memory type's field name." + "LongTermMemorySearchSortOrder": { + "type": "string", + "description": "Direction used to order long-term memory search results.", + "enum": [ + "asc", + "desc" + ] }, - "AttributeMap": { + "MatcherKind": { + "type": "string", + "description": "The matching techniques this API supports.", + "enum": [ + "regex" + ] + }, + "MatcherSpec": { "type": "object", - "additionalProperties": {}, - "description": "Structured record attributes: a map from the custom memory type's field name to that field's value. Values are arbitrary JSON matching the field's declared type (str, int, float, bool, list[str], list[float], or object)." + "description": "How a detector matches, as a tagged union of techniques.\n\nExactly the block naming the declared kind must be present; a block belonging to\nany other kind is rejected rather than guessed at. Further kinds are additive.", + "properties": { + "kind": { + "$ref": "#/components/schemas/MatcherKind" + }, + "regex": { + "$ref": "#/components/schemas/RegexSpec" + } + }, + "required": [ + "kind" + ] }, - "AuthenticationErrorResponseContent": { + "MemoryFailedDependencyErrorResponseContent": { "type": "object", - "description": "Authentication credentials are missing, malformed, or invalid.", + "description": "A resource or model-provider dependency required by Agent Memory could not complete the request.", "properties": { "title": { "type": "string", @@ -2208,7 +7560,7 @@ }, "status": { "type": "integer", - "default": 401, + "default": 424, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -2221,7 +7573,7 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/AuthenticationErrorType" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorType" } }, "required": [ @@ -2230,107 +7582,239 @@ "type" ] }, - "AuthenticationErrorType": { + "MemoryFailedDependencyErrorType": { "type": "string", - "description": "Problem type URI for authentication errors.", + "description": "Problem type URI for Agent Memory failed-dependency errors.", "enum": [ - "/errors/authentication-failed" + "/errors/resource-unavailable", + "/errors/database-out-of-memory", + "/errors/invalid-model-credentials", + "/errors/model-quota-exceeded" + ] + }, + "MemoryRecord": { + "type": "object", + "description": "A long-term memory record.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Unique memory record ID." + }, + "text": { + "type": "string", + "description": "The memory content." + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Type of memory. Built-in or a custom type registered on the store." + }, + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session ID associated with this memory." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner ID associated with this memory." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" + }, + "topics": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Topic name (1-100 chars)." + }, + "maxItems": 50, + "description": "Topic tags for categorization." + }, + "attributes": { + "$ref": "#/components/schemas/AttributeMap" + }, + "createdAt": { + "type": "string", + "description": "Timestamp when the memory was created (UTC).", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "description": "Timestamp when the memory was last updated (UTC).", + "format": "date-time" + }, + "sources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemorySourceLink" + }, + "description": "Generic source links for this memory. Document sources include the customer document ID and revision that produced the memory." + }, + "sourcesNextPageToken": { + "type": "string", + "description": "Opaque token for additional source links when this response truncated attribution. Absent when every link is included." + }, + "documentManaged": { + "type": "boolean", + "description": "True when this memory was created by document ingestion and is protected from generic overwrite." + } + }, + "required": [ + "createdAt", + "id", + "text", + "updatedAt" ] }, - "BadRequestErrorResponseContent": { + "MemorySourceEvidence": { "type": "object", - "description": "Request validation or input decoding failed.", "properties": { - "title": { + "evidenceId": { "type": "string", - "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + "maxLength": 128, + "minLength": 1, + "description": "Identity of a logical extraction occurrence. Delivery retries retain this ID; a new extraction receives a new ID. Absent on legacy evidence." }, - "status": { + "schemaVersion": { "type": "integer", - "default": 400, - "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "description": "Version of the evidence metadata contract, currently 1. Absent on legacy evidence.", "format": "int32" }, - "detail": { + "capturedAt": { "type": "string", - "description": "A human-readable explanation specific to this occurrence of the problem." + "description": "UTC time when the producer prepared the input for extraction, before model invocation. Preserved through checkpoint replay. This is not the source event time.", + "format": "date-time" }, - "instance": { + "producer": { + "$ref": "#/components/schemas/EvidenceProducer" + }, + "ingestionId": { "type": "string", - "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." }, - "type": { - "$ref": "#/components/schemas/BadRequestErrorType" + "chunkId": { + "type": "string" }, - "fields": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FieldError" - }, - "description": "Optional field-level validation errors." + "start": { + "type": "integer", + "format": "int32" + }, + "end": { + "type": "integer", + "format": "int32" } }, "required": [ - "status", - "title", - "type" + "chunkId", + "ingestionId" ] }, - "BadRequestErrorType": { + "MemorySourceKind": { "type": "string", - "description": "Problem type URI for bad request errors.", "enum": [ - "/errors/invalid-data" + "document", + "session" ] }, - "BulkCreateLongTermMemoriesRequestContent": { + "MemorySourceLink": { "type": "object", - "description": "Bulk create request for long-term memory records.", "properties": { - "memories": { + "sourceId": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/MemorySourceKind" + }, + "identity": { + "$ref": "#/components/schemas/DocumentSourceIdentity" + }, + "session": { + "$ref": "#/components/schemas/SessionSourceIdentity" + }, + "evidence": { "type": "array", "items": { - "$ref": "#/components/schemas/CreateMemoryRecord" - }, - "maxItems": 100, - "minItems": 1, - "description": "List of memory records to create." + "$ref": "#/components/schemas/MemorySourceEvidence" + } + }, + "sessionEvidence": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionSourceEvidence" + } + }, + "evidenceNextPageToken": { + "type": "string", + "description": "Opaque token for GET /v1/stores/{storeId}/long-term-memory/{memoryId}/sources/{sourceId}/evidence. Continues evidence or sessionEvidence for this source; absent when every item is included." } }, "required": [ - "memories" + "kind", + "sourceId" ] }, - "BulkCreateLongTermMemoriesResponseContent": { + "MemoryTypeFilter": { "type": "object", - "description": "Bulk create result for long-term memory records.", + "description": "Filter by memory type. Matching is case-insensitive.", "properties": { - "created": { + "eq": { + "type": "string", + "description": "Equals the given value." + }, + "ne": { + "type": "string", + "description": "Not equal to the given value." + }, + "in": { "type": "array", "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + "type": "string" }, - "description": "IDs of successfully created memory records. Always returned; empty when no records were created." + "description": "Matches any of the given values." }, - "errors": { + "all": { "type": "array", "items": { - "$ref": "#/components/schemas/BulkOperationError" + "type": "string" }, - "description": "Errors for memory records that failed to be created." + "description": "Matches all of the given values." } - }, - "required": [ - "created" + } + }, + "MessageRole": { + "type": "string", + "description": "Message role for session events.", + "enum": [ + "USER", + "ASSISTANT", + "SYSTEM" ] }, - "BulkDeleteLongTermMemoriesRequestContent": { + "MoveLongTermMemoriesRequestContent": { "type": "object", - "description": "Bulk delete request for long-term memory records.", "properties": { "memoryIds": { "type": "array", @@ -2342,19 +7826,20 @@ "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." }, "maxItems": 100, - "minItems": 1, - "description": "List of memory record IDs to delete (1-100 IDs)." + "minItems": 1 + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" } }, "required": [ "memoryIds" ] }, - "BulkDeleteLongTermMemoriesResponseContent": { + "MoveLongTermMemoriesResponseContent": { "type": "object", - "description": "Bulk delete result for long-term memory records.", "properties": { - "deleted": { + "moved": { "type": "array", "items": { "type": "string", @@ -2362,160 +7847,314 @@ "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." - }, - "description": "IDs of successfully deleted memory records. Always returned; empty when no records were deleted." + } }, "errors": { "type": "array", "items": { "$ref": "#/components/schemas/BulkOperationError" - }, - "description": "Errors for memory records that failed to be deleted." + } } }, "required": [ - "deleted" + "moved" ] }, - "BulkOperationError": { + "MutableSummaryViewState": { + "type": "string", + "description": "States a caller may set directly. Deleting is entered only by the delete operation.", + "enum": [ + "active", + "disabled" + ] + }, + "Namespace": { "type": "object", - "description": "Error detail for a single item in a bulk operation.", + "description": "A namespace hierarchy record.", "properties": { - "id": { + "namespaceId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Memory record ID of the item that failed." + "description": "Server-generated namespace identifier." }, - "error": { + "name": { "type": "string", - "description": "Error message describing the failure." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One case-sensitive namespace path segment." + }, + "parentId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "rootId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "ownerId": { + "type": "string", + "minLength": 1, + "description": "An opaque application-user ID selected by the platform for a namespace root." + }, + "scope": { + "$ref": "#/components/schemas/NamespaceScope" + }, + "state": { + "$ref": "#/components/schemas/NamespaceState" + }, + "revision": { + "type": "integer", + "format": "int64" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "Current case-sensitive path, derived from the live hierarchy." } }, "required": [ - "error", - "id" - ] - }, - "Content": { - "description": "A single content item in a message. Exactly one member is set.", - "oneOf": [ - { - "type": "object", - "title": "text", - "properties": { - "text": { - "type": "string", - "description": "Text content." - } - }, - "required": [ - "text" - ] - } + "createdAt", + "name", + "namespaceId", + "path", + "revision", + "rootId", + "scope", + "state", + "updatedAt" ] }, - "CreateMemoryRecord": { + "NamespaceFilter": { "type": "object", - "description": "A memory record to create.", + "description": "Filter by namespace. Matching is case-insensitive.\n\nNamespace is optional, so records without one need care. The positive operators (`eq`, `in`, `all`) require the field to be present and therefore never match a record that has no namespace \u2014 including memories extracted from a session that has none. `ne` is a plain negation and *does* match those records, so use `eq` or `in` when you need strict namespace isolation rather than `ne` against the namespaces you want to exclude.\n\nOmit this filter to search across every namespace, and use it to keep recall inside one project, workspace, team, environment, or tenant.", "properties": { - "id": { + "eq": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Client-provided memory record ID for idempotent creation." + "description": "Equals the given value.", + "deprecated": true }, - "text": { - "type": "string", - "maxLength": 50000, - "minLength": 1, - "description": "The memory content (1-50000 chars)." - }, - "memoryType": { + "ne": { "type": "string", "maxLength": 64, "minLength": 1, - "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Type of memory. Built-in or a custom type registered on the store." + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Not equal to the given value.", + "deprecated": true }, - "sessionId": { + "in": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true + }, + "description": "Matches any of the given values." + }, + "all": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true + }, + "description": "Matches all of the given values." + } + } + }, + "NamespaceRef": { + "type": "object", + "description": "Reference to a namespace resource with its stable identifier and current display data.", + "properties": { + "namespaceId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session ID associated with this memory (1-64 chars, alphanumeric and dashes)." + "description": "Stable namespace resource identifier." }, - "ownerId": { + "name": { "type": "string", "maxLength": 64, "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner ID associated with this memory (1-64 chars, alphanumeric and dashes)." + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Current namespace name." }, - "namespace": { + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "Current namespace path." + } + }, + "required": [ + "name", + "namespaceId", + "path" + ] + }, + "NamespaceRefFilter": { + "type": "object", + "description": "Filter by stable namespace resource identifier. Supports exact or any-of matching and matches only resource-backed records, never deprecated namespace labels.", + "properties": { + "eq": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional namespace for logical grouping (1-64 chars, alphanumeric and dashes). Omit to create the record without a namespace. Records created here are explicit writes, so this value is taken as given; extraction instead inherits the source session's namespace." + "description": "Equals the given value." }, - "topics": { + "in": { "type": "array", "items": { "type": "string", - "maxLength": 100, + "maxLength": 64, "minLength": 1, - "description": "Topic name (1-100 chars)." + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." }, - "maxItems": 50, - "description": "Topic tags for categorization (max 50 topics, each 1-100 chars)." + "description": "Matches any of the given values." + } + } + }, + "NamespaceRefInput": { + "type": "object", + "description": "Reference to an existing namespace resource for a write or namespace-scoped operation.", + "properties": { + "namespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable namespace resource identifier." + } + }, + "required": [ + "namespaceId" + ] + }, + "NamespaceRoutingPolicy": { + "type": "object", + "description": "Store-level automatic namespace routing and admission policy. Global rules target shared namespaces only. Accept rules select candidates in list order. All enabled reject rules for a destination apply regardless of their position or how the destination was selected.", + "properties": { + "enabled": { + "type": "boolean" }, - "attributes": { - "$ref": "#/components/schemas/AttributeMap" + "rules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NamespaceRoutingRule" + }, + "maxItems": 32 } }, "required": [ - "id", - "ownerId", - "text" + "enabled" ] }, - "CreatedAtFilter": { + "NamespaceRoutingRule": { "type": "object", - "description": "Filter by creation timestamp.", + "description": "One routing or admission rule. action is accept or reject. Strategies run in list order and the first match fires the rule. An accept with no strategies always matches; a reject with no strategies never matches. A matching accept selects a candidate. Every enabled reject for that destination must pass before placement. A veto skips that candidate during automatic routing and fails explicit placement.", "properties": { - "gt": { + "id": { "type": "string", - "description": "After the given timestamp.", - "format": "date-time" + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Routing rule identifier (1-64 chars). Published in logs, so it is an identifier rather than free text." }, - "gte": { - "type": "string", - "description": "At or after the given timestamp.", - "format": "date-time" + "destination": { + "$ref": "#/components/schemas/NamespaceRefInput" }, - "lt": { - "type": "string", - "description": "Before the given timestamp.", - "format": "date-time" + "enabled": { + "type": "boolean" }, - "lte": { - "type": "string", - "description": "At or before the given timestamp.", - "format": "date-time" + "action": { + "$ref": "#/components/schemas/RoutingRuleAction" }, - "eq": { - "type": "string", - "description": "Exactly at the given timestamp.", - "format": "date-time" + "strategies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RouteCondition" + }, + "maxItems": 8 } - } + }, + "required": [ + "action", + "destination", + "enabled", + "id" + ] }, - "FailedDependencyErrorResponseContent": { + "NamespaceScope": { + "type": "string", + "description": "The root scope of a namespace hierarchy.", + "enum": [ + "PERSONAL", + "SHARED" + ] + }, + "NamespaceSortBy": { + "type": "string", + "enum": [ + "id", + "name", + "createdAt", + "updatedAt" + ] + }, + "NamespaceSortOrder": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "NamespaceState": { + "type": "string", + "description": "Whether a namespace accepts new children and placements.", + "enum": [ + "ACTIVE", + "ARCHIVED" + ] + }, + "NamespaceUpdateState": { + "type": "string", + "description": "The archive-only state transition accepted by UpdateNamespace.", + "enum": [ + "ARCHIVED" + ] + }, + "NotFoundErrorResponseContent": { "type": "object", - "description": "A dependent resource required to process the request is unavailable or unhealthy.", + "description": "The requested resource does not exist.", "properties": { "title": { "type": "string", @@ -2523,7 +8162,7 @@ }, "status": { "type": "integer", - "default": 424, + "default": 404, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -2536,7 +8175,7 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/FailedDependencyErrorType" + "$ref": "#/components/schemas/NotFoundErrorType" } }, "required": [ @@ -2545,48 +8184,58 @@ "type" ] }, - "FailedDependencyErrorType": { + "NotFoundErrorType": { "type": "string", - "description": "Problem type URI for failed-dependency errors.", + "description": "Problem type URI for not-found errors.", "enum": [ - "/errors/resource-unavailable", - "/errors/database-out-of-memory" + "/errors/resource-not-found" ] }, - "FieldError": { + "OwnerIdFilter": { "type": "object", - "description": "Validation error details for a single request field.", + "description": "Filter by the long-term memory ownerId field. Matching is case-sensitive.", "properties": { - "field": { + "eq": { "type": "string", - "description": "Name of the invalid request field." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Equals the given value." }, - "rule": { + "ne": { "type": "string", - "description": "Validation rule that was violated." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Not equal to the given value." }, - "message": { - "type": "string", - "description": "Human-readable validation error message." + "in": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + }, + "description": "Matches any of the given values." + }, + "all": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + }, + "description": "Matches all of the given values." } - }, - "required": [ - "field", - "message", - "rule" - ] - }, - "FilterConjunction": { - "type": "string", - "description": "Filter conjunction mode.", - "enum": [ - "all", - "any" - ] + } }, - "ForbiddenErrorResponseContent": { + "PayloadTooLargeErrorResponseContent": { "type": "object", - "description": "The caller is authenticated but not allowed to access the requested resource.", + "description": "The request payload exceeds the maximum supported size.", "properties": { "title": { "type": "string", @@ -2594,7 +8243,7 @@ }, "status": { "type": "integer", - "default": 403, + "default": 413, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -2607,7 +8256,7 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/ForbiddenErrorType" + "$ref": "#/components/schemas/PayloadTooLargeErrorType" } }, "required": [ @@ -2616,180 +8265,215 @@ "type" ] }, - "ForbiddenErrorType": { + "PayloadTooLargeErrorType": { "type": "string", - "description": "Problem type URI for authorization errors.", + "description": "Problem type URI for payload-too-large errors.", "enum": [ - "/errors/insufficient-permissions" + "/errors/payload-too-large" ] }, - "GetLongTermMemoryResponseContent": { + "PutNamespaceRoutingRequestContent": { "type": "object", - "description": "A long-term memory record.", "properties": { - "id": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique memory record ID." - }, - "text": { - "type": "string", - "description": "The memory content." - }, - "memoryType": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Type of memory. Built-in or a custom type registered on the store." - }, - "sessionId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session ID associated with this memory." - }, - "ownerId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner ID associated with this memory." + "policy": { + "$ref": "#/components/schemas/NamespaceRoutingPolicy" + } + }, + "required": [ + "policy" + ] + }, + "PutNamespaceRoutingResponseContent": { + "type": "object", + "properties": { + "policy": { + "$ref": "#/components/schemas/NamespaceRoutingPolicy" + } + }, + "required": [ + "policy" + ] + }, + "RefreshSummaryViewRequestContent": { + "type": "object", + "properties": { + "target": { + "$ref": "#/components/schemas/SummaryViewRefreshTarget" }, - "namespace": { + "scope": { + "$ref": "#/components/schemas/SummaryViewScope" + } + }, + "required": [ + "target" + ] + }, + "RefreshSummaryViewResponseContent": { + "type": "object", + "properties": { + "viewId": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping. Absent when the record has none: either it was written directly without one, or it was extracted from a session that has no namespace." - }, - "topics": { - "type": "array", - "items": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "description": "Topic name (1-100 chars)." - }, - "maxItems": 50, - "description": "Topic tags for categorization." - }, - "attributes": { - "$ref": "#/components/schemas/AttributeMap" + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." }, - "createdAt": { - "type": "string", - "description": "Timestamp when the memory was created (UTC).", - "format": "date-time" + "target": { + "$ref": "#/components/schemas/SummaryViewRefreshTarget" }, - "updatedAt": { - "type": "string", - "description": "Timestamp when the memory was last updated (UTC).", - "format": "date-time" + "accepted": { + "type": "boolean" } }, "required": [ - "createdAt", - "id", - "text", - "updatedAt" + "accepted", + "target", + "viewId" ] }, - "GetSessionEventResponseContent": { + "RegexSpec": { "type": "object", - "description": "Response containing a single requested session event.", + "description": "A regular expression to match against.\n\nEvaluated by RE2, which runs in linear time \u2014 so an expensive pattern is not a\ndenial-of-service risk, but lookaround and backreferences are unavailable.\nRejected on write if it does not compile, or if it can match without consuming\ntext: such a pattern matches at every position, so it would exclude every memory\nthe store writes.", "properties": { - "event": { - "$ref": "#/components/schemas/SessionEvent" + "pattern": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "The pattern, in RE2 syntax." } }, "required": [ - "event" + "pattern" ] }, - "GetSessionMemoryResponseContent": { + "ResourceSuspendedErrorResponseContent": { "type": "object", - "description": "Session memory state for a session.", + "description": "The requested resource exists but is suspended by an administrator.", "properties": { - "sessionId": { + "title": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." }, - "ownerId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The owner of the session, set from the actorId of the first event." + "status": { + "type": "integer", + "default": 423, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" }, - "namespace": { + "detail": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The namespace of the session, set from the namespace of the first event. Absent when the first event did not supply one; long-term memories extracted from this session then carry no namespace." + "description": "A human-readable explanation specific to this occurrence of the problem." }, - "events": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SessionEvent" - }, - "description": "Conversation events retained for this session. When summarization is enabled, events already covered by the summary are excluded unless `includeSummarisedEvents` is set; the compacted summary is returned separately in `summary`." + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, - "summary": { - "$ref": "#/components/schemas/SessionSummary" + "type": { + "$ref": "#/components/schemas/ResourceSuspendedErrorType" } }, "required": [ - "events", - "ownerId", - "sessionId" + "status", + "title", + "type" + ] + }, + "ResourceSuspendedErrorType": { + "type": "string", + "description": "Problem type URI for suspended resources.", + "enum": [ + "/errors/resource-suspended" ] }, - "HealthResponseContent": { + "RouteCondition": { "type": "object", - "description": "Health check response.", + "description": "One plug-in strategy invocation. Exactly the block naming the declared kind must be present. List order is evaluation order.", "properties": { - "status": { - "$ref": "#/components/schemas/HealthStatus" + "kind": { + "$ref": "#/components/schemas/RouteStrategyKind" + }, + "llmPrompt": { + "$ref": "#/components/schemas/LlmPromptSpec" + }, + "matcher": { + "$ref": "#/components/schemas/MatcherSpec" } }, "required": [ - "status" + "kind" ] }, - "HealthStatus": { + "RouteStrategyKind": { "type": "string", - "description": "Health status returned by the /health endpoint.", + "description": "Routing strategy kinds. Unknown kinds are rejected. New kinds register on the evaluator without changing namespace records.", "enum": [ - "healthy" + "llm_prompt", + "matcher_spec" ] }, - "ListSessionsResponseContent": { + "RoutingRuleAction": { + "type": "string", + "description": "Whether the rule accepts into the destination or rejects that destination.", + "enum": [ + "accept", + "reject" + ] + }, + "SearchLongTermMemoryRequestContent": { "type": "object", - "description": "Paginated list of session IDs for a store.", + "description": "Semantic search request for long-term memory records.", + "properties": { + "text": { + "type": "string", + "description": "Text to use for semantic search." + }, + "similarityThreshold": { + "type": "number", + "maximum": 1, + "minimum": 0, + "description": "The minimum similarity threshold for vector search results (normalized cosine similarity). Allowed range: 0-1.", + "format": "float" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "excludeDocumentManaged": { + "type": "boolean", + "description": "When true, omit document-managed memories from results. Automatic session reconciliation uses this; public search omits it so document memories remain visible." + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "sortBy": { + "$ref": "#/components/schemas/LongTermMemorySearchSortBy" + }, + "sortOrder": { + "$ref": "#/components/schemas/LongTermMemorySearchSortOrder" + }, + "limit": { + "type": "integer", + "default": 10, + "maximum": 100, + "minimum": 1, + "description": "Maximum number of results to return. Defaults to 10. Allowed range: 1-100.", + "format": "int32" + }, + "pageToken": { + "type": "string", + "description": "Opaque token from a previous response for the next page." + } + } + }, + "SearchLongTermMemoryResponseContent": { + "type": "object", + "description": "Semantic search result for long-term memory records.", "properties": { "items": { "type": "array", "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + "$ref": "#/components/schemas/MemoryRecord" }, - "description": "List of session IDs. Always returned; empty when the store has no sessions." - }, - "total": { - "type": "integer", - "description": "Total number of matching sessions.", - "format": "int32" + "description": "List of matching memory records. Always returned; empty when no records match." }, "nextPageToken": { "type": "string", @@ -2797,275 +8481,405 @@ } }, "required": [ - "items", - "total" + "items" ] }, - "LongTermMemoryFilter": { + "SessionEvent": { "type": "object", - "description": "Grouping of filter conditions for long-term memory search.", + "description": "A single event in the session history.", "properties": { - "sessionId": { - "$ref": "#/components/schemas/SessionIdFilter" + "eventId": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Auto-generated event ID." }, - "ownerId": { - "$ref": "#/components/schemas/OwnerIdFilter" + "actorId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant." }, - "namespace": { - "$ref": "#/components/schemas/NamespaceFilter" + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session identifier (client-provided or server-generated)." }, - "topics": { - "$ref": "#/components/schemas/TopicsFilter" + "role": { + "$ref": "#/components/schemas/MessageRole" }, - "memoryType": { - "$ref": "#/components/schemas/MemoryTypeFilter" + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Content" + }, + "description": "The message content as an array of typed content parts." }, "createdAt": { - "$ref": "#/components/schemas/CreatedAtFilter" + "type": "string", + "description": "Client-supplied timestamp for when the event happened (UTC).", + "format": "date-time" }, - "attributes": { - "$ref": "#/components/schemas/AttributeFilterMap" + "systemTimestamp": { + "type": "string", + "description": "Server-set timestamp for when the dataplane ingested the event (UTC).", + "format": "date-time" + }, + "metadata": { + "description": "Optional metadata as any valid JSON value." } - } + }, + "required": [ + "actorId", + "content", + "createdAt", + "eventId", + "role", + "sessionId", + "systemTimestamp" + ] }, - "MemoryRecord": { + "SessionIdFilter": { "type": "object", - "description": "A long-term memory record.", + "description": "Filter by the long-term memory sessionId field. Matching is case-sensitive.", "properties": { - "id": { + "eq": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique memory record ID." - }, - "text": { - "type": "string", - "description": "The memory content." - }, - "memoryType": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Type of memory. Built-in or a custom type registered on the store." + "description": "Equals the given value." }, - "sessionId": { + "ne": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session ID associated with this memory." + "description": "Not equal to the given value." }, - "ownerId": { + "in": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + }, + "description": "Matches any of the given values." + }, + "all": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + }, + "description": "Matches all of the given values." + } + } + }, + "SessionListItem": { + "type": "object", + "description": "One session row without events or summary text. Owner ID and timestamps are absent when unknown on legacy sessions.", + "properties": { + "sessionId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner ID associated with this memory." + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." }, - "namespace": { + "ownerId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping. Absent when the record has none: either it was written directly without one, or it was extracted from a session that has no namespace." - }, - "topics": { - "type": "array", - "items": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "description": "Topic name (1-100 chars)." - }, - "maxItems": 50, - "description": "Topic tags for categorization." - }, - "attributes": { - "$ref": "#/components/schemas/AttributeMap" + "description": "The owner of the session, set from the actorId of the first event. Absent for a legacy session that has no recorded owner." }, "createdAt": { "type": "string", - "description": "Timestamp when the memory was created (UTC).", "format": "date-time" }, "updatedAt": { "type": "string", - "description": "Timestamp when the memory was last updated (UTC).", "format": "date-time" + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" } }, "required": [ + "sessionId" + ] + }, + "SessionListSortBy": { + "type": "string", + "enum": [ + "sessionId", "createdAt", - "id", - "text", "updatedAt" ] }, - "MemoryTypeFilter": { + "SessionListSortOrder": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "SessionSourceEvidence": { "type": "object", - "description": "Filter by memory type. Matching is case-insensitive.", "properties": { - "eq": { + "evidenceId": { "type": "string", - "description": "Equals the given value." + "maxLength": 128, + "minLength": 1, + "description": "Identity of a logical extraction occurrence. Delivery retries retain this ID; a new extraction receives a new ID. Absent on legacy evidence." }, - "ne": { + "schemaVersion": { + "type": "integer", + "description": "Version of the evidence metadata contract, currently 1. Absent on legacy evidence.", + "format": "int32" + }, + "capturedAt": { "type": "string", - "description": "Not equal to the given value." + "description": "UTC time when the producer prepared the input for extraction, before model invocation. Preserved through checkpoint replay. This is not the source event time.", + "format": "date-time" }, - "in": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Matches any of the given values." + "producer": { + "$ref": "#/components/schemas/EvidenceProducer" }, - "all": { + "eventIds": { "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated event identifier (exactly 32 alphanumeric characters, UUID without dashes)." }, - "description": "Matches all of the given values." + "description": "Ordered IDs of events whose content was supplied to one model request. Input attribution, not fact citations." } - } - }, - "MessageRole": { - "type": "string", - "description": "Message role for session events.", - "enum": [ - "USER", - "ASSISTANT", - "SYSTEM" + }, + "required": [ + "eventIds" ] }, - "NamespaceFilter": { + "SessionSourceIdentity": { "type": "object", - "description": "Filter by namespace. Matching is case-insensitive.\n\nNamespace is optional, so records without one need care. The positive operators (`eq`, `in`, `all`) require the field to be present and therefore never match a record that has no namespace — including memories extracted from a session that has none. `ne` is a plain negation and *does* match those records, so use `eq` or `in` when you need strict namespace isolation rather than `ne` against the namespaces you want to exclude.\n\nOmit this filter to search across every namespace, and use it to keep recall inside one project, workspace, team, environment, or tenant.", "properties": { - "eq": { + "sessionId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value." + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." }, - "ne": { + "ownerId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Not equal to the given value." - }, - "in": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping (1-64 chars, alphanumeric and dashes).\n\nThis is the shared namespace format for both session memory and long-term memory: sessions carry it as session-level state, and long-term memory records carry it per record. The same constraints apply everywhere it is used.\n\nNamespace is always optional; a record without one is valid. Applications use it to isolate memories by project, workspace, team, environment, or tenant. See `NamespaceFilter` for how records without a namespace behave under search.\n\nA namespace reaches a record one of two ways. Records written directly carry whatever namespace the caller sets. Records produced by extraction inherit the namespace of the session they were extracted from, which is fixed when that session is created; a session with no namespace yields records with no namespace. Existing records are never backfilled when a namespace is introduced later." - }, - "description": "Matches any of the given values." - }, - "all": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping (1-64 chars, alphanumeric and dashes).\n\nThis is the shared namespace format for both session memory and long-term memory: sessions carry it as session-level state, and long-term memory records carry it per record. The same constraints apply everywhere it is used.\n\nNamespace is always optional; a record without one is valid. Applications use it to isolate memories by project, workspace, team, environment, or tenant. See `NamespaceFilter` for how records without a namespace behave under search.\n\nA namespace reaches a record one of two ways. Records written directly carry whatever namespace the caller sets. Records produced by extraction inherit the namespace of the session they were extracted from, which is fixed when that session is created; a session with no namespace yields records with no namespace. Existing records are never backfilled when a namespace is introduced later." - }, - "description": "Matches all of the given values." + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." } - } + }, + "required": [ + "ownerId", + "sessionId" + ] }, - "NotFoundErrorResponseContent": { + "SessionSummary": { "type": "object", - "description": "The requested resource does not exist.", + "description": "Compacted summary of older session history for a session.\n\nThis is derived session state maintained by the background summarization workflow, not a conversation event. It is present only after at least one summarization cycle has completed.", "properties": { - "title": { + "text": { "type": "string", - "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + "description": "The summary text, a single coherent block." }, - "status": { - "type": "integer", - "default": 404, - "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", - "format": "int32" + "summarizedUpToEventId": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Boundary event ID. This event and all events before it (by array position) are covered by the summary; `events` contains the retained turns after this boundary." }, - "detail": { + "createdAt": { "type": "string", - "description": "A human-readable explanation specific to this occurrence of the problem." + "description": "Timestamp when the summary was first created (UTC). Preserved across re-summarization cycles.", + "format": "date-time" }, - "instance": { + "updatedAt": { "type": "string", - "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + "description": "Timestamp when the summary was last regenerated (UTC). Updated on every summarization cycle. Equal to createdAt on the first cycle.", + "format": "date-time" }, - "type": { - "$ref": "#/components/schemas/NotFoundErrorType" + "metadata": { + "description": "Metadata as a JSON object (e.g., model name, token count). Always present; defaults to an empty object when no metadata was supplied." + }, + "summarizedEvents": { + "type": "integer", + "minimum": 1, + "description": "Cumulative number of session events folded into this summary across all summarization cycles. Always positive. A value of 1 may indicate a summary created before this field was tracked; it self-corrects to a real count on the next summarization cycle.", + "format": "int64" } }, "required": [ - "status", - "title", - "type" + "createdAt", + "metadata", + "summarizedEvents", + "summarizedUpToEventId", + "text", + "updatedAt" ] }, - "NotFoundErrorType": { + "StoreHealthFeature": { + "type": "object", + "description": "Feature-level store health.", + "properties": { + "status": { + "$ref": "#/components/schemas/StoreHealthStatus" + } + }, + "required": [ + "status" + ] + }, + "StoreHealthFeatureMap": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/StoreHealthFeature" + } + }, + "StoreHealthResponseContent": { + "type": "object", + "description": "Store-scoped RAM feature health.", + "properties": { + "status": { + "$ref": "#/components/schemas/StoreHealthStatus" + }, + "features": { + "$ref": "#/components/schemas/StoreHealthFeatureMap" + } + }, + "required": [ + "features", + "status" + ] + }, + "StoreHealthStatus": { "type": "string", - "description": "Problem type URI for not-found errors.", + "description": "Store health status.", "enum": [ - "/errors/resource-not-found" + "healthy", + "degraded", + "unavailable" ] }, - "OwnerIdFilter": { + "SummaryView": { "type": "object", - "description": "Filter by the long-term memory ownerId field. Matching is case-sensitive.", + "description": "An owned Summary View definition. Results are separate derived state and never enter long-term-memory search.", "properties": { - "eq": { + "id": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "ownerId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value." + "description": "Immutable canonical user ID assigned by the server. Clients cannot set ownership." }, - "ne": { + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { "type": "string", - "maxLength": 64, + "maxLength": 100, "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Not equal to the given value." + "description": "Customer-visible Summary View name (1-100 characters)." }, - "in": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." - }, - "description": "Matches any of the given values." + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." }, - "all": { + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { "type": "array", "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + "$ref": "#/components/schemas/SummaryViewGroupField" }, - "description": "Matches all of the given values." + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" } - } + }, + "required": [ + "createdAt", + "definitionVersion", + "filterOp", + "groupBy", + "id", + "name", + "output", + "ownerId", + "refreshPolicy", + "source", + "state", + "updatedAt", + "visibility" + ] }, - "PayloadTooLargeErrorResponseContent": { + "SummaryViewConflictErrorResponseContent": { "type": "object", - "description": "The request payload exceeds the maximum supported size.", + "description": "A Summary View operation conflicts with the current definition or partition state.", "properties": { "title": { "type": "string", @@ -3073,7 +8887,7 @@ }, "status": { "type": "integer", - "default": 413, + "default": 409, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -3086,7 +8900,7 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/PayloadTooLargeErrorType" + "$ref": "#/components/schemas/SummaryViewConflictErrorType" } }, "required": [ @@ -3095,254 +8909,318 @@ "type" ] }, - "PayloadTooLargeErrorType": { + "SummaryViewConflictErrorType": { "type": "string", - "description": "Problem type URI for payload-too-large errors.", + "description": "Problem type URI for Summary View conflicts.", "enum": [ - "/errors/payload-too-large" + "/errors/summary-view-conflict" ] }, - "SearchLongTermMemoryRequestContent": { + "SummaryViewEvictionPolicy": { "type": "object", - "description": "Semantic search request for long-term memory records.", + "description": "Selects the bounded set of grouped results. Matching long-term-memory mutations make a scope recent.", "properties": { - "text": { - "type": "string", - "description": "Text to use for semantic search." - }, - "similarityThreshold": { - "type": "number", - "maximum": 1, - "minimum": 0, - "description": "The minimum similarity threshold for vector search results (normalized cosine similarity). Allowed range: 0-1.", - "format": "float" - }, - "filter": { - "$ref": "#/components/schemas/LongTermMemoryFilter" - }, - "filterOp": { - "$ref": "#/components/schemas/FilterConjunction" - }, - "limit": { + "maxPartitions": { "type": "integer", - "default": 10, - "maximum": 100, "minimum": 1, - "description": "Maximum number of results to return. Defaults to 10. Allowed range: 1-100.", "format": "int32" - }, - "pageToken": { - "type": "string", - "description": "Opaque token from a previous response for the next page." - } - } - }, - "SearchLongTermMemoryResponseContent": { - "type": "object", - "description": "Semantic search result for long-term memory records.", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MemoryRecord" - }, - "description": "List of matching memory records. Always returned; empty when no records match." - }, - "nextPageToken": { - "type": "string", - "description": "Opaque token for fetching the next page. Omitted when no more results." } }, "required": [ - "items" + "maxPartitions" ] }, - "SessionEvent": { + "SummaryViewField": { "type": "object", - "description": "A single event in the session history.", + "description": "A field in a bounded structured Summary View result. All fields are optional in generated results.", "properties": { - "eventId": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "Auto-generated event ID." - }, - "actorId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant." - }, - "sessionId": { + "name": { "type": "string", "maxLength": 64, "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (client-provided or server-generated)." - }, - "role": { - "$ref": "#/components/schemas/MessageRole" - }, - "content": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Content" - }, - "description": "The message content as an array of typed content parts." - }, - "createdAt": { - "type": "string", - "description": "Client-supplied timestamp for when the event happened (UTC).", - "format": "date-time" + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$" }, - "systemTimestamp": { + "description": { "type": "string", - "description": "Server-set timestamp for when the dataplane ingested the event (UTC).", - "format": "date-time" + "maxLength": 200, + "minLength": 1 }, - "metadata": { - "description": "Optional metadata as any valid JSON value." + "type": { + "$ref": "#/components/schemas/SummaryViewFieldType" } }, "required": [ - "actorId", - "content", - "createdAt", - "eventId", - "role", + "description", + "name", + "type" + ] + }, + "SummaryViewFieldType": { + "type": "string", + "description": "Supported structured-output field types. This matches the custom-memory field vocabulary.", + "enum": [ + "str", + "int", + "float", + "bool", + "list[str]", + "list[float]", + "object" + ] + }, + "SummaryViewGroupField": { + "type": "string", + "description": "A deterministic source field used to partition a Summary View.", + "enum": [ + "ownerId", "sessionId", - "systemTimestamp" + "namespaceRef", + "memoryType" ] }, - "SessionIdFilter": { + "SummaryViewOutputConfig": { "type": "object", - "description": "Filter by the long-term memory sessionId field. Matching is case-sensitive.", + "description": "Text or structured generation configuration. Schema is required exactly when format is structured.", "properties": { - "eq": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value." + "format": { + "$ref": "#/components/schemas/SummaryViewOutputFormat" }, - "ne": { + "instructions": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Not equal to the given value." - }, - "in": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." - }, - "description": "Matches any of the given values." + "maxLength": 10000, + "description": "Customer-authored generation instructions. Prompt-security validation is enforced by the service." }, - "all": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." - }, - "description": "Matches all of the given values." + "schema": { + "$ref": "#/components/schemas/SummaryViewSchema" } - } + }, + "required": [ + "format" + ] }, - "SessionSummary": { + "SummaryViewOutputFormat": { + "type": "string", + "description": "Summary View output format.", + "enum": [ + "text", + "structured" + ] + }, + "SummaryViewQuotaCode": { + "type": "string", + "description": "Stable machine-readable Summary View quota code.", + "enum": [ + "DEFINITION_LIMIT_EXCEEDED", + "PARTITION_LIMIT_EXCEEDED", + "SOURCE_LIMIT_EXCEEDED", + "SCHEDULED_GENERATION_LIMIT_EXCEEDED", + "CONCURRENCY_LIMIT_EXCEEDED" + ] + }, + "SummaryViewQuotaErrorResponseContent": { "type": "object", - "description": "Compacted summary of older session history for a session.\n\nThis is derived session state maintained by the background summarization workflow, not a conversation event. It is present only after at least one summarization cycle has completed.", + "description": "A Summary View operation exceeded an enabled store quota. Exact ceilings are deployment configuration established from benchmark evidence.", "properties": { - "text": { + "title": { "type": "string", - "description": "The summary text, a single coherent block." + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." }, - "summarizedUpToEventId": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "Boundary event ID. This event and all events before it (by array position) are covered by the summary; `events` contains the retained turns after this boundary." + "status": { + "type": "integer", + "default": 429, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" }, - "createdAt": { + "detail": { "type": "string", - "description": "Timestamp when the summary was first created (UTC). Preserved across re-summarization cycles.", - "format": "date-time" + "description": "A human-readable explanation specific to this occurrence of the problem." }, - "updatedAt": { + "instance": { "type": "string", - "description": "Timestamp when the summary was last regenerated (UTC). Updated on every summarization cycle. Equal to createdAt on the first cycle.", - "format": "date-time" + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, - "metadata": { - "description": "Metadata as a JSON object (e.g., model name, token count). Always present; defaults to an empty object when no metadata was supplied." + "type": { + "$ref": "#/components/schemas/SummaryViewQuotaErrorType" }, - "summarizedEvents": { + "code": { + "$ref": "#/components/schemas/SummaryViewQuotaCode" + } + }, + "required": [ + "code", + "status", + "title", + "type" + ] + }, + "SummaryViewQuotaErrorType": { + "type": "string", + "description": "Problem type URI for Summary View quota errors.", + "enum": [ + "/errors/summary-view-quota-exceeded" + ] + }, + "SummaryViewRefreshMode": { + "type": "string", + "description": "Summary View refresh mode.", + "enum": [ + "manual", + "continuous" + ] + }, + "SummaryViewRefreshPolicy": { + "type": "object", + "description": "Refresh policy. Continuous views require reconcileIntervalSeconds; manual views omit both interval fields.", + "properties": { + "mode": { + "$ref": "#/components/schemas/SummaryViewRefreshMode" + }, + "debounceSeconds": { "type": "integer", "minimum": 1, - "description": "Cumulative number of session events folded into this summary across all summarization cycles. Always positive. A value of 1 may indicate a summary created before this field was tracked; it self-corrects to a real count on the next summarization cycle.", - "format": "int64" + "format": "int32" + }, + "reconcileIntervalSeconds": { + "type": "integer", + "minimum": 1, + "format": "int32" + } + }, + "required": [ + "mode" + ] + }, + "SummaryViewRefreshTarget": { + "type": "string", + "description": "Manual refresh target. Partition refreshes require an exact configured scope; full refreshes reconcile every current and prior partition.", + "enum": [ + "partition", + "full" + ] + }, + "SummaryViewResultErrorCode": { + "type": "string", + "description": "Customer-safe refresh failure code. Provider details and source content are never returned.", + "enum": [ + "SOURCE_LIMIT_EXCEEDED", + "GENERATION_FAILED", + "OUTPUT_VALIDATION_FAILED", + "REFRESH_RETRIES_EXHAUSTED", + "INTERNAL_ERROR" + ] + }, + "SummaryViewResultStatus": { + "type": "string", + "description": "Materialization refresh state.", + "enum": [ + "cold", + "pending", + "ready", + "empty", + "refreshing", + "failed" + ] + }, + "SummaryViewResultValue": { + "description": "Exactly one generated result value. It is omitted for cold, pending, and empty results, and may be omitted for failed results with no last good value.", + "oneOf": [ + { + "type": "object", + "title": "text", + "properties": { + "text": { + "type": "string" + } + }, + "required": [ + "text" + ] + }, + { + "type": "object", + "title": "data", + "properties": { + "data": {} + }, + "required": [ + "data" + ] } - }, - "required": [ - "createdAt", - "metadata", - "summarizedEvents", - "summarizedUpToEventId", - "text", - "updatedAt" ] }, - "StoreHealthFeature": { + "SummaryViewSchema": { "type": "object", - "description": "Feature-level store health.", + "description": "Bounded structured-output schema. Nested object schemas, required fields, and arbitrary JSON Schema are not supported in the MVP.", "properties": { - "status": { - "$ref": "#/components/schemas/StoreHealthStatus" + "fields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewField" + }, + "maxItems": 50, + "minItems": 1 } }, "required": [ - "status" + "fields" ] }, - "StoreHealthFeatureMap": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/StoreHealthFeature" - } - }, - "StoreHealthResponseContent": { + "SummaryViewScope": { "type": "object", - "description": "Store-scoped RAM feature health.", + "description": "An exact partition scope. The service requires exactly the fields configured by groupBy; singleton views accept none.", "properties": { - "status": { - "$ref": "#/components/schemas/StoreHealthStatus" + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." }, - "features": { - "$ref": "#/components/schemas/StoreHealthFeatureMap" + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + }, + "namespaceRef": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, `message`, and `session_summary_view`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes." } - }, - "required": [ - "features", - "status" + } + }, + "SummaryViewSource": { + "type": "string", + "description": "The source record family summarized by a Summary View. The MVP supports long-term memory only.", + "enum": [ + "long_term" ] }, - "StoreHealthStatus": { + "SummaryViewState": { "type": "string", - "description": "Store health status.", + "description": "Summary View definition lifecycle state.", "enum": [ - "healthy", - "degraded", - "unavailable" + "active", + "disabled", + "deleting" + ] + }, + "SummaryViewVisibility": { + "type": "string", + "description": "The immutable audience of a Summary View.", + "enum": [ + "PRIVATE", + "SHARED" ] }, "TimeoutErrorResponseContent": { @@ -3506,7 +9384,11 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The caller's namespace. The update is rejected with 409 unless the record's stored namespace matches exactly.\n\nOmit it to assert that the record has no namespace — an omitted value matches only records stored without one, it does not mean \"any namespace\". The check is applied atomically with the update, so a record cannot be re-filed into another namespace between the check and the write." + "description": "The caller's current deprecated namespace label. The update is rejected with 409 unless the record still stores that label. Do not supply it with namespaceRef.\n\nOmit both namespace and namespaceRef to assert that the record has no namespace placement. Omission does not mean \"any namespace\".", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" }, "text": { "type": "string", @@ -3552,7 +9434,11 @@ }, "namespace": { "type": "string", - "description": "Updated namespace. When provided, must be 1-64 chars alphanumeric and dashes; omit the field or send an empty string to clear.\n\nThis is the only way to change the namespace of an existing record, including one produced by extraction: a record inherits its session's namespace at creation and is not revised afterwards, so use this to re-file a record or to add a namespace to one created before the session had any." + "description": "Updated deprecated namespace label. When provided, it must be 1-64 alphanumeric characters or dashes. Send an empty string to clear it.\nDo not supply it with namespaceRef.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" }, "ownerId": { "type": "string", @@ -3605,7 +9491,11 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Namespace for logical grouping. Absent when the record has none: either it was written directly without one, or it was extracted from a session that has no namespace." + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" }, "topics": { "type": "array", @@ -3630,6 +9520,21 @@ "type": "string", "description": "Timestamp when the memory was last updated (UTC).", "format": "date-time" + }, + "sources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemorySourceLink" + }, + "description": "Generic source links for this memory. Document sources include the customer document ID and revision that produced the memory." + }, + "sourcesNextPageToken": { + "type": "string", + "description": "Opaque token for additional source links when this response truncated attribution. Absent when every link is included." + }, + "documentManaged": { + "type": "boolean", + "description": "True when this memory was created by document ingestion and is protected from generic overwrite." } }, "required": [ @@ -3639,146 +9544,179 @@ "updatedAt" ] }, - "ResourceSuspendedErrorResponseContent": { + "UpdateNamespaceRequestContent": { "type": "object", - "description": "The requested resource exists but is suspended by an administrator.", "properties": { - "title": { - "type": "string", - "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." - }, - "status": { - "type": "integer", - "default": 423, - "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", - "format": "int32" - }, - "detail": { - "type": "string", - "description": "A human-readable explanation specific to this occurrence of the problem." - }, - "instance": { + "name": { "type": "string", - "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One case-sensitive namespace path segment." }, - "type": { - "$ref": "#/components/schemas/ResourceSuspendedErrorType" + "state": { + "$ref": "#/components/schemas/NamespaceUpdateState" } - }, - "required": [ - "status", - "title", - "type" - ] - }, - "ResourceSuspendedErrorType": { - "type": "string", - "description": "Problem type URI for suspended resources.", - "enum": [ - "/errors/resource-suspended" - ] + } }, - "ListSessionSummariesResponseContent": { + "UpdateNamespaceResponseContent": { "type": "object", - "description": "Paginated session rows for a store.", "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SessionListItem" - } - }, - "total": { - "type": "integer", - "format": "int32" - }, - "nextPageToken": { - "type": "string" + "namespace": { + "$ref": "#/components/schemas/Namespace" } }, "required": [ - "items", - "total" + "namespace" ] }, - "SessionListItem": { + "UpdateSummaryViewRequestContent": { "type": "object", - "description": "One session row without events or summary text. Owner ID and timestamps are absent when unknown on legacy sessions.", "properties": { - "sessionId": { + "name": { "type": "string", - "maxLength": 64, + "maxLength": 100, "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + "description": "Customer-visible Summary View name (1-100 characters)." }, - "ownerId": { + "description": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Absent for a legacy session that has no recorded owner." + "maxLength": 200, + "description": "New description. Send an empty string to clear it." }, - "createdAt": { - "type": "string", - "format": "date-time" + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" }, - "updatedAt": { - "type": "string", - "format": "date-time" + "clearFilter": { + "type": "boolean", + "description": "Clear the saved filter. Mutually exclusive with filter." }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRef" + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "clearTimeWindow": { + "type": "boolean", + "description": "Clear the rolling time window. Mutually exclusive with timeWindowSeconds." + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "state": { + "$ref": "#/components/schemas/MutableSummaryViewState" } - }, - "required": [ - "sessionId" - ] + } }, - "NamespaceRef": { + "UpdateSummaryViewResponseContent": { "type": "object", - "description": "Reference to a namespace resource with its stable identifier and current display data.", + "description": "An owned Summary View definition. Results are separate derived state and never enter long-term-memory search.", "properties": { - "namespaceId": { + "id": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "ownerId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Stable namespace resource identifier." + "description": "Immutable canonical user ID assigned by the server. Clients cannot set ownership." + }, + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" }, "name": { "type": "string", - "maxLength": 64, + "maxLength": 100, "minLength": 1, - "pattern": "^[a-zA-Z0-9_-]+$", - "description": "Current namespace name." + "description": "Customer-visible Summary View name (1-100 characters)." }, - "path": { + "description": { "type": "string", - "maxLength": 1024, - "minLength": 1, - "description": "Current namespace path." + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" } }, "required": [ - "name", - "namespaceId", - "path" - ] - }, - "SessionListSortOrder": { - "type": "string", - "enum": [ - "asc", - "desc" - ] - }, - "SessionListSortBy": { - "type": "string", - "enum": [ - "sessionId", "createdAt", - "updatedAt" + "definitionVersion", + "filterOp", + "groupBy", + "id", + "name", + "output", + "ownerId", + "refreshPolicy", + "source", + "state", + "updatedAt", + "visibility" ] } } diff --git a/content/develop/ai/context-engine/agent-memory/list-sessions.md b/content/develop/ai/context-engine/agent-memory/list-sessions.md new file mode 100644 index 0000000000..959f0ee259 --- /dev/null +++ b/content/develop/ai/context-engine/agent-memory/list-sessions.md @@ -0,0 +1,118 @@ +--- +alwaysopen: false +categories: +- docs +- develop +- ai +description: List session metadata by owner or namespace, sort results, and follow pagination. +hideListLinks: true +linktitle: List sessions +title: List and sort sessions +weight: 10 +--- + +Use session listing to let a user reopen an earlier conversation. The session rows contain IDs and available owner, timestamp, and namespace metadata. They do not contain conversation events or summary text. Retrieve the selected session with `GetSessionMemory` to build the agent's conversation context. + +## Choose a listing operation + +| Result you need | REST endpoint | Python method | TypeScript method | +|:----------------|:--------------|:--------------|:------------------| +| Session IDs | `GET /v1/stores/{storeId}/session-memory` | `list_sessions` | `listSessions` | +| Session rows with sorting | `GET /v1/stores/{storeId}/sessions` | `list_session_summaries` | `listSessionSummaries` | + +The second method's name refers to metadata rows, not generated conversation summaries. The ID-only operation remains available. Both operations return `items`, `total`, and an optional `nextPageToken`. + +For a user's personal conversations, set `filterOwnerId` to the user's authenticated ID. The session owner is the first event's `actorId`. Add `namespaceRef` to require an exact namespace ID as well. It does not include descendants, deprecated namespace labels, or sessions without a namespace resource. Owner and namespace matching is case-sensitive. + +A request without filters must set `includeAll=true`. Do not combine `includeAll=true` with either filter. Authorize store-wide or shared namespace listing in your application before making the request. A filter selects records; it does not establish the caller's right to access them. + +## Sort and paginate session rows + +The default is `sortBy=updatedAt` and `sortOrder=desc`, so recent activity appears first. `sortBy` accepts `sessionId`, `createdAt`, or `updatedAt`. When omitted, `sortOrder` is `asc` for `sessionId` and `desc` for timestamps. `limit` accepts 1–1000 and defaults to 100. + +The examples use Python `redis-agent-memory==0.5.0b1` or TypeScript `@redis-iris/agent-memory@0.4.0-beta.1`. Install that beta explicitly and use a service that supports the endpoint. Set `AGENT_MEMORY_URL`, `STORE_ID`, `API_KEY`, and `OWNER_ID` in your shell. See the [developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md#connect-to-a-redis-agent-memory-service) for connection details. + +These examples list all pages for one owner, with the most recently updated sessions first: + +{{< multitabs id="sorted-session-list" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python +import asyncio +import os + +from redis_agent_memory import AgentMemory + + +async def main(): + async with AgentMemory( + os.environ["AGENT_MEMORY_URL"], + store_id=os.environ["STORE_ID"], + api_key=os.environ["API_KEY"], + ) as agent_memory: + page_token = None + while True: + page = await agent_memory.list_session_summaries_async( + filter_owner_id=os.environ["OWNER_ID"], + sort_by="updatedAt", sort_order="desc", + limit=20, page_token=page_token, + ) + for session in page.items: + print(session.model_dump_json(by_alias=True)) + page_token = page.next_page_token + if not page_token: + break + + +asyncio.run(main()) +``` + +-tab-sep- + +```typescript +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function main() { + const { AGENT_MEMORY_URL, STORE_ID, API_KEY, OWNER_ID } = process.env; + if (!AGENT_MEMORY_URL || !STORE_ID || !API_KEY || !OWNER_ID) { + throw new Error("Set AGENT_MEMORY_URL, STORE_ID, API_KEY, and OWNER_ID."); + } + const agentMemory = new AgentMemory({ + serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY, + }); + let pageToken: string | undefined; + do { + const page = await agentMemory.listSessionSummaries( + 20, pageToken, OWNER_ID, undefined, undefined, "updatedAt", "desc", + ); + console.dir(page.items, { depth: null }); + pageToken = page.nextPageToken; + } while (pageToken); +} + +main().catch((error) => { console.error(error); process.exitCode = 1; }); +``` + +-tab-sep- + +```sh +page_token='' +while :; do + page=$(curl --fail-with-body --silent --show-error --get \ + --header "Authorization: Bearer $API_KEY" \ + --data-urlencode "filterOwnerId=$OWNER_ID" \ + --data-urlencode 'sortBy=updatedAt' --data-urlencode 'sortOrder=desc' \ + --data-urlencode 'limit=20' --data-urlencode "pageToken=$page_token" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/sessions") || break + printf '%s\n' "$page" | jq '.items' + page_token=$(printf '%s\n' "$page" | jq -r '.nextPageToken // empty') + [ -n "$page_token" ] || break +done +``` + +{{< /multitabs >}} + +Pass each returned `nextPageToken` as `pageToken`. Keep filters and sort options unchanged; the token is bound to them. Stop when the token is absent. The token is opaque, so URL-encode it in REST requests. Changing a filter or sort requires a new request without the old token. + +Legacy sessions can omit `ownerId`, `createdAt`, or `updatedAt`. Handle those missing values when rendering a conversation list. A row's timestamps describe session metadata; they do not promise that background extraction or summarization has completed. + +TypeScript uses positional arguments. `listSessionSummaries` takes namespace filtering fourth, followed by `includeAll`, `sortBy`, and `sortOrder`. The older `listSessions` method keeps its previous argument order: `includeAll` is fourth, request options fifth, and the new namespace filter sixth. Do not copy one method's argument positions to the other. diff --git a/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md b/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md index a37a75709e..8c7b05ff80 100644 --- a/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md @@ -51,7 +51,7 @@ Verify that the service is available: ```sh curl --fail-with-body --silent --show-error \ --header "Authorization: Bearer $API_KEY" \ - "$AGENT_MEMORY_URL/health" | jq + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/health" | jq ``` ## 1. Build conversation context with session memory @@ -105,7 +105,7 @@ curl --fail-with-body --silent --show-error \ The response includes `sessionId` and the available owner, creation, update, and namespace details. It defaults to newest `updatedAt` first. Use `nextPageToken` as `pageToken` with the same filter and sort options for the -next page. The existing `/session-memory` list returns session IDs. +next page. The existing `/session-memory` list returns session IDs. See [List and sort sessions](/content/develop/ai/context-engine/agent-memory/list-sessions.md) for Python, TypeScript, and curl pagination examples and filter rules. ## 2. Recall automatically extracted information From 6c656f936de80498a9be4b2832e7cdd3b0a9068a Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Wed, 21 Oct 2026 12:18:54 -0700 Subject: [PATCH 11/26] Cover beta retrieval and placement behavior --- .../api-reference/openapi-agent-memory.json | 8395 +++++++++++++---- .../agent-memory/long-term-memory.md | 48 + .../context-engine/agent-memory/namespaces.md | 6 +- .../context-engine/agent-memory/quickstart.md | 18 +- .../context-engine/agent-memory/sessions.md | 2 + 5 files changed, 6578 insertions(+), 1891 deletions(-) diff --git a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json index 514f56fa26..c3095f3194 100644 --- a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json +++ b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json @@ -6,50 +6,50 @@ "description": "API for storing, retrieving, and searching Redis Agent Memory session memory and long-term memory.\n\nAuthentication depends on your deployment. Use the credential issued for your Redis Agent Memory service or store. For self-managed deployments, see the self-managed Redis Agent Memory guide for authentication options.\n" }, "paths": { - "/health": { - "get": { - "description": "Return information about the operational status of the service.", - "operationId": "Health", - "responses": { - "200": { - "description": "Health 200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HealthResponseContent" - } + "/v1/stores/{storeId}/document-ingestions": { + "post": { + "description": "Submit one revisioned document for extraction.", + "operationId": "CreateDocumentIngestion", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateDocumentIngestionRequestContent" } } - } - } - } - }, - "/v1/stores/{storeId}/health": { - "get": { - "description": "Returns read-only health for applicable store-scoped RAM features.", - "operationId": "StoreHealth", + }, + "required": true + }, "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { - "200": { - "description": "StoreHealth 200 response", + "202": { + "description": "CreateDocumentIngestion 202 response", + "headers": { + "Location": { + "schema": { + "type": "string" + }, + "required": true + } + }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StoreHealthResponseContent" + "$ref": "#/components/schemas/CreateDocumentIngestionResponseContent" } } } @@ -104,6 +104,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -114,6 +124,16 @@ } } }, + "422": { + "description": "IngestionHookStoppedError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookStoppedErrorResponseContent" + } + } + } + }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -125,11 +145,11 @@ } }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -153,46 +173,58 @@ } } } + }, + "503": { + "description": "IngestionHookRetryError 503 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookRetryErrorResponseContent" + } + } + } } } } }, - "/v1/stores/{storeId}/long-term-memory": { - "delete": { - "description": "Deletes long-term memories in bulk by their IDs.", - "operationId": "BulkDeleteLongTermMemories", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BulkDeleteLongTermMemoriesRequestContent" - } - } - }, - "required": true - }, + "/v1/stores/{storeId}/document-ingestions/{ingestionId}": { + "get": { + "operationId": "GetDocumentIngestion", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "ingestionId", + "in": "path", + "description": "Server-generated document ingestion identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." }, "required": true } ], "responses": { "200": { - "description": "BulkDeleteLongTermMemories 200 response", + "description": "GetDocumentIngestion 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BulkDeleteLongTermMemoriesResponseContent" + "$ref": "#/components/schemas/GetDocumentIngestionResponseContent" } } } @@ -247,16 +279,6 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" - } - } - } - }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -277,16 +299,6 @@ } } }, - "429": { - "description": "TooManyRequestsError 429 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" - } - } - } - }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -298,54 +310,62 @@ } } } - }, - "post": { - "description": "Creates long-term memories in bulk.", - "operationId": "BulkCreateLongTermMemories", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BulkCreateLongTermMemoriesRequestContent" - }, - "example": { - "memories": [ - { - "id": "mem-1", - "text": "The user prefers dark mode", - "ownerId": "user-1", - "namespaceRef": { - "namespaceId": "ns-product" - } - } - ] - } - } - }, - "required": true - }, + } + }, + "/v1/stores/{storeId}/document-ingestions/{ingestionId}/outputs": { + "get": { + "operationId": "ListDocumentIngestionOutputs", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "ingestionId", + "in": "path", + "description": "Server-generated document ingestion identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." }, "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } } ], "responses": { - "201": { - "description": "BulkCreateLongTermMemories 201 response", + "200": { + "description": "ListDocumentIngestionOutputs 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BulkCreateLongTermMemoriesResponseContent" + "$ref": "#/components/schemas/ListDocumentIngestionOutputsResponseContent" } } } @@ -400,16 +420,6 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" - } - } - } - }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -421,21 +431,11 @@ } }, "424": { - "description": "MemoryFailedDependencyError 424 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" - } - } - } - }, - "429": { - "description": "TooManyRequestsError 429 response", + "description": "FailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" } } } @@ -453,50 +453,32 @@ } } }, - "/v1/stores/{storeId}/long-term-memory/move": { - "post": { - "description": "Moves long-term memories to one existing namespace resource. Returns 409 when the destination namespace is archived.", - "operationId": "MoveLongTermMemories", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MoveLongTermMemoriesRequestContent" - }, - "example": { - "memoryIds": [ - "mem-1" - ], - "namespaceRef": { - "namespaceId": "ns-product" - } - } - } - }, - "required": true - }, + "/v1/stores/{storeId}/health": { + "get": { + "description": "Returns read-only health for applicable store-scoped RAM features.", + "operationId": "StoreHealth", "parameters": [ { "name": "storeId", "in": "path", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "description": "The store instance ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + "description": "The store instance ID." }, "required": true } ], "responses": { "200": { - "description": "MoveLongTermMemories 200 response", + "description": "StoreHealth 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MoveLongTermMemoriesResponseContent" + "$ref": "#/components/schemas/StoreHealthResponseContent" } } } @@ -551,16 +533,6 @@ } } }, - "409": { - "description": "ConflictError 409 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ConflictErrorResponseContent" - } - } - } - }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -611,54 +583,58 @@ } } } - } + }, + "tags": [ + "Store Health" + ] } }, - "/v1/stores/{storeId}/long-term-memory/search": { - "post": { - "description": "Runs a semantic search on long-term memory with filtering options.", - "operationId": "SearchLongTermMemory", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SearchLongTermMemoryRequestContent" - }, - "example": { - "text": "preferred theme", - "filter": { - "namespaceRef": { - "eq": "ns-product" - } - } - } - } - } - }, + "/v1/stores/{storeId}/ingestion-hooks/events": { + "get": { + "description": "List hook decisions in stable newest-first order. Pages can be empty after owner filtering; continue while nextCursor is present. Internal run reservations are omitted.", + "operationId": "ListIngestionHookStatuses", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true - } - ], - "responses": { - "200": { - "description": "SearchLongTermMemory 200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SearchLongTermMemoryResponseContent" - } - } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 20, + "maximum": 100, + "minimum": 1, + "format": "int32" + } + }, + { + "name": "cursor", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ListIngestionHookStatuses 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListIngestionHookStatusesResponseContent" + } + } } }, "400": { @@ -691,62 +667,136 @@ } } }, - "404": { - "description": "NotFoundError 404 response", + "423": { + "description": "ResourceSuspendedError 423 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NotFoundErrorResponseContent" + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" } } } }, - "408": { - "description": "TimeoutError 408 response", + "424": { + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TimeoutErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } }, - "413": { - "description": "PayloadTooLargeError 413 response", + "500": { + "description": "UnexpectedError 500 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" } } } + } + }, + "tags": [ + "Ingestion Hooks" + ] + } + }, + "/v1/stores/{storeId}/ingestion-hooks/events/{eventId}": { + "get": { + "description": "Read a hook decision and application status. Requires authenticated store access and, when enforced, matching owner scope. Content and secrets are never returned.", + "operationId": "GetIngestionHookStatus", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true }, - "423": { - "description": "ResourceSuspendedError 423 response", + { + "name": "eventId", + "in": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetIngestionHookStatus 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + "$ref": "#/components/schemas/GetIngestionHookStatusResponseContent" } } } }, - "424": { - "description": "MemoryFailedDependencyError 424 response", + "400": { + "description": "BadRequestError 400 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/BadRequestErrorResponseContent" } } } }, - "429": { - "description": "TooManyRequestsError 429 response", + "401": { + "description": "AuthenticationError 401 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "MemoryFailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -761,13 +811,26 @@ } } } - } + }, + "tags": [ + "Ingestion Hooks" + ] } }, - "/v1/stores/{storeId}/long-term-memory/{memoryId}": { - "get": { - "description": "Returns a long-term memory by its ID.", - "operationId": "GetLongTermMemory", + "/v1/stores/{storeId}/long-term-memory": { + "delete": { + "description": "Deletes long-term memories in bulk by their IDs.", + "operationId": "BulkDeleteLongTermMemories", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkDeleteLongTermMemoriesRequestContent" + } + } + }, + "required": true + }, "parameters": [ { "name": "storeId", @@ -781,28 +844,15 @@ "description": "The store instance ID." }, "required": true - }, - { - "name": "memoryId", - "in": "path", - "description": "The memory record ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The memory record ID." - }, - "required": true } ], "responses": { "200": { - "description": "GetLongTermMemory 200 response", + "description": "BulkDeleteLongTermMemories 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetLongTermMemoryResponseContent" + "$ref": "#/components/schemas/BulkDeleteLongTermMemoriesResponseContent" } } } @@ -907,19 +957,23 @@ } } } - } + }, + "tags": [ + "Long-Term Memory" + ] }, - "patch": { - "description": "Partially updates a long-term memory by its ID.", - "operationId": "UpdateLongTermMemory", + "post": { + "description": "Creates long-term memories in bulk. Records without namespace or namespaceRef stay un-namespaced. Automatic routing does not place them in a shared namespace. An explicit namespaceRef must pass destination admission and is never rerouted. Admission failures are reported per record in errors.", + "operationId": "BulkCreateLongTermMemories", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateLongTermMemoryRequestContent" + "$ref": "#/components/schemas/BulkCreateLongTermMemoriesRequestContent" } } - } + }, + "required": true }, "parameters": [ { @@ -934,28 +988,15 @@ "description": "The store instance ID." }, "required": true - }, - { - "name": "memoryId", - "in": "path", - "description": "The memory record ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The memory record ID." - }, - "required": true } ], "responses": { - "200": { - "description": "UpdateLongTermMemory 200 response", + "201": { + "description": "BulkCreateLongTermMemories 201 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateLongTermMemoryResponseContent" + "$ref": "#/components/schemas/BulkCreateLongTermMemoriesResponseContent" } } } @@ -1020,6 +1061,16 @@ } } }, + "422": { + "description": "IngestionHookStoppedError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookStoppedErrorResponseContent" + } + } + } + }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -1059,19 +1110,32 @@ } } } + }, + "503": { + "description": "IngestionHookRetryError 503 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookRetryErrorResponseContent" + } + } + } } - } + }, + "tags": [ + "Long-Term Memory" + ] } }, - "/v1/stores/{storeId}/long-term-memory/{memoryId}/fields": { - "patch": { - "description": "Atomically updates a custom-typed long-term memory's text and/or attributes, scoped to the caller's bound memory type. Used by the custom-extraction worker.", - "operationId": "UpdateLongTermMemoryFields", + "/v1/stores/{storeId}/long-term-memory/move": { + "post": { + "description": "Moves long-term memories to an explicit namespaceRef, or automatically routes a memory that is already in a shared namespace when namespaceRef is omitted. Personal and un-namespaced memories are not automatic-routing candidates and keep their current placement with a per-item error. If routing is disabled or no shared destination qualifies, the memory also keeps its current placement and receives a per-item error. An explicit destination must pass admission and is never rerouted. Returns 409 when the explicit destination is archived; automatic routing and admission failures are reported per item in errors.", + "operationId": "MoveLongTermMemories", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateLongTermMemoryFieldsRequestContent" + "$ref": "#/components/schemas/MoveLongTermMemoriesRequestContent" } } }, @@ -1081,33 +1145,27 @@ { "name": "storeId", "in": "path", - "description": "The store instance ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." - }, - "required": true - }, - { - "name": "memoryId", - "in": "path", - "description": "The memory record ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The memory record ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { "200": { - "description": "UpdateLongTermMemoryFields 200 response" + "description": "MoveLongTermMemories 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MoveLongTermMemoriesResponseContent" + } + } + } }, "400": { "description": "BadRequestError 400 response", @@ -1159,22 +1217,42 @@ } } }, - "423": { - "description": "ResourceSuspendedError 423 response", + "409": { + "description": "ConflictError 409 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + "$ref": "#/components/schemas/ConflictErrorResponseContent" } } } }, - "424": { - "description": "MemoryFailedDependencyError 424 response", - "content": { + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" } } } @@ -1199,89 +1277,47 @@ } } } - } + }, + "tags": [ + "Long-Term Memory" + ] } }, - "/v1/stores/{storeId}/namespaces": { - "get": { - "description": "Lists namespace roots or direct children.", - "operationId": "ListNamespaces", + "/v1/stores/{storeId}/long-term-memory/search": { + "post": { + "description": "Runs a semantic search on long-term memory with filtering options.", + "operationId": "SearchLongTermMemory", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchLongTermMemoryRequestContent" + } + } + } + }, "parameters": [ { "name": "storeId", "in": "path", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "description": "The store instance ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + "description": "The store instance ID." }, "required": true - }, - { - "name": "parentId", - "in": "query", - "description": "List direct children of this namespace. Omit to list roots.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "List direct children of this namespace. Omit to list roots." - } - }, - { - "name": "scope", - "in": "query", - "description": "Restrict root results to one scope. Cannot be combined with parentId.", - "schema": { - "$ref": "#/components/schemas/NamespaceScope" - } - }, - { - "name": "ownerId", - "in": "query", - "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots." - } - }, - { - "name": "limit", - "in": "query", - "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", - "schema": { - "type": "integer", - "default": 100, - "maximum": 1000, - "minimum": 1, - "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", - "format": "int32" - } - }, - { - "name": "pageToken", - "in": "query", - "description": "Opaque token from a previous response for the next page.", - "schema": { - "type": "string", - "description": "Opaque token from a previous response for the next page." - } } ], "responses": { "200": { - "description": "ListNamespaces 200 response", + "description": "SearchLongTermMemory 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListNamespacesResponseContent" + "$ref": "#/components/schemas/SearchLongTermMemoryResponseContent" } } } @@ -1357,11 +1393,11 @@ } }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -1386,46 +1422,51 @@ } } } - } - }, - "post": { - "description": "Creates a namespace resource as a root, direct child, or explicit hierarchy path. Returns 409 when an active or archived namespace already occupies the requested location.", - "operationId": "CreateNamespace", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateNamespaceRequestContent" - }, - "example": { - "name": "product", - "scope": "SHARED" - } - } - } }, + "tags": [ + "Long-Term Memory" + ] + } + }, + "/v1/stores/{storeId}/long-term-memory/{memoryId}": { + "get": { + "description": "Returns a long-term memory by its ID.", + "operationId": "GetLongTermMemory", "parameters": [ { "name": "storeId", "in": "path", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "description": "The store instance ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "memoryId", + "in": "path", + "description": "The memory record ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The memory record ID." }, "required": true } ], "responses": { - "201": { - "description": "CreateNamespace 201 response", + "200": { + "description": "GetLongTermMemory 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateNamespaceResponseContent" + "$ref": "#/components/schemas/GetLongTermMemoryResponseContent" } } } @@ -1480,16 +1521,6 @@ } } }, - "409": { - "description": "ConflictError 409 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ConflictErrorResponseContent" - } - } - } - }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -1540,44 +1571,61 @@ } } } - } - } - }, - "/v1/stores/{storeId}/namespaces/{namespaceId}": { - "delete": { - "description": "Deletes an empty leaf namespace. Returns 409 when the namespace has children or memory placements, or when it changed concurrently.", - "operationId": "DeleteNamespace", + }, + "tags": [ + "Long-Term Memory" + ] + }, + "patch": { + "description": "Partially updates a long-term memory by its ID. The merged record must pass admission at its destination. Updates never select a different destination automatically.", + "operationId": "UpdateLongTermMemory", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLongTermMemoryRequestContent" + } + } + } + }, "parameters": [ { "name": "storeId", "in": "path", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "description": "The store instance ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + "description": "The store instance ID." }, "required": true }, { - "name": "namespaceId", + "name": "memoryId", "in": "path", - "description": "Server-generated namespace identifier.", + "description": "The memory record ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." + "description": "The memory record ID." }, "required": true } ], "responses": { - "204": { - "description": "DeleteNamespace 204 response" + "200": { + "description": "UpdateLongTermMemory 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLongTermMemoryResponseContent" + } + } + } }, "400": { "description": "BadRequestError 400 response", @@ -1660,11 +1708,11 @@ } }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -1689,49 +1737,57 @@ } } } - } - }, - "get": { - "description": "Gets one namespace by its canonical ID.", - "operationId": "GetNamespace", + }, + "tags": [ + "Long-Term Memory" + ] + } + }, + "/v1/stores/{storeId}/long-term-memory/{memoryId}/fields": { + "patch": { + "description": "Atomically updates a custom-typed long-term memory's text and/or attributes, scoped to the caller's bound memory type. Used by the custom-extraction worker. New text must pass destination admission before the update is applied.", + "operationId": "UpdateLongTermMemoryFields", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLongTermMemoryFieldsRequestContent" + } + } + }, + "required": true + }, "parameters": [ { "name": "storeId", "in": "path", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "description": "The store instance ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + "description": "The store instance ID." }, "required": true }, { - "name": "namespaceId", + "name": "memoryId", "in": "path", - "description": "Server-generated namespace identifier.", + "description": "The memory record ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." + "description": "The memory record ID." }, "required": true } ], "responses": { "200": { - "description": "GetNamespace 200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetNamespaceResponseContent" - } - } - } + "description": "UpdateLongTermMemoryFields 200 response" }, "400": { "description": "BadRequestError 400 response", @@ -1783,12 +1839,12 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", + "409": { + "description": "ConflictError 409 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + "$ref": "#/components/schemas/ConflictErrorResponseContent" } } } @@ -1804,11 +1860,11 @@ } }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -1833,20 +1889,16 @@ } } } - } - }, - "patch": { - "description": "Renames or archives a namespace. Returns 409 when the namespace changed concurrently, an active or archived sibling already uses the requested name, or the namespace is archived.", - "operationId": "UpdateNamespace", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateNamespaceRequestContent" - } - } - } }, + "tags": [ + "Long-Term Memory" + ] + } + }, + "/v1/stores/{storeId}/long-term-memory/{memoryId}/sources": { + "get": { + "description": "Pages source links for one long-term memory. Tokens bind this query and expire.", + "operationId": "ListMemorySourceLinks", "parameters": [ { "name": "storeId", @@ -1862,26 +1914,43 @@ "required": true }, { - "name": "namespaceId", + "name": "memoryId", "in": "path", - "description": "Server-generated namespace identifier.", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." }, "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { "200": { - "description": "UpdateNamespace 200 response", + "description": "ListMemorySourceLinks 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateNamespaceResponseContent" + "$ref": "#/components/schemas/ListMemorySourceLinksResponseContent" } } } @@ -1936,26 +2005,6 @@ } } }, - "409": { - "description": "ConflictError 409 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ConflictErrorResponseContent" - } - } - } - }, - "413": { - "description": "PayloadTooLargeError 413 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" - } - } - } - }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -1976,16 +2025,6 @@ } } }, - "429": { - "description": "TooManyRequestsError 429 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" - } - } - } - }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -1996,90 +2035,81 @@ } } } - } + }, + "tags": [ + "Long-Term Memory" + ] } }, - "/v1/stores/{storeId}/session-memory": { + "/v1/stores/{storeId}/long-term-memory/{memoryId}/sources/{sourceId}/evidence": { "get": { - "description": "Returns a paginated list of session IDs for a store.", - "operationId": "ListSessions", + "description": "Pages extraction evidence for one source of a memory, in append order. Tokens bind the store, current owner, memory and source, and expire.", + "operationId": "ListMemorySourceEvidence", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true }, { - "name": "limit", - "in": "query", - "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", - "schema": { - "type": "integer", - "default": 100, - "maximum": 1000, - "minimum": 1, - "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", - "format": "int32" - } - }, - { - "name": "pageToken", - "in": "query", - "description": "Opaque token from a previous response for the next page.", + "name": "memoryId", + "in": "path", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation.", "schema": { "type": "string", - "description": "Opaque token from a previous response for the next page." - } + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "required": true }, { - "name": "filterOwnerId", - "in": "query", - "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll.", + "name": "sourceId", + "in": "path", + "description": "Stable source identifier for one revisioned origin in a store.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll." - } + "description": "Stable source identifier for one revisioned origin in a store." + }, + "required": true }, { - "name": "namespaceRef", + "name": "limit", "in": "query", - "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll.", "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll." + "type": "integer", + "maximum": 100, + "minimum": 1, + "format": "int32" } }, { - "name": "includeAll", + "name": "pageToken", "in": "query", - "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters.", "schema": { - "type": "boolean", - "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters." + "type": "string" } } ], "responses": { "200": { - "description": "ListSessions 200 response", + "description": "ListMemorySourceEvidence 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListSessionsResponseContent" + "$ref": "#/components/schemas/ListMemorySourceEvidenceResponseContent" } } } @@ -2134,16 +2164,6 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" - } - } - } - }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -2164,16 +2184,6 @@ } } }, - "429": { - "description": "TooManyRequestsError 429 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" - } - } - } - }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -2184,59 +2194,38 @@ } } } - } + }, + "tags": [ + "Long-Term Memory" + ] } }, - "/v1/stores/{storeId}/session-memory/events": { - "post": { - "description": "Appends a single event to a session. Creates the session if it does not exist. If sessionId is omitted, the server generates one.", - "operationId": "AddSessionEvent", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddSessionEventRequestContent" - }, - "example": { - "sessionId": "session-user42-20240315", - "actorId": "user-42", - "role": "USER", - "createdAt": "2024-03-15T10:00:00Z", - "content": [ - { - "text": "What were the action items from last week's meeting?" - } - ], - "namespaceRef": { - "namespaceId": "ns-product" - } - } - } - }, - "required": true - }, + "/v1/stores/{storeId}/namespace-routing": { + "get": { + "description": "Returns the store's namespace routing policy. An empty disabled policy is returned when none has been saved.", + "operationId": "GetNamespaceRouting", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { - "201": { - "description": "AddSessionEvent 201 response", + "200": { + "description": "GetNamespaceRouting 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddSessionEventResponseContent" + "$ref": "#/components/schemas/GetNamespaceRoutingResponseContent" } } } @@ -2341,44 +2330,49 @@ } } } - } - } - }, - "/v1/stores/{storeId}/session-memory/{sessionId}": { - "delete": { - "description": "Deletes the session memory for a session.", - "operationId": "DeleteSessionMemory", + }, + "tags": [ + "Namespaces" + ] + }, + "put": { + "description": "Replaces the store's namespace routing policy. Namespaces remain hierarchy records; routing is an ordered store-level document of plug-in strategies.", + "operationId": "PutNamespaceRouting", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PutNamespaceRoutingRequestContent" + } + } + }, + "required": true + }, "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." - }, - "required": true - }, - { - "name": "sessionId", - "in": "path", - "description": "The session ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { - "204": { - "description": "DeleteSessionMemory 204 response" + "200": { + "description": "PutNamespaceRouting 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PutNamespaceRoutingResponseContent" + } + } + } }, "400": { "description": "BadRequestError 400 response", @@ -2430,6 +2424,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -2480,56 +2484,108 @@ } } } - } - }, + }, + "tags": [ + "Namespaces" + ] + } + }, + "/v1/stores/{storeId}/namespaces": { "get": { - "description": "Returns the session memory for a session.", - "operationId": "GetSessionMemory", + "description": "Lists namespace roots or direct children.", + "operationId": "ListNamespaces", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true }, { - "name": "sessionId", - "in": "path", - "description": "The session ID.", + "name": "parentId", + "in": "query", + "description": "List direct children of this namespace. Omit to list roots.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "required": true, - "example": "session-user42-20240315" + "description": "List direct children of this namespace. Omit to list roots." + } }, { - "name": "includeSummarisedEvents", + "name": "scope", "in": "query", - "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage.", + "description": "Restrict root results to one scope. Cannot be combined with parentId.", "schema": { - "type": "boolean", - "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage." + "$ref": "#/components/schemas/NamespaceScope" + } + }, + { + "name": "ownerId", + "in": "query", + "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots." + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Field to sort by. Defaults to id. Name sorts by the indexed display name using Redis text collation, not the namespace path. Equal values are ordered by ID ascending.", + "schema": { + "$ref": "#/components/schemas/NamespaceSortBy" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort direction. Defaults to asc. Use the same sort options for each page; concurrent changes can shift offset-based pages.", + "schema": { + "$ref": "#/components/schemas/NamespaceSortOrder" + } + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page." } } ], "responses": { "200": { - "description": "GetSessionMemory 200 response", + "description": "ListNamespaces 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetSessionMemoryResponseContent" + "$ref": "#/components/schemas/ListNamespacesResponseContent" } } } @@ -2634,57 +2690,48 @@ } } } - } - } - }, - "/v1/stores/{storeId}/session-memory/{sessionId}/events/{eventId}": { - "delete": { - "description": "Deletes a single event from a session by event ID.", - "operationId": "DeleteSessionEvent", + }, + "tags": [ + "Namespaces" + ] + }, + "post": { + "description": "Creates a namespace resource as a root, direct child, or explicit hierarchy path. Returns 409 when an active or archived namespace already occupies the requested location.", + "operationId": "CreateNamespace", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateNamespaceRequestContent" + } + } + } + }, "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." - }, - "required": true - }, - { - "name": "sessionId", - "in": "path", - "description": "The session ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "required": true - }, - { - "name": "eventId", - "in": "path", - "description": "The event ID.", - "schema": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "The event ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { - "204": { - "description": "DeleteSessionEvent 204 response" + "201": { + "description": "CreateNamespace 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateNamespaceResponseContent" + } + } + } }, "400": { "description": "BadRequestError 400 response", @@ -2736,6 +2783,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -2786,62 +2843,47 @@ } } } - } - }, - "get": { - "description": "Returns a single event from a session by event ID.", - "operationId": "GetSessionEvent", + }, + "tags": [ + "Namespaces" + ] + } + }, + "/v1/stores/{storeId}/namespaces/{namespaceId}": { + "delete": { + "description": "Deletes an empty leaf namespace. Returns 409 when the namespace has children or memory placements, or when it changed concurrently.", + "operationId": "DeleteNamespace", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true }, { - "name": "sessionId", + "name": "namespaceId", "in": "path", - "description": "The session ID.", + "description": "Server-generated namespace identifier.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "required": true - }, - { - "name": "eventId", - "in": "path", - "description": "The event ID.", - "schema": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "The event ID." + "description": "Server-generated namespace identifier." }, "required": true } ], "responses": { - "200": { - "description": "GetSessionEvent 200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetSessionEventResponseContent" - } - } - } + "204": { + "description": "DeleteNamespace 204 response" }, "400": { "description": "BadRequestError 400 response", @@ -2893,6 +2935,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -2943,310 +2995,4826 @@ } } } - } - } - } - }, - "components": { - "schemas": { - "AddSessionEventRequestContent": { - "type": "object", - "properties": { - "sessionId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional session ID. If omitted, the server generates a unique ID.", - "example": "session-user42-20240315" - }, - "actorId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant.", - "example": "user-42" - }, - "namespace": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional deprecated namespace label for the session. This label does not create or resolve a namespace resource. Do not supply it with namespaceRef.\n\nLike `ownerId`, this is a session-level value taken from the first event: the value sent with the first event of a session is stored on the session, and values sent with later events are ignored. Omit it to leave extracted memories without a namespace.", - "deprecated": true - }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRefInput", - "example": { - "namespaceId": "ns-product" - } - }, - "role": { - "$ref": "#/components/schemas/MessageRole", - "example": "USER" - }, - "content": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Content" - }, - "description": "The message content as an array of typed content parts.", - "example": [ - { - "text": "What were the action items from last week's meeting?" - } - ] - }, - "createdAt": { - "type": "string", - "description": "Client-supplied timestamp for when the event happened (UTC).", - "format": "date-time", - "example": "2024-03-15T10:00:00Z" - }, - "metadata": { - "description": "Optional metadata as any valid JSON value." - } }, - "required": [ - "actorId", - "content", - "createdAt", - "role" - ], - "example": { - "sessionId": "session-user42-20240315", - "actorId": "user-42", - "role": "USER", - "createdAt": "2024-03-15T10:00:00Z", - "content": [ - { - "text": "What were the action items from last week's meeting?" - } - ], - "namespaceRef": { - "namespaceId": "ns-product" - } - } + "tags": [ + "Namespaces" + ] }, - "AddSessionEventResponseContent": { - "type": "object", - "description": "Response returned after appending a session event.", - "properties": { - "event": { + "get": { + "description": "Gets one namespace by its canonical ID.", + "operationId": "GetNamespace", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "namespaceId", + "in": "path", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetNamespace 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetNamespaceResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Namespaces" + ] + }, + "patch": { + "description": "Renames or archives a namespace. Returns 409 when the namespace changed concurrently, an active or archived sibling already uses the requested name, or the namespace is archived.", + "operationId": "UpdateNamespace", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateNamespaceRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "namespaceId", + "in": "path", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateNamespace 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateNamespaceResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Namespaces" + ] + } + }, + "/v1/stores/{storeId}/session-memory": { + "get": { + "description": "Returns a paginated list of session IDs for a store.", + "operationId": "ListSessions", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page." + } + }, + { + "name": "filterOwnerId", + "in": "query", + "description": "Filter sessions by owner. The owner of a session is the actorId of its first event. Matching is exact and case-sensitive. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by owner. The owner of a session is the actorId of its first event. Matching is exact and case-sensitive. Mutually exclusive with includeAll." + } + }, + { + "name": "namespaceRef", + "in": "query", + "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll." + } + }, + { + "name": "includeAll", + "in": "query", + "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters.", + "schema": { + "type": "boolean", + "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters." + } + } + ], + "responses": { + "200": { + "description": "ListSessions 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSessionsResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/session-memory/events": { + "post": { + "description": "Appends a single event to a session. Creates the session if it does not exist. If sessionId is omitted, the server generates one.", + "operationId": "AddSessionEvent", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddSessionEventRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + } + ], + "responses": { + "201": { + "description": "AddSessionEvent 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddSessionEventResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "422": { + "description": "IngestionHookStoppedError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookStoppedErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + }, + "503": { + "description": "IngestionHookRetryError 503 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookRetryErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/session-memory/{sessionId}": { + "delete": { + "description": "Deletes the session memory for a session.", + "operationId": "DeleteSessionMemory", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteSessionMemory 204 response" + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + }, + "get": { + "description": "Returns the session memory for a session.", + "operationId": "GetSessionMemory", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + }, + { + "name": "includeSummarisedEvents", + "in": "query", + "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage.", + "schema": { + "type": "boolean", + "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage." + } + } + ], + "responses": { + "200": { + "description": "GetSessionMemory 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSessionMemoryResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/session-memory/{sessionId}/events/{eventId}": { + "delete": { + "description": "Deletes a single event from a session by event ID.", + "operationId": "DeleteSessionEvent", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + }, + { + "name": "eventId", + "in": "path", + "description": "The event ID.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "The event ID." + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteSessionEvent 204 response" + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + }, + "get": { + "description": "Returns a single event from a session by event ID.", + "operationId": "GetSessionEvent", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + }, + { + "name": "eventId", + "in": "path", + "description": "The event ID.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "The event ID." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetSessionEvent 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSessionEventResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/sessions": { + "get": { + "description": "Returns paginated session rows with metadata and caller-selected order. The ID-only ListSessions operation remains unchanged.", + "operationId": "ListSessionSummaries", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page. Bound to the effective filters and sort.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page. Bound to the effective filters and sort." + } + }, + { + "name": "filterOwnerId", + "in": "query", + "description": "Filter sessions by owner. The owner of a session is the actorId of its first event. Matching is exact and case-sensitive. Combined with namespaceRef using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by owner. The owner of a session is the actorId of its first event. Matching is exact and case-sensitive. Combined with namespaceRef using AND. Mutually exclusive with includeAll." + } + }, + { + "name": "namespaceRef", + "in": "query", + "description": "Filter by stable namespace ID. Combined with filterOwnerId using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter by stable namespace ID. Combined with filterOwnerId using AND. Mutually exclusive with includeAll." + } + }, + { + "name": "includeAll", + "in": "query", + "description": "Set to true to list all visible sessions. Required when no filter is given; mutually exclusive with filters.", + "schema": { + "type": "boolean", + "description": "Set to true to list all visible sessions. Required when no filter is given; mutually exclusive with filters." + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Sort field. Defaults to updatedAt.", + "schema": { + "$ref": "#/components/schemas/SessionListSortBy" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort direction. Defaults to desc for timestamps and asc for sessionId.", + "schema": { + "$ref": "#/components/schemas/SessionListSortOrder" + } + } + ], + "responses": { + "200": { + "description": "ListSessionSummaries 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSessionSummariesResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/sources/{sourceId}/memories": { + "get": { + "description": "Pages memories linked to one source. A source link does not grant access to a memory in another owner or namespace.", + "operationId": "ListSourceMemories", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "sourceId", + "in": "path", + "description": "Stable source identifier for one revisioned origin in a store.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable source identifier for one revisioned origin in a store." + }, + "required": true + }, + { + "name": "ownerId", + "in": "query", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ListSourceMemories 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSourceMemoriesResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Long-Term Memory" + ] + } + }, + "/v1/stores/{storeId}/summary-views": { + "get": { + "description": "Lists Summary View definitions and high-level state. Partition values are not included.", + "operationId": "ListSummaryViews", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 100, + "maximum": 100, + "minimum": 1, + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ListSummaryViews 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSummaryViewsResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + }, + "post": { + "description": "Creates a Summary View definition and asynchronously enqueues its initial reconciliation.", + "operationId": "CreateSummaryView", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSummaryViewRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateSummaryView 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "SummaryViewQuotaError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewQuotaErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + } + }, + "/v1/stores/{storeId}/summary-views/{viewId}": { + "delete": { + "description": "Marks a Summary View deleting and asynchronously removes its schedule and materializations in bounded batches. Repeated requests are idempotent.", + "operationId": "DeleteSummaryView", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + } + ], + "responses": { + "202": { + "description": "DeleteSummaryView 202 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + }, + "get": { + "description": "Returns one Summary View definition without partition values.", + "operationId": "GetSummaryView", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetSummaryView 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + }, + "patch": { + "description": "Updates mutable Summary View fields. Source is immutable. Material changes increment definitionVersion; name- or description-only changes do not enqueue refresh work.", + "operationId": "UpdateSummaryView", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSummaryViewRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateSummaryView 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "SummaryViewQuotaError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewQuotaErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + } + }, + "/v1/stores/{storeId}/summary-views/{viewId}/refresh": { + "post": { + "description": "Asynchronously enqueues one exact partition refresh or a complete view reconciliation. It never performs generation synchronously.", + "operationId": "RefreshSummaryView", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RefreshSummaryViewRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + } + ], + "responses": { + "202": { + "description": "RefreshSummaryView 202 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RefreshSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "SummaryViewQuotaError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewQuotaErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + } + }, + "/v1/stores/{storeId}/summary-views/{viewId}/result": { + "get": { + "description": "Returns one authorized exact Summary View partition after validating its contributing source records. Invalid output is withheld while repair is pending.", + "operationId": "GetSummaryViewResult", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + }, + { + "name": "ownerId", + "in": "query", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + } + }, + { + "name": "sessionId", + "in": "query", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + } + }, + { + "name": "namespaceRef", + "in": "query", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + } + }, + { + "name": "memoryType", + "in": "query", + "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, `message`, and `session_summary_view`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes.\n", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, `message`, and `session_summary_view`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes." + } + }, + { + "name": "includeSourceMemoryIds", + "in": "query", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "GetSummaryViewResult 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSummaryViewResultResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + } + } + }, + "components": { + "schemas": { + "AddSessionEventRequestContent": { + "type": "object", + "properties": { + "idempotencyKey": { + "type": "string", + "maxLength": 128, + "minLength": 1, + "pattern": "^[ -~]+$", + "description": "Required when the store selects session.append input hooks. Reuse this key only for an identical request; accepted retries reuse the saved decision and event identity." + }, + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Optional session ID. If omitted, the server generates a unique ID." + }, + "actorId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Optional deprecated namespace label for the session. This label does not create or resolve a namespace resource. Do not supply it with namespaceRef.\n\nLike `ownerId`, this is a session-level value taken from the first event: the value sent with the first event of a session is stored on the session, and values sent with later events are ignored. Omit it to leave extracted memories without a namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" + }, + "role": { + "$ref": "#/components/schemas/MessageRole" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Content" + }, + "description": "The message content as an array of typed content parts." + }, + "createdAt": { + "type": "string", + "description": "Client-supplied timestamp for when the event happened (UTC).", + "format": "date-time" + }, + "metadata": { + "description": "Optional metadata as any valid JSON value." + } + }, + "required": [ + "actorId", + "content", + "createdAt", + "role" + ] + }, + "AddSessionEventResponseContent": { + "type": "object", + "description": "Response returned after appending a session event.", + "properties": { + "event": { + "$ref": "#/components/schemas/SessionEvent" + } + }, + "required": [ + "event" + ] + }, + "AttributeFilterClause": { + "description": "Filter clause for a single record attribute. Exactly one member is set; the member must match the attribute's declared field type.", + "oneOf": [ + { + "type": "object", + "title": "string", + "properties": { + "string": { + "type": "string", + "description": "Match a string attribute equal to this value." + } + }, + "required": [ + "string" + ] + }, + { + "type": "object", + "title": "number", + "properties": { + "number": { + "type": "number", + "description": "Match a numeric attribute equal to this value.", + "format": "double" + } + }, + "required": [ + "number" + ] + }, + { + "type": "object", + "title": "boolean", + "properties": { + "boolean": { + "type": "boolean", + "description": "Match a boolean attribute equal to this value." + } + }, + "required": [ + "boolean" + ] + }, + { + "type": "object", + "title": "list", + "properties": { + "list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Match a list attribute that contains all of these values." + } + }, + "required": [ + "list" + ] + } + ] + }, + "AttributeFilterMap": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/AttributeFilterClause" + }, + "description": "Per-attribute filter clauses keyed by the custom memory type's field name." + }, + "AttributeMap": { + "type": "object", + "additionalProperties": {}, + "description": "Structured record attributes: a map from the custom memory type's field name to that field's value. Values are arbitrary JSON matching the field's declared type (str, int, float, bool, list[str], list[float], or object)." + }, + "AuthenticationErrorResponseContent": { + "type": "object", + "description": "Authentication credentials are missing, malformed, or invalid.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 401, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/AuthenticationErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "AuthenticationErrorType": { + "type": "string", + "description": "Problem type URI for authentication errors.", + "enum": [ + "/errors/authentication-failed" + ] + }, + "BadRequestErrorResponseContent": { + "type": "object", + "description": "Request validation or input decoding failed.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 400, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/BadRequestErrorType" + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FieldError" + }, + "description": "Optional field-level validation errors." + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "BadRequestErrorType": { + "type": "string", + "description": "Problem type URI for bad request errors.", + "enum": [ + "/errors/invalid-data" + ] + }, + "BulkCreateLongTermMemoriesRequestContent": { + "type": "object", + "description": "Bulk create request for long-term memory records.", + "properties": { + "idempotencyKey": { + "type": "string", + "maxLength": 128, + "minLength": 1, + "pattern": "^[ -~]+$", + "description": "Required when the memory.create input hook is selected. Reuse the same key and original request to resume its saved decision." + }, + "memories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateMemoryRecord" + }, + "maxItems": 100, + "minItems": 1, + "description": "List of memory records to create." + } + }, + "required": [ + "memories" + ] + }, + "BulkCreateLongTermMemoriesResponseContent": { + "type": "object", + "description": "Bulk create result for long-term memory records.", + "properties": { + "created": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "description": "IDs of successfully created memory records. Always returned; empty when no records were created." + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkOperationError" + }, + "description": "Errors for memory records that failed to be created." + } + }, + "required": [ + "created" + ] + }, + "BulkDeleteLongTermMemoriesRequestContent": { + "type": "object", + "description": "Bulk delete request for long-term memory records.", + "properties": { + "memoryIds": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "maxItems": 100, + "minItems": 1, + "description": "List of memory record IDs to delete (1-100 IDs)." + } + }, + "required": [ + "memoryIds" + ] + }, + "BulkDeleteLongTermMemoriesResponseContent": { + "type": "object", + "description": "Bulk delete result for long-term memory records.", + "properties": { + "deleted": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "description": "IDs of successfully deleted memory records. Always returned; empty when no records were deleted." + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkOperationError" + }, + "description": "Errors for memory records that failed to be deleted." + } + }, + "required": [ + "deleted" + ] + }, + "BulkOperationError": { + "type": "object", + "description": "Error detail for a single item in a bulk operation.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record ID of the item that failed." + }, + "error": { + "type": "string", + "description": "Error message describing the failure." + } + }, + "required": [ + "error", + "id" + ] + }, + "ConflictErrorResponseContent": { + "type": "object", + "description": "The request conflicts with the current state of the resource.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 409, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/ConflictErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "ConflictErrorType": { + "type": "string", + "description": "Problem type URI for conflict errors.", + "enum": [ + "/errors/conflict" + ] + }, + "Content": { + "description": "A single content item in a message. Exactly one member is set.", + "oneOf": [ + { + "type": "object", + "title": "text", + "properties": { + "text": { + "type": "string", + "description": "Text content." + } + }, + "required": [ + "text" + ] + } + ] + }, + "CreateDocumentIngestionRequestContent": { + "type": "object", + "properties": { + "documentId": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "Customer-controlled logical document ID. Opaque and case-sensitive." + }, + "revision": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "Customer-controlled immutable revision token. Opaque and case-sensitive." + }, + "content": { + "type": "string", + "format": "byte" + }, + "mediaType": { + "$ref": "#/components/schemas/DocumentMediaType" + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + }, + "extractorId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Extractor identifier (1-64 chars, alphanumeric and dashes)." + }, + "idempotencyKey": { + "type": "string", + "maxLength": 128, + "minLength": 1, + "description": "Customer idempotency key for one document submission." + } + }, + "required": [ + "content", + "documentId", + "extractorId", + "idempotencyKey", + "mediaType", + "ownerId", + "revision" + ] + }, + "CreateDocumentIngestionResponseContent": { + "type": "object", + "properties": { + "ingestionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." + }, + "status": { + "$ref": "#/components/schemas/DocumentIngestionStatus" + } + }, + "required": [ + "ingestionId", + "status" + ] + }, + "CreateMemoryRecord": { + "type": "object", + "description": "A memory record to create.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Client-provided memory record ID for idempotent creation." + }, + "text": { + "type": "string", + "maxLength": 50000, + "minLength": 1, + "description": "The memory content (1-50000 chars)." + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Type of memory. Built-in or a custom type registered on the store." + }, + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session ID associated with this memory (1-64 chars, alphanumeric and dashes)." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner ID associated with this memory (1-64 chars, alphanumeric and dashes)." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Optional deprecated namespace label. It does not create or resolve a namespace resource. Do not supply it with namespaceRef. If both fields are omitted, the record is created without a namespace. Automatic routing does not place an un-namespaced record in a shared namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" + }, + "topics": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Topic name (1-100 chars)." + }, + "maxItems": 50, + "description": "Topic tags for categorization (max 50 topics, each 1-100 chars)." + }, + "attributes": { + "$ref": "#/components/schemas/AttributeMap" + } + }, + "required": [ + "id", + "ownerId", + "text" + ] + }, + "CreateNamespaceRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One namespace segment. Use with scope for a root or parentId for a child." + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "A complete hierarchy path to create. Missing segments are created as needed. Mutually exclusive with name and parentId. The path does not encode scope." + }, + "scope": { + "$ref": "#/components/schemas/NamespaceScope" + }, + "parentId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Parent namespace for a direct child." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner for a personal root or path. Required for personal scope; omit for shared roots and children." + } + } + }, + "CreateNamespaceResponseContent": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" + } + }, + "required": [ + "namespace" + ] + }, + "CreateSummaryViewRequestContent": { + "type": "object", + "properties": { + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + } + }, + "required": [ + "groupBy", + "name", + "output", + "refreshPolicy", + "source" + ] + }, + "CreateSummaryViewResponseContent": { + "type": "object", + "description": "An owned Summary View definition. Results are separate derived state and never enter long-term-memory search.", + "properties": { + "id": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Immutable canonical user ID assigned by the server. Clients cannot set ownership." + }, + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "createdAt", + "definitionVersion", + "filterOp", + "groupBy", + "id", + "name", + "output", + "ownerId", + "refreshPolicy", + "source", + "state", + "updatedAt", + "visibility" + ] + }, + "CreatedAtFilter": { + "type": "object", + "description": "Filter by creation timestamp.", + "properties": { + "gt": { + "type": "string", + "description": "After the given timestamp.", + "format": "date-time" + }, + "gte": { + "type": "string", + "description": "At or after the given timestamp.", + "format": "date-time" + }, + "lt": { + "type": "string", + "description": "Before the given timestamp.", + "format": "date-time" + }, + "lte": { + "type": "string", + "description": "At or before the given timestamp.", + "format": "date-time" + }, + "eq": { + "type": "string", + "description": "Exactly at the given timestamp.", + "format": "date-time" + } + } + }, + "DeleteSummaryViewResponseContent": { + "type": "object", + "properties": { + "viewId": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + } + }, + "required": [ + "state", + "viewId" + ] + }, + "DocumentIngestionCleanupState": { + "type": "string", + "enum": [ + "pending", + "deleted", + "failed" + ] + }, + "DocumentIngestionProcessingState": { + "type": "string", + "enum": [ + "queued", + "running", + "succeeded", + "failed", + "partial" + ] + }, + "DocumentIngestionRecord": { + "type": "object", + "properties": { + "ingestionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." + }, + "status": { + "$ref": "#/components/schemas/DocumentIngestionStatus" + } + }, + "required": [ + "ingestionId", + "status" + ] + }, + "DocumentIngestionStatus": { + "type": "object", + "properties": { + "processing": { + "$ref": "#/components/schemas/DocumentIngestionProcessingState" + }, + "cleanup": { + "$ref": "#/components/schemas/DocumentIngestionCleanupState" + }, + "extractorId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Extractor identifier (1-64 chars, alphanumeric and dashes)." + }, + "extractorRevision": { + "type": "integer", + "format": "int64" + }, + "completedChunks": { + "type": "integer", + "format": "int32" + }, + "totalChunks": { + "type": "integer", + "format": "int32" + }, + "createdCount": { + "type": "integer", + "format": "int32" + }, + "filteredCount": { + "type": "integer", + "format": "int32" + }, + "error": { + "type": "string" + }, + "retentionDeadline": { + "type": "number", + "format": "double" + }, + "deletedAt": { + "type": "number", + "format": "double" + } + }, + "required": [ + "cleanup", + "processing" + ] + }, + "DocumentMediaType": { + "type": "string", + "enum": [ + "text/plain", + "text/markdown" + ] + }, + "DocumentSourceIdentity": { + "type": "object", + "properties": { + "documentId": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "Customer-controlled logical document ID. Opaque and case-sensitive." + }, + "revision": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "Customer-controlled immutable revision token. Opaque and case-sensitive." + } + }, + "required": [ + "documentId", + "revision" + ] + }, + "EvidenceProducer": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 128, + "minLength": 1 + } + }, + "required": [ + "name" + ] + }, + "FailedDependencyErrorResponseContent": { + "type": "object", + "description": "A dependent resource required to process the request is unavailable or unhealthy.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 424, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/FailedDependencyErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "FailedDependencyErrorType": { + "type": "string", + "description": "Problem type URI for failed-dependency errors.", + "enum": [ + "/errors/resource-unavailable", + "/errors/database-out-of-memory" + ] + }, + "FieldError": { + "type": "object", + "description": "Validation error details for a single request field.", + "properties": { + "field": { + "type": "string", + "description": "Name of the invalid request field." + }, + "rule": { + "type": "string", + "description": "Validation rule that was violated." + }, + "message": { + "type": "string", + "description": "Human-readable validation error message." + } + }, + "required": [ + "field", + "message", + "rule" + ] + }, + "FilterConjunction": { + "type": "string", + "description": "Filter conjunction mode.", + "enum": [ + "all", + "any" + ] + }, + "ForbiddenErrorResponseContent": { + "type": "object", + "description": "The caller is authenticated but not allowed to access the requested resource.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 403, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/ForbiddenErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "ForbiddenErrorType": { + "type": "string", + "description": "Problem type URI for authorization errors.", + "enum": [ + "/errors/insufficient-permissions" + ] + }, + "GetDocumentIngestionResponseContent": { + "type": "object", + "properties": { + "ingestion": { + "$ref": "#/components/schemas/DocumentIngestionRecord" + } + }, + "required": [ + "ingestion" + ] + }, + "GetIngestionHookStatusResponseContent": { + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/IngestionHookStatus" + } + }, + "required": [ + "status" + ] + }, + "GetLongTermMemoryResponseContent": { + "type": "object", + "description": "A long-term memory record.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Unique memory record ID." + }, + "text": { + "type": "string", + "description": "The memory content." + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Type of memory. Built-in or a custom type registered on the store." + }, + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session ID associated with this memory." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner ID associated with this memory." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" + }, + "topics": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Topic name (1-100 chars)." + }, + "maxItems": 50, + "description": "Topic tags for categorization." + }, + "attributes": { + "$ref": "#/components/schemas/AttributeMap" + }, + "createdAt": { + "type": "string", + "description": "Timestamp when the memory was created (UTC).", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "description": "Timestamp when the memory was last updated (UTC).", + "format": "date-time" + }, + "sources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemorySourceLink" + }, + "description": "Generic source links for this memory. Document sources include the customer document ID and revision that produced the memory." + }, + "sourcesNextPageToken": { + "type": "string", + "description": "Opaque token for additional source links when this response truncated attribution. Absent when every link is included." + }, + "documentManaged": { + "type": "boolean", + "description": "True when this memory was created by document ingestion and is protected from generic overwrite." + } + }, + "required": [ + "createdAt", + "id", + "text", + "updatedAt" + ] + }, + "GetNamespaceResponseContent": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" + } + }, + "required": [ + "namespace" + ] + }, + "GetNamespaceRoutingResponseContent": { + "type": "object", + "properties": { + "policy": { + "$ref": "#/components/schemas/NamespaceRoutingPolicy" + } + }, + "required": [ + "policy" + ] + }, + "GetSessionEventResponseContent": { + "type": "object", + "description": "Response containing a single requested session event.", + "properties": { + "event": { "$ref": "#/components/schemas/SessionEvent" } }, "required": [ - "event" + "event" + ] + }, + "GetSessionMemoryResponseContent": { + "type": "object", + "description": "Session memory state for a session.", + "properties": { + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The owner of the session, set from the actorId of the first event." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated namespace label for the session, set from the first event. Absent when the session uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" + }, + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionEvent" + }, + "description": "Conversation events retained for this session. When summarization is enabled, events already covered by the summary are excluded unless `includeSummarisedEvents` is set; the compacted summary is returned separately in `summary`." + }, + "summary": { + "$ref": "#/components/schemas/SessionSummary" + } + }, + "required": [ + "events", + "ownerId", + "sessionId" + ] + }, + "GetSummaryViewResponseContent": { + "type": "object", + "description": "An owned Summary View definition. Results are separate derived state and never enter long-term-memory search.", + "properties": { + "id": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Immutable canonical user ID assigned by the server. Clients cannot set ownership." + }, + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "createdAt", + "definitionVersion", + "filterOp", + "groupBy", + "id", + "name", + "output", + "ownerId", + "refreshPolicy", + "source", + "state", + "updatedAt", + "visibility" + ] + }, + "GetSummaryViewResultResponseContent": { + "type": "object", + "properties": { + "viewId": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "scope": { + "$ref": "#/components/schemas/SummaryViewScope" + }, + "status": { + "$ref": "#/components/schemas/SummaryViewResultStatus" + }, + "stale": { + "type": "boolean" + }, + "format": { + "$ref": "#/components/schemas/SummaryViewOutputFormat" + }, + "value": { + "$ref": "#/components/schemas/SummaryViewResultValue" + }, + "sourceMemoryCount": { + "type": "integer", + "minimum": 0, + "description": "Number of matching source memories. When selection stops early, a separate count query measures the matching total without reading the remaining source pages. Concurrent writes can change the source set between queries; the count is never less than the number of records already observed.", + "format": "int64" + }, + "summarizedMemoryCount": { + "type": "integer", + "minimum": 0, + "description": "Number of matching source memories included in the generated value after bounded deterministic selection.", + "format": "int64" + }, + "truncated": { + "type": "boolean", + "description": "Combined signal for sourcesOmitted or sourceContentTruncated." + }, + "sourcesOmitted": { + "type": "boolean", + "description": "True when matching source memories were excluded from generation, including when further source pages were not read after reaching an input budget." + }, + "sourceContentTruncated": { + "type": "boolean", + "description": "True when content from an included source memory was clipped to fit a source or generation input budget. This can be true even when sourceMemoryCount equals summarizedMemoryCount." + }, + "sourceMemoryIds": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "description": "The memory IDs selected for the materialized value. Included only when requested with includeSourceMemoryIds=true." + }, + "sourceUpdatedAt": { + "type": "string", + "format": "date-time" + }, + "computedAt": { + "type": "string", + "format": "date-time" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "errorCode": { + "$ref": "#/components/schemas/SummaryViewResultErrorCode" + } + }, + "required": [ + "definitionVersion", + "format", + "scope", + "sourceContentTruncated", + "sourceMemoryCount", + "sourcesOmitted", + "stale", + "status", + "summarizedMemoryCount", + "truncated", + "viewId" + ] + }, + "HookStatusSource": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "IngestionHookRetryErrorResponseContent": { + "type": "object", + "description": "A saved ingestion hook decision is waiting for its next bounded attempt.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 503, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/IngestionHookRetryErrorType" + }, + "retryAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "retryAt", + "status", + "title", + "type" + ] + }, + "IngestionHookRetryErrorType": { + "type": "string", + "enum": [ + "/errors/ingestion-hook-retry" + ] + }, + "IngestionHookStatus": { + "type": "object", + "description": "Compact hook metadata, retained for seven days after completion or expiry. Application state is independent of the decision state; earlier writes may have completed before a later failure.", + "properties": { + "eventId": { + "type": "string" + }, + "ingestionOperationId": { + "type": "string" + }, + "phase": { + "type": "string" + }, + "path": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/HookStatusSource" + }, + "configRevision": { + "type": "integer", + "format": "int64" + }, + "state": { + "type": "string" + }, + "applyState": { + "type": "string" + }, + "attempts": { + "type": "integer", + "format": "int32" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "deadline": { + "type": "string", + "format": "date-time" + }, + "nextAttemptAt": { + "type": "string", + "format": "date-time" + }, + "lastError": { + "type": "string" + }, + "applied": { + "type": "integer", + "format": "int32" + }, + "filtered": { + "type": "integer", + "format": "int32" + }, + "conflicts": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "applied", + "applyState", + "attempts", + "configRevision", + "conflicts", + "createdAt", + "deadline", + "eventId", + "filtered", + "ingestionOperationId", + "ownerId", + "path", + "phase", + "source", + "state", + "updatedAt" + ] + }, + "IngestionHookStoppedErrorResponseContent": { + "type": "object", + "description": "The ingestion hook stopped this operation without applying unapproved content.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 422, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/IngestionHookStoppedErrorType" + }, + "state": { + "$ref": "#/components/schemas/IngestionHookStoppedState" + } + }, + "required": [ + "state", + "status", + "title", + "type" + ] + }, + "IngestionHookStoppedErrorType": { + "type": "string", + "enum": [ + "/errors/ingestion-hook-stopped" + ] + }, + "IngestionHookStoppedState": { + "type": "string", + "enum": [ + "rejected", + "failed", + "expired", + "cancelled", + "conflict" + ] + }, + "ListDocumentIngestionOutputsResponseContent": { + "type": "object", + "properties": { + "memoryIds": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + } + } + }, + "required": [ + "memoryIds" + ] + }, + "ListIngestionHookStatusesResponseContent": { + "type": "object", + "properties": { + "statuses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IngestionHookStatus" + } + }, + "nextCursor": { + "type": "string" + } + }, + "required": [ + "statuses" + ] + }, + "ListMemorySourceEvidenceResponseContent": { + "type": "object", + "properties": { + "source": { + "$ref": "#/components/schemas/MemorySourceLink" + } + }, + "required": [ + "source" + ] + }, + "ListMemorySourceLinksResponseContent": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemorySourceLink" + } + }, + "nextPageToken": { + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "ListNamespacesResponseContent": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Namespace" + } + }, + "nextPageToken": { + "type": "string", + "description": "Opaque token for fetching the next page. Omitted when no more results." + } + }, + "required": [ + "items" + ] + }, + "ListSessionSummariesResponseContent": { + "type": "object", + "description": "Paginated session rows for a store.", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionListItem" + } + }, + "total": { + "type": "integer", + "format": "int32" + }, + "nextPageToken": { + "type": "string" + } + }, + "required": [ + "items", + "total" + ] + }, + "ListSessionsResponseContent": { + "type": "object", + "description": "Paginated list of session IDs for a store.", + "properties": { + "items": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + }, + "description": "List of session IDs. Always returned; empty when the store has no sessions." + }, + "total": { + "type": "integer", + "description": "Total number of matching sessions.", + "format": "int32" + }, + "nextPageToken": { + "type": "string", + "description": "Opaque token for fetching the next page. Omitted when no more results." + } + }, + "required": [ + "items", + "total" ] }, - "AttributeFilterClause": { - "description": "Filter clause for a single record attribute. Exactly one member is set; the member must match the attribute's declared field type.", - "oneOf": [ - { - "type": "object", - "title": "string", - "properties": { - "string": { - "type": "string", - "description": "Match a string attribute equal to this value." - } - }, - "required": [ - "string" - ] - }, - { - "type": "object", - "title": "number", - "properties": { - "number": { - "type": "number", - "description": "Match a numeric attribute equal to this value.", - "format": "double" - } - }, - "required": [ - "number" - ] + "ListSourceMemoriesResponseContent": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemoryRecord" + } }, - { - "type": "object", - "title": "boolean", - "properties": { - "boolean": { - "type": "boolean", - "description": "Match a boolean attribute equal to this value." - } - }, - "required": [ - "boolean" - ] + "nextPageToken": { + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "ListSummaryViewsResponseContent": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryView" + } }, - { - "type": "object", - "title": "list", - "properties": { - "list": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Match a list attribute that contains all of these values." - } - }, - "required": [ - "list" - ] + "nextPageToken": { + "type": "string" } + }, + "required": [ + "items" ] }, - "AttributeFilterMap": { + "LlmPromptSpec": { "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/AttributeFilterClause" + "description": "Customer-authored prompt evaluated by the LLM routing strategy.", + "properties": { + "prompt": { + "type": "string", + "maxLength": 2000, + "minLength": 1 + } }, - "description": "Per-attribute filter clauses keyed by the custom memory type's field name." + "required": [ + "prompt" + ] }, - "AttributeMap": { + "LongTermMemoryFilter": { "type": "object", - "additionalProperties": {}, - "description": "Structured record attributes: a map from the custom memory type's field name to that field's value. Values are arbitrary JSON matching the field's declared type (str, int, float, bool, list[str], list[float], or object)." + "description": "Grouping of filter conditions for long-term memory search.", + "properties": { + "sessionId": { + "$ref": "#/components/schemas/SessionIdFilter" + }, + "ownerId": { + "$ref": "#/components/schemas/OwnerIdFilter" + }, + "namespace": { + "$ref": "#/components/schemas/NamespaceFilter" + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefFilter" + }, + "unnamespaced": { + "type": "boolean", + "description": "When true, matches only records that have neither a deprecated namespace label nor a namespace resource placement. This scope is always combined with every other filter condition. Do not supply namespace or namespaceRef with this field." + }, + "topics": { + "$ref": "#/components/schemas/TopicsFilter" + }, + "memoryType": { + "$ref": "#/components/schemas/MemoryTypeFilter" + }, + "createdAt": { + "$ref": "#/components/schemas/CreatedAtFilter" + }, + "attributes": { + "$ref": "#/components/schemas/AttributeFilterMap" + } + } }, - "AuthenticationErrorResponseContent": { + "LongTermMemorySearchSortBy": { + "type": "string", + "description": "Timestamp field used to order long-term memory search results.", + "enum": [ + "createdAt", + "updatedAt" + ] + }, + "LongTermMemorySearchSortOrder": { + "type": "string", + "description": "Direction used to order long-term memory search results.", + "enum": [ + "asc", + "desc" + ] + }, + "MatcherKind": { + "type": "string", + "description": "The matching techniques this API supports.", + "enum": [ + "regex" + ] + }, + "MatcherSpec": { "type": "object", - "description": "Authentication credentials are missing, malformed, or invalid.", + "description": "How a detector matches, as a tagged union of techniques.\n\nExactly the block naming the declared kind must be present; a block belonging to\nany other kind is rejected rather than guessed at. Further kinds are additive.", + "properties": { + "kind": { + "$ref": "#/components/schemas/MatcherKind" + }, + "regex": { + "$ref": "#/components/schemas/RegexSpec" + } + }, + "required": [ + "kind" + ] + }, + "MemoryFailedDependencyErrorResponseContent": { + "type": "object", + "description": "A resource or model-provider dependency required by Agent Memory could not complete the request.", "properties": { "title": { "type": "string", "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." }, - "status": { - "type": "integer", - "default": 401, - "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", - "format": "int32" + "status": { + "type": "integer", + "default": 424, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/MemoryFailedDependencyErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "MemoryFailedDependencyErrorType": { + "type": "string", + "description": "Problem type URI for Agent Memory failed-dependency errors.", + "enum": [ + "/errors/resource-unavailable", + "/errors/database-out-of-memory", + "/errors/invalid-model-credentials", + "/errors/model-quota-exceeded" + ] + }, + "MemoryRecord": { + "type": "object", + "description": "A long-term memory record.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Unique memory record ID." + }, + "text": { + "type": "string", + "description": "The memory content." + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Type of memory. Built-in or a custom type registered on the store." + }, + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session ID associated with this memory." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner ID associated with this memory." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" + }, + "topics": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Topic name (1-100 chars)." + }, + "maxItems": 50, + "description": "Topic tags for categorization." + }, + "attributes": { + "$ref": "#/components/schemas/AttributeMap" + }, + "createdAt": { + "type": "string", + "description": "Timestamp when the memory was created (UTC).", + "format": "date-time" }, - "detail": { + "updatedAt": { "type": "string", - "description": "A human-readable explanation specific to this occurrence of the problem." + "description": "Timestamp when the memory was last updated (UTC).", + "format": "date-time" }, - "instance": { + "sources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemorySourceLink" + }, + "description": "Generic source links for this memory. Document sources include the customer document ID and revision that produced the memory." + }, + "sourcesNextPageToken": { "type": "string", - "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + "description": "Opaque token for additional source links when this response truncated attribution. Absent when every link is included." }, - "type": { - "$ref": "#/components/schemas/AuthenticationErrorType" + "documentManaged": { + "type": "boolean", + "description": "True when this memory was created by document ingestion and is protected from generic overwrite." } }, "required": [ - "status", - "title", - "type" - ] - }, - "AuthenticationErrorType": { - "type": "string", - "description": "Problem type URI for authentication errors.", - "enum": [ - "/errors/authentication-failed" + "createdAt", + "id", + "text", + "updatedAt" ] }, - "BadRequestErrorResponseContent": { + "MemorySourceEvidence": { "type": "object", - "description": "Request validation or input decoding failed.", "properties": { - "title": { + "evidenceId": { "type": "string", - "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + "maxLength": 128, + "minLength": 1, + "description": "Identity of a logical extraction occurrence. Delivery retries retain this ID; a new extraction receives a new ID. Absent on legacy evidence." }, - "status": { + "schemaVersion": { "type": "integer", - "default": 400, - "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "description": "Version of the evidence metadata contract, currently 1. Absent on legacy evidence.", "format": "int32" }, - "detail": { + "capturedAt": { "type": "string", - "description": "A human-readable explanation specific to this occurrence of the problem." + "description": "UTC time when the producer prepared the input for extraction, before model invocation. Preserved through checkpoint replay. This is not the source event time.", + "format": "date-time" }, - "instance": { + "producer": { + "$ref": "#/components/schemas/EvidenceProducer" + }, + "ingestionId": { "type": "string", - "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." }, - "type": { - "$ref": "#/components/schemas/BadRequestErrorType" + "chunkId": { + "type": "string" }, - "fields": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FieldError" - }, - "description": "Optional field-level validation errors." + "start": { + "type": "integer", + "format": "int32" + }, + "end": { + "type": "integer", + "format": "int32" } }, "required": [ - "status", - "title", - "type" + "chunkId", + "ingestionId" ] }, - "BadRequestErrorType": { + "MemorySourceKind": { "type": "string", - "description": "Problem type URI for bad request errors.", "enum": [ - "/errors/invalid-data" + "document", + "session" ] }, - "BulkCreateLongTermMemoriesRequestContent": { + "MemorySourceLink": { "type": "object", - "description": "Bulk create request for long-term memory records.", "properties": { - "memories": { + "sourceId": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/MemorySourceKind" + }, + "identity": { + "$ref": "#/components/schemas/DocumentSourceIdentity" + }, + "session": { + "$ref": "#/components/schemas/SessionSourceIdentity" + }, + "evidence": { "type": "array", "items": { - "$ref": "#/components/schemas/CreateMemoryRecord" - }, - "maxItems": 100, - "minItems": 1, - "description": "List of memory records to create." + "$ref": "#/components/schemas/MemorySourceEvidence" + } + }, + "sessionEvidence": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionSourceEvidence" + } + }, + "evidenceNextPageToken": { + "type": "string", + "description": "Opaque token for GET /v1/stores/{storeId}/long-term-memory/{memoryId}/sources/{sourceId}/evidence. Continues evidence or sessionEvidence for this source; absent when every item is included." } }, "required": [ - "memories" + "kind", + "sourceId" ] }, - "BulkCreateLongTermMemoriesResponseContent": { + "MemoryTypeFilter": { "type": "object", - "description": "Bulk create result for long-term memory records.", + "description": "Filter by memory type. Matching is case-insensitive.", "properties": { - "created": { + "eq": { + "type": "string", + "description": "Equals the given value." + }, + "ne": { + "type": "string", + "description": "Not equal to the given value." + }, + "in": { "type": "array", "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + "type": "string" }, - "description": "IDs of successfully created memory records. Always returned; empty when no records were created." + "description": "Matches any of the given values." }, - "errors": { + "all": { "type": "array", "items": { - "$ref": "#/components/schemas/BulkOperationError" + "type": "string" }, - "description": "Errors for memory records that failed to be created." + "description": "Matches all of the given values." } - }, - "required": [ - "created" + } + }, + "MessageRole": { + "type": "string", + "description": "Message role for session events.", + "enum": [ + "USER", + "ASSISTANT", + "SYSTEM" ] }, - "BulkDeleteLongTermMemoriesRequestContent": { + "MoveLongTermMemoriesRequestContent": { "type": "object", - "description": "Bulk delete request for long-term memory records.", "properties": { "memoryIds": { "type": "array", @@ -3258,19 +7826,20 @@ "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." }, "maxItems": 100, - "minItems": 1, - "description": "List of memory record IDs to delete (1-100 IDs)." + "minItems": 1 + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" } }, "required": [ "memoryIds" ] }, - "BulkDeleteLongTermMemoriesResponseContent": { + "MoveLongTermMemoriesResponseContent": { "type": "object", - "description": "Bulk delete result for long-term memory records.", "properties": { - "deleted": { + "moved": { "type": "array", "items": { "type": "string", @@ -3278,341 +7847,314 @@ "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." - }, - "description": "IDs of successfully deleted memory records. Always returned; empty when no records were deleted." + } }, "errors": { "type": "array", "items": { "$ref": "#/components/schemas/BulkOperationError" - }, - "description": "Errors for memory records that failed to be deleted." + } } }, "required": [ - "deleted" + "moved" ] }, - "BulkOperationError": { + "MutableSummaryViewState": { + "type": "string", + "description": "States a caller may set directly. Deleting is entered only by the delete operation.", + "enum": [ + "active", + "disabled" + ] + }, + "Namespace": { "type": "object", - "description": "Error detail for a single item in a bulk operation.", + "description": "A namespace hierarchy record.", "properties": { - "id": { + "namespaceId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Memory record ID of the item that failed." - }, - "error": { - "type": "string", - "description": "Error message describing the failure." - } - }, - "required": [ - "error", - "id" - ] - }, - "ConflictErrorResponseContent": { - "type": "object", - "description": "The request conflicts with the current state of the resource.", - "properties": { - "title": { - "type": "string", - "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." - }, - "status": { - "type": "integer", - "default": 409, - "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", - "format": "int32" + "description": "Server-generated namespace identifier." }, - "detail": { + "name": { "type": "string", - "description": "A human-readable explanation specific to this occurrence of the problem." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One case-sensitive namespace path segment." }, - "instance": { + "parentId": { "type": "string", - "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." }, - "type": { - "$ref": "#/components/schemas/ConflictErrorType" - } - }, - "required": [ - "status", - "title", - "type" - ] - }, - "ConflictErrorType": { - "type": "string", - "description": "Problem type URI for conflict errors.", - "enum": [ - "/errors/conflict" - ] - }, - "Content": { - "description": "A single content item in a message. Exactly one member is set.", - "oneOf": [ - { - "type": "object", - "title": "text", - "properties": { - "text": { - "type": "string", - "description": "Text content.", - "example": "What were the action items from last week's meeting?" - } - }, - "required": [ - "text" - ] - } - ] - }, - "CreateMemoryRecord": { - "type": "object", - "description": "A memory record to create.", - "properties": { - "id": { + "rootId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Client-provided memory record ID for idempotent creation.", - "example": "mem-1" + "description": "Server-generated namespace identifier." + }, + "ownerId": { + "type": "string", + "minLength": 1, + "description": "An opaque application-user ID selected by the platform for a namespace root." + }, + "scope": { + "$ref": "#/components/schemas/NamespaceScope" + }, + "state": { + "$ref": "#/components/schemas/NamespaceState" }, - "text": { + "revision": { + "type": "integer", + "format": "int64" + }, + "createdAt": { "type": "string", - "maxLength": 50000, - "minLength": 1, - "description": "The memory content (1-50000 chars).", - "example": "The user prefers dark mode" + "format": "date-time" }, - "memoryType": { + "updatedAt": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Type of memory. Built-in or a custom type registered on the store." + "format": "date-time" }, - "sessionId": { + "path": { "type": "string", - "maxLength": 64, + "maxLength": 1024, "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session ID associated with this memory (1-64 chars, alphanumeric and dashes)." - }, - "ownerId": { + "description": "Current case-sensitive path, derived from the live hierarchy." + } + }, + "required": [ + "createdAt", + "name", + "namespaceId", + "path", + "revision", + "rootId", + "scope", + "state", + "updatedAt" + ] + }, + "NamespaceFilter": { + "type": "object", + "description": "Filter by namespace. Matching is case-insensitive.\n\nNamespace is optional, so records without one need care. The positive operators (`eq`, `in`, `all`) require the field to be present and therefore never match a record that has no namespace \u2014 including memories extracted from a session that has none. `ne` is a plain negation and *does* match those records, so use `eq` or `in` when you need strict namespace isolation rather than `ne` against the namespaces you want to exclude.\n\nOmit this filter to search across every namespace, and use it to keep recall inside one project, workspace, team, environment, or tenant.", + "properties": { + "eq": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner ID associated with this memory (1-64 chars, alphanumeric and dashes).", - "example": "user-1" + "description": "Equals the given value.", + "deprecated": true }, - "namespace": { + "ne": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional deprecated namespace label. It does not create or resolve a namespace resource. Omit it to create the record without a namespace, and do not supply it with namespaceRef.", + "description": "Not equal to the given value.", "deprecated": true }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRefInput", - "example": { - "namespaceId": "ns-product" - } - }, - "topics": { + "in": { "type": "array", "items": { "type": "string", - "maxLength": 100, + "maxLength": 64, "minLength": 1, - "description": "Topic name (1-100 chars)." + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true }, - "maxItems": 50, - "description": "Topic tags for categorization (max 50 topics, each 1-100 chars)." + "description": "Matches any of the given values." }, - "attributes": { - "$ref": "#/components/schemas/AttributeMap" - } - }, - "required": [ - "id", - "ownerId", - "text" - ], - "example": { - "id": "mem-1", - "text": "The user prefers dark mode", - "ownerId": "user-1", - "namespaceRef": { - "namespaceId": "ns-product" + "all": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true + }, + "description": "Matches all of the given values." } } }, - "CreateNamespaceRequestContent": { + "NamespaceRef": { "type": "object", + "description": "Reference to a namespace resource with its stable identifier and current display data.", "properties": { + "namespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable namespace resource identifier." + }, "name": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9_-]+$", - "description": "One namespace segment. Use with scope for a root or parentId for a child.", - "example": "product" + "description": "Current namespace name." }, "path": { "type": "string", "maxLength": 1024, "minLength": 1, - "description": "A complete hierarchy path to create. Missing segments are created as needed. Mutually exclusive with name and parentId. The path does not encode scope." - }, - "scope": { - "$ref": "#/components/schemas/NamespaceScope", - "example": "SHARED" - }, - "parentId": { + "description": "Current namespace path." + } + }, + "required": [ + "name", + "namespaceId", + "path" + ] + }, + "NamespaceRefFilter": { + "type": "object", + "description": "Filter by stable namespace resource identifier. Supports exact or any-of matching and matches only resource-backed records, never deprecated namespace labels.", + "properties": { + "eq": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Parent namespace for a direct child." + "description": "Equals the given value." }, - "ownerId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner for a personal root or path. Required for personal scope; omit for shared roots and children." + "in": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + }, + "description": "Matches any of the given values." } - }, - "example": { - "name": "product", - "scope": "SHARED" } }, - "CreateNamespaceResponseContent": { + "NamespaceRefInput": { "type": "object", + "description": "Reference to an existing namespace resource for a write or namespace-scoped operation.", "properties": { - "namespace": { - "$ref": "#/components/schemas/Namespace" + "namespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable namespace resource identifier." } }, "required": [ - "namespace" + "namespaceId" ] }, - "CreatedAtFilter": { + "NamespaceRoutingPolicy": { "type": "object", - "description": "Filter by creation timestamp.", + "description": "Store-level automatic namespace routing and admission policy. Global rules target shared namespaces only. Accept rules select candidates in list order. All enabled reject rules for a destination apply regardless of their position or how the destination was selected.", "properties": { - "gt": { - "type": "string", - "description": "After the given timestamp.", - "format": "date-time" - }, - "gte": { - "type": "string", - "description": "At or after the given timestamp.", - "format": "date-time" - }, - "lt": { - "type": "string", - "description": "Before the given timestamp.", - "format": "date-time" - }, - "lte": { - "type": "string", - "description": "At or before the given timestamp.", - "format": "date-time" + "enabled": { + "type": "boolean" }, - "eq": { - "type": "string", - "description": "Exactly at the given timestamp.", - "format": "date-time" + "rules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NamespaceRoutingRule" + }, + "maxItems": 32 } - } + }, + "required": [ + "enabled" + ] }, - "FailedDependencyErrorResponseContent": { + "NamespaceRoutingRule": { "type": "object", - "description": "A dependent resource required to process the request is unavailable or unhealthy.", + "description": "One routing or admission rule. action is accept or reject. Strategies run in list order and the first match fires the rule. An accept with no strategies always matches; a reject with no strategies never matches. A matching accept selects a candidate. Every enabled reject for that destination must pass before placement. A veto skips that candidate during automatic routing and fails explicit placement.", "properties": { - "title": { + "id": { "type": "string", - "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Routing rule identifier (1-64 chars). Published in logs, so it is an identifier rather than free text." }, - "status": { - "type": "integer", - "default": 424, - "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", - "format": "int32" + "destination": { + "$ref": "#/components/schemas/NamespaceRefInput" }, - "detail": { - "type": "string", - "description": "A human-readable explanation specific to this occurrence of the problem." + "enabled": { + "type": "boolean" }, - "instance": { - "type": "string", - "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + "action": { + "$ref": "#/components/schemas/RoutingRuleAction" }, - "type": { - "$ref": "#/components/schemas/FailedDependencyErrorType" + "strategies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RouteCondition" + }, + "maxItems": 8 } }, "required": [ - "status", - "title", - "type" + "action", + "destination", + "enabled", + "id" ] }, - "FailedDependencyErrorType": { + "NamespaceScope": { "type": "string", - "description": "Problem type URI for failed-dependency errors.", + "description": "The root scope of a namespace hierarchy.", "enum": [ - "/errors/resource-unavailable", - "/errors/database-out-of-memory" + "PERSONAL", + "SHARED" ] }, - "FieldError": { - "type": "object", - "description": "Validation error details for a single request field.", - "properties": { - "field": { - "type": "string", - "description": "Name of the invalid request field." - }, - "rule": { - "type": "string", - "description": "Validation rule that was violated." - }, - "message": { - "type": "string", - "description": "Human-readable validation error message." - } - }, - "required": [ - "field", - "message", - "rule" + "NamespaceSortBy": { + "type": "string", + "enum": [ + "id", + "name", + "createdAt", + "updatedAt" ] }, - "FilterConjunction": { + "NamespaceSortOrder": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "NamespaceState": { + "type": "string", + "description": "Whether a namespace accepts new children and placements.", + "enum": [ + "ACTIVE", + "ARCHIVED" + ] + }, + "NamespaceUpdateState": { "type": "string", - "description": "Filter conjunction mode.", + "description": "The archive-only state transition accepted by UpdateNamespace.", "enum": [ - "all", - "any" + "ARCHIVED" ] }, - "ForbiddenErrorResponseContent": { + "NotFoundErrorResponseContent": { "type": "object", - "description": "The caller is authenticated but not allowed to access the requested resource.", + "description": "The requested resource does not exist.", "properties": { "title": { "type": "string", @@ -3620,7 +8162,7 @@ }, "status": { "type": "integer", - "default": 403, + "default": 404, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -3633,7 +8175,7 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/ForbiddenErrorType" + "$ref": "#/components/schemas/NotFoundErrorType" } }, "required": [ @@ -3642,268 +8184,171 @@ "type" ] }, - "ForbiddenErrorType": { + "NotFoundErrorType": { "type": "string", - "description": "Problem type URI for authorization errors.", + "description": "Problem type URI for not-found errors.", "enum": [ - "/errors/insufficient-permissions" + "/errors/resource-not-found" ] }, - "GetLongTermMemoryResponseContent": { + "OwnerIdFilter": { "type": "object", - "description": "A long-term memory record.", + "description": "Filter by the long-term memory ownerId field. Matching is case-sensitive.", "properties": { - "id": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique memory record ID." - }, - "text": { - "type": "string", - "description": "The memory content." - }, - "memoryType": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Type of memory. Built-in or a custom type registered on the store." - }, - "sessionId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session ID associated with this memory." - }, - "ownerId": { + "eq": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner ID associated with this memory." + "description": "Equals the given value." }, - "namespace": { + "ne": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", - "deprecated": true + "description": "Not equal to the given value." }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRef" + "in": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + }, + "description": "Matches any of the given values." }, - "topics": { + "all": { "type": "array", "items": { "type": "string", - "maxLength": 100, + "maxLength": 64, "minLength": 1, - "description": "Topic name (1-100 chars)." + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." }, - "maxItems": 50, - "description": "Topic tags for categorization." + "description": "Matches all of the given values." + } + } + }, + "PayloadTooLargeErrorResponseContent": { + "type": "object", + "description": "The request payload exceeds the maximum supported size.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." }, - "attributes": { - "$ref": "#/components/schemas/AttributeMap" + "status": { + "type": "integer", + "default": 413, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" }, - "createdAt": { + "detail": { "type": "string", - "description": "Timestamp when the memory was created (UTC).", - "format": "date-time" + "description": "A human-readable explanation specific to this occurrence of the problem." }, - "updatedAt": { + "instance": { "type": "string", - "description": "Timestamp when the memory was last updated (UTC).", - "format": "date-time" - } - }, - "required": [ - "createdAt", - "id", - "text", - "updatedAt" - ] - }, - "GetNamespaceResponseContent": { - "type": "object", - "properties": { - "namespace": { - "$ref": "#/components/schemas/Namespace" + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/PayloadTooLargeErrorType" } }, "required": [ - "namespace" + "status", + "title", + "type" ] }, - "GetSessionEventResponseContent": { - "type": "object", - "description": "Response containing a single requested session event.", - "properties": { - "event": { - "$ref": "#/components/schemas/SessionEvent" - } - }, - "required": [ - "event" + "PayloadTooLargeErrorType": { + "type": "string", + "description": "Problem type URI for payload-too-large errors.", + "enum": [ + "/errors/payload-too-large" ] }, - "GetSessionMemoryResponseContent": { + "PutNamespaceRoutingRequestContent": { "type": "object", - "description": "Session memory state for a session.", "properties": { - "sessionId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "ownerId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The owner of the session, set from the actorId of the first event." - }, - "namespace": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Deprecated namespace label for the session, set from the first event. Absent when the session uses namespaceRef or has no namespace.", - "deprecated": true - }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRef" - }, - "events": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SessionEvent" - }, - "description": "Conversation events retained for this session. When summarization is enabled, events already covered by the summary are excluded unless `includeSummarisedEvents` is set; the compacted summary is returned separately in `summary`." - }, - "summary": { - "$ref": "#/components/schemas/SessionSummary" + "policy": { + "$ref": "#/components/schemas/NamespaceRoutingPolicy" } }, "required": [ - "events", - "ownerId", - "sessionId" + "policy" ] }, - "HealthResponseContent": { + "PutNamespaceRoutingResponseContent": { "type": "object", - "description": "Health check response.", "properties": { - "status": { - "$ref": "#/components/schemas/HealthStatus" + "policy": { + "$ref": "#/components/schemas/NamespaceRoutingPolicy" } }, "required": [ - "status" + "policy" ] }, - "HealthStatus": { - "type": "string", - "description": "Health status returned by the /health endpoint.", - "enum": [ - "healthy" - ] - }, - "ListNamespacesResponseContent": { + "RefreshSummaryViewRequestContent": { "type": "object", "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Namespace" - } + "target": { + "$ref": "#/components/schemas/SummaryViewRefreshTarget" }, - "nextPageToken": { - "type": "string", - "description": "Opaque token for fetching the next page. Omitted when no more results." + "scope": { + "$ref": "#/components/schemas/SummaryViewScope" } }, "required": [ - "items" + "target" ] }, - "ListSessionsResponseContent": { + "RefreshSummaryViewResponseContent": { "type": "object", - "description": "Paginated list of session IDs for a store.", "properties": { - "items": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." - }, - "description": "List of session IDs. Always returned; empty when the store has no sessions." + "viewId": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." }, - "total": { - "type": "integer", - "description": "Total number of matching sessions.", - "format": "int32" + "target": { + "$ref": "#/components/schemas/SummaryViewRefreshTarget" }, - "nextPageToken": { - "type": "string", - "description": "Opaque token for fetching the next page. Omitted when no more results." + "accepted": { + "type": "boolean" } }, "required": [ - "items", - "total" + "accepted", + "target", + "viewId" ] - }, - "LongTermMemoryFilter": { - "type": "object", - "description": "Grouping of filter conditions for long-term memory search.", - "properties": { - "sessionId": { - "$ref": "#/components/schemas/SessionIdFilter" - }, - "ownerId": { - "$ref": "#/components/schemas/OwnerIdFilter" - }, - "namespace": { - "$ref": "#/components/schemas/NamespaceFilter" - }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRefFilter", - "example": { - "eq": "ns-product" - } - }, - "unnamespaced": { - "type": "boolean", - "description": "When true, matches only records that have neither a deprecated namespace label nor a namespace resource placement. This scope is always combined with every other filter condition. Do not supply namespace or namespaceRef with this field." - }, - "topics": { - "$ref": "#/components/schemas/TopicsFilter" - }, - "memoryType": { - "$ref": "#/components/schemas/MemoryTypeFilter" - }, - "createdAt": { - "$ref": "#/components/schemas/CreatedAtFilter" - }, - "attributes": { - "$ref": "#/components/schemas/AttributeFilterMap" + }, + "RegexSpec": { + "type": "object", + "description": "A regular expression to match against.\n\nEvaluated by RE2, which runs in linear time \u2014 so an expensive pattern is not a\ndenial-of-service risk, but lookaround and backreferences are unavailable.\nRejected on write if it does not compile, or if it can match without consuming\ntext: such a pattern matches at every position, so it would exclude every memory\nthe store writes.", + "properties": { + "pattern": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "The pattern, in RE2 syntax." } - } + }, + "required": [ + "pattern" + ] }, - "MemoryFailedDependencyErrorResponseContent": { + "ResourceSuspendedErrorResponseContent": { "type": "object", - "description": "A resource or model-provider dependency required by Agent Memory could not complete the request.", + "description": "The requested resource exists but is suspended by an administrator.", "properties": { "title": { "type": "string", @@ -3911,7 +8356,7 @@ }, "status": { "type": "integer", - "default": 424, + "default": 423, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -3924,7 +8369,7 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/MemoryFailedDependencyErrorType" + "$ref": "#/components/schemas/ResourceSuspendedErrorType" } }, "required": [ @@ -3933,281 +8378,188 @@ "type" ] }, - "MemoryFailedDependencyErrorType": { + "ResourceSuspendedErrorType": { "type": "string", - "description": "Problem type URI for Agent Memory failed-dependency errors.", + "description": "Problem type URI for suspended resources.", "enum": [ - "/errors/resource-unavailable", - "/errors/database-out-of-memory", - "/errors/invalid-model-credentials", - "/errors/model-quota-exceeded" + "/errors/resource-suspended" ] }, - "MemoryRecord": { + "RouteCondition": { "type": "object", - "description": "A long-term memory record.", + "description": "One plug-in strategy invocation. Exactly the block naming the declared kind must be present. List order is evaluation order.", "properties": { - "id": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique memory record ID." - }, - "text": { - "type": "string", - "description": "The memory content." - }, - "memoryType": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Type of memory. Built-in or a custom type registered on the store." - }, - "sessionId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session ID associated with this memory." - }, - "ownerId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner ID associated with this memory." - }, - "namespace": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", - "deprecated": true - }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRef" - }, - "topics": { - "type": "array", - "items": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "description": "Topic name (1-100 chars)." - }, - "maxItems": 50, - "description": "Topic tags for categorization." - }, - "attributes": { - "$ref": "#/components/schemas/AttributeMap" + "kind": { + "$ref": "#/components/schemas/RouteStrategyKind" }, - "createdAt": { - "type": "string", - "description": "Timestamp when the memory was created (UTC).", - "format": "date-time" + "llmPrompt": { + "$ref": "#/components/schemas/LlmPromptSpec" }, - "updatedAt": { - "type": "string", - "description": "Timestamp when the memory was last updated (UTC).", - "format": "date-time" + "matcher": { + "$ref": "#/components/schemas/MatcherSpec" } }, "required": [ - "createdAt", - "id", - "text", - "updatedAt" + "kind" ] }, - "MemoryTypeFilter": { - "type": "object", - "description": "Filter by memory type. Matching is case-insensitive.", - "properties": { - "eq": { - "type": "string", - "description": "Equals the given value." - }, - "ne": { - "type": "string", - "description": "Not equal to the given value." - }, - "in": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Matches any of the given values." - }, - "all": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Matches all of the given values." - } - } + "RouteStrategyKind": { + "type": "string", + "description": "Routing strategy kinds. Unknown kinds are rejected. New kinds register on the evaluator without changing namespace records.", + "enum": [ + "llm_prompt", + "matcher_spec" + ] }, - "MessageRole": { + "RoutingRuleAction": { "type": "string", - "description": "Message role for session events.", + "description": "Whether the rule accepts into the destination or rejects that destination.", "enum": [ - "USER", - "ASSISTANT", - "SYSTEM" + "accept", + "reject" ] }, - "MoveLongTermMemoriesRequestContent": { + "SearchLongTermMemoryRequestContent": { "type": "object", + "description": "Semantic search request for long-term memory records.", "properties": { - "memoryIds": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." - }, - "maxItems": 100, - "minItems": 1 + "text": { + "type": "string", + "description": "Text to use for semantic search." }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRefInput" - } - }, - "required": [ - "memoryIds", - "namespaceRef" - ], - "example": { - "memoryIds": [ - "mem-1" - ], - "namespaceRef": { - "namespaceId": "ns-product" + "similarityThreshold": { + "type": "number", + "maximum": 1, + "minimum": 0, + "description": "The minimum similarity threshold for vector search results (normalized cosine similarity). Allowed range: 0-1.", + "format": "float" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "excludeDocumentManaged": { + "type": "boolean", + "description": "When true, omit document-managed memories from results. Automatic session reconciliation uses this; public search omits it so document memories remain visible." + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "sortBy": { + "$ref": "#/components/schemas/LongTermMemorySearchSortBy" + }, + "sortOrder": { + "$ref": "#/components/schemas/LongTermMemorySearchSortOrder" + }, + "limit": { + "type": "integer", + "default": 10, + "maximum": 100, + "minimum": 1, + "description": "Maximum number of results to return. Defaults to 10. Allowed range: 1-100.", + "format": "int32" + }, + "pageToken": { + "type": "string", + "description": "Opaque token from a previous response for the next page." } } }, - "MoveLongTermMemoriesResponseContent": { + "SearchLongTermMemoryResponseContent": { "type": "object", + "description": "Semantic search result for long-term memory records.", "properties": { - "moved": { + "items": { "type": "array", "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." - } + "$ref": "#/components/schemas/MemoryRecord" + }, + "description": "List of matching memory records. Always returned; empty when no records match." }, - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BulkOperationError" - } + "nextPageToken": { + "type": "string", + "description": "Opaque token for fetching the next page. Omitted when no more results." } }, "required": [ - "moved" + "items" ] }, - "Namespace": { + "SessionEvent": { "type": "object", - "description": "A namespace hierarchy record.", + "description": "A single event in the session history.", "properties": { - "namespaceId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." - }, - "name": { + "eventId": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9_-]+$", - "description": "One case-sensitive namespace path segment." + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Auto-generated event ID." }, - "parentId": { + "actorId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." + "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant." }, - "rootId": { + "sessionId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." - }, - "ownerId": { - "type": "string", - "minLength": 1, - "description": "An opaque application-user ID selected by the platform for a namespace root." - }, - "scope": { - "$ref": "#/components/schemas/NamespaceScope" + "description": "Session identifier (client-provided or server-generated)." }, - "state": { - "$ref": "#/components/schemas/NamespaceState" + "role": { + "$ref": "#/components/schemas/MessageRole" }, - "revision": { - "type": "integer", - "format": "int64" + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Content" + }, + "description": "The message content as an array of typed content parts." }, "createdAt": { "type": "string", + "description": "Client-supplied timestamp for when the event happened (UTC).", "format": "date-time" }, - "updatedAt": { + "systemTimestamp": { "type": "string", + "description": "Server-set timestamp for when the dataplane ingested the event (UTC).", "format": "date-time" }, - "path": { - "type": "string", - "maxLength": 1024, - "minLength": 1, - "description": "Current case-sensitive path, derived from the live hierarchy." + "metadata": { + "description": "Optional metadata as any valid JSON value." } }, "required": [ + "actorId", + "content", "createdAt", - "name", - "namespaceId", - "path", - "revision", - "rootId", - "scope", - "state", - "updatedAt" + "eventId", + "role", + "sessionId", + "systemTimestamp" ] }, - "NamespaceFilter": { + "SessionIdFilter": { "type": "object", - "description": "Filter by namespace. Matching is case-insensitive.\n\nNamespace is optional, so records without one need care. The positive operators (`eq`, `in`, `all`) require the field to be present and therefore never match a record that has no namespace \u2014 including memories extracted from a session that has none. `ne` is a plain negation and *does* match those records, so use `eq` or `in` when you need strict namespace isolation rather than `ne` against the namespaces you want to exclude.\n\nOmit this filter to search across every namespace, and use it to keep recall inside one project, workspace, team, environment, or tenant.", + "description": "Filter by the long-term memory sessionId field. Matching is case-sensitive.", "properties": { "eq": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value.", - "deprecated": true + "description": "Equals the given value." }, "ne": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Not equal to the given value.", - "deprecated": true + "description": "Not equal to the given value." }, "in": { "type": "array", @@ -4216,8 +8568,7 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", - "deprecated": true + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." }, "description": "Matches any of the given values." }, @@ -4228,115 +8579,307 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", - "deprecated": true + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." }, "description": "Matches all of the given values." } } }, - "NamespaceRef": { + "SessionListItem": { "type": "object", - "description": "Reference to a namespace resource with its stable identifier and current display data.", + "description": "One session row without events or summary text. Owner ID and timestamps are absent when unknown on legacy sessions.", "properties": { - "namespaceId": { + "sessionId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Stable namespace resource identifier." + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." }, - "name": { + "ownerId": { "type": "string", "maxLength": 64, "minLength": 1, - "pattern": "^[a-zA-Z0-9_-]+$", - "description": "Current namespace name." + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The owner of the session, set from the actorId of the first event. Absent for a legacy session that has no recorded owner." }, - "path": { + "createdAt": { "type": "string", - "maxLength": 1024, - "minLength": 1, - "description": "Current namespace path." + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" } }, "required": [ - "name", - "namespaceId", - "path" + "sessionId" ] }, - "NamespaceRefFilter": { + "SessionListSortBy": { + "type": "string", + "enum": [ + "sessionId", + "createdAt", + "updatedAt" + ] + }, + "SessionListSortOrder": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "SessionSourceEvidence": { "type": "object", - "description": "Filter by stable namespace resource identifier. Supports exact or any-of matching and matches only resource-backed records, never deprecated namespace labels.", "properties": { - "eq": { + "evidenceId": { "type": "string", - "maxLength": 64, + "maxLength": 128, "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value.", - "example": "ns-product" + "description": "Identity of a logical extraction occurrence. Delivery retries retain this ID; a new extraction receives a new ID. Absent on legacy evidence." }, - "in": { + "schemaVersion": { + "type": "integer", + "description": "Version of the evidence metadata contract, currently 1. Absent on legacy evidence.", + "format": "int32" + }, + "capturedAt": { + "type": "string", + "description": "UTC time when the producer prepared the input for extraction, before model invocation. Preserved through checkpoint replay. This is not the source event time.", + "format": "date-time" + }, + "producer": { + "$ref": "#/components/schemas/EvidenceProducer" + }, + "eventIds": { "type": "array", "items": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated event identifier (exactly 32 alphanumeric characters, UUID without dashes)." }, - "description": "Matches any of the given values." + "description": "Ordered IDs of events whose content was supplied to one model request. Input attribution, not fact citations." + } + }, + "required": [ + "eventIds" + ] + }, + "SessionSourceIdentity": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + } + }, + "required": [ + "ownerId", + "sessionId" + ] + }, + "SessionSummary": { + "type": "object", + "description": "Compacted summary of older session history for a session.\n\nThis is derived session state maintained by the background summarization workflow, not a conversation event. It is present only after at least one summarization cycle has completed.", + "properties": { + "text": { + "type": "string", + "description": "The summary text, a single coherent block." + }, + "summarizedUpToEventId": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Boundary event ID. This event and all events before it (by array position) are covered by the summary; `events` contains the retained turns after this boundary." + }, + "createdAt": { + "type": "string", + "description": "Timestamp when the summary was first created (UTC). Preserved across re-summarization cycles.", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "description": "Timestamp when the summary was last regenerated (UTC). Updated on every summarization cycle. Equal to createdAt on the first cycle.", + "format": "date-time" + }, + "metadata": { + "description": "Metadata as a JSON object (e.g., model name, token count). Always present; defaults to an empty object when no metadata was supplied." + }, + "summarizedEvents": { + "type": "integer", + "minimum": 1, + "description": "Cumulative number of session events folded into this summary across all summarization cycles. Always positive. A value of 1 may indicate a summary created before this field was tracked; it self-corrects to a real count on the next summarization cycle.", + "format": "int64" + } + }, + "required": [ + "createdAt", + "metadata", + "summarizedEvents", + "summarizedUpToEventId", + "text", + "updatedAt" + ] + }, + "StoreHealthFeature": { + "type": "object", + "description": "Feature-level store health.", + "properties": { + "status": { + "$ref": "#/components/schemas/StoreHealthStatus" } + }, + "required": [ + "status" + ] + }, + "StoreHealthFeatureMap": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/StoreHealthFeature" } }, - "NamespaceRefInput": { + "StoreHealthResponseContent": { "type": "object", - "description": "Reference to an existing namespace resource for a write or namespace-scoped operation.", + "description": "Store-scoped RAM feature health.", "properties": { - "namespaceId": { + "status": { + "$ref": "#/components/schemas/StoreHealthStatus" + }, + "features": { + "$ref": "#/components/schemas/StoreHealthFeatureMap" + } + }, + "required": [ + "features", + "status" + ] + }, + "StoreHealthStatus": { + "type": "string", + "description": "Store health status.", + "enum": [ + "healthy", + "degraded", + "unavailable" + ] + }, + "SummaryView": { + "type": "object", + "description": "An owned Summary View definition. Results are separate derived state and never enter long-term-memory search.", + "properties": { + "id": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Immutable canonical user ID assigned by the server. Clients cannot set ownership." + }, + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + }, + "createdAt": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Stable namespace resource identifier.", - "example": "ns-product" + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" } }, "required": [ - "namespaceId" - ], - "example": { - "namespaceId": "ns-product" - } - }, - "NamespaceScope": { - "type": "string", - "description": "The root scope of a namespace hierarchy.", - "enum": [ - "PERSONAL", - "SHARED" - ] - }, - "NamespaceState": { - "type": "string", - "description": "Whether a namespace accepts new children and placements.", - "enum": [ - "ACTIVE", - "ARCHIVED" - ] - }, - "NamespaceUpdateState": { - "type": "string", - "description": "The archive-only state transition accepted by UpdateNamespace.", - "enum": [ - "ARCHIVED" + "createdAt", + "definitionVersion", + "filterOp", + "groupBy", + "id", + "name", + "output", + "ownerId", + "refreshPolicy", + "source", + "state", + "updatedAt", + "visibility" ] }, - "NotFoundErrorResponseContent": { + "SummaryViewConflictErrorResponseContent": { "type": "object", - "description": "The requested resource does not exist.", + "description": "A Summary View operation conflicts with the current definition or partition state.", "properties": { "title": { "type": "string", @@ -4344,7 +8887,7 @@ }, "status": { "type": "integer", - "default": 404, + "default": 409, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -4357,7 +8900,7 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/NotFoundErrorType" + "$ref": "#/components/schemas/SummaryViewConflictErrorType" } }, "required": [ @@ -4366,97 +8909,117 @@ "type" ] }, - "NotFoundErrorType": { + "SummaryViewConflictErrorType": { "type": "string", - "description": "Problem type URI for not-found errors.", + "description": "Problem type URI for Summary View conflicts.", "enum": [ - "/errors/resource-not-found" + "/errors/summary-view-conflict" ] }, - "OwnerIdFilter": { + "SummaryViewEvictionPolicy": { "type": "object", - "description": "Filter by the long-term memory ownerId field. Matching is case-sensitive.", + "description": "Selects the bounded set of grouped results. Matching long-term-memory mutations make a scope recent.", "properties": { - "eq": { + "maxPartitions": { + "type": "integer", + "minimum": 1, + "format": "int32" + } + }, + "required": [ + "maxPartitions" + ] + }, + "SummaryViewField": { + "type": "object", + "description": "A field in a bounded structured Summary View result. All fields are optional in generated results.", + "properties": { + "name": { "type": "string", "maxLength": 64, "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value." + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$" }, - "ne": { + "description": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Not equal to the given value." - }, - "in": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." - }, - "description": "Matches any of the given values." + "maxLength": 200, + "minLength": 1 }, - "all": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." - }, - "description": "Matches all of the given values." + "type": { + "$ref": "#/components/schemas/SummaryViewFieldType" } - } + }, + "required": [ + "description", + "name", + "type" + ] }, - "PayloadTooLargeErrorResponseContent": { + "SummaryViewFieldType": { + "type": "string", + "description": "Supported structured-output field types. This matches the custom-memory field vocabulary.", + "enum": [ + "str", + "int", + "float", + "bool", + "list[str]", + "list[float]", + "object" + ] + }, + "SummaryViewGroupField": { + "type": "string", + "description": "A deterministic source field used to partition a Summary View.", + "enum": [ + "ownerId", + "sessionId", + "namespaceRef", + "memoryType" + ] + }, + "SummaryViewOutputConfig": { "type": "object", - "description": "The request payload exceeds the maximum supported size.", + "description": "Text or structured generation configuration. Schema is required exactly when format is structured.", "properties": { - "title": { - "type": "string", - "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + "format": { + "$ref": "#/components/schemas/SummaryViewOutputFormat" }, - "status": { - "type": "integer", - "default": 413, - "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", - "format": "int32" - }, - "detail": { - "type": "string", - "description": "A human-readable explanation specific to this occurrence of the problem." - }, - "instance": { + "instructions": { "type": "string", - "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + "maxLength": 10000, + "description": "Customer-authored generation instructions. Prompt-security validation is enforced by the service." }, - "type": { - "$ref": "#/components/schemas/PayloadTooLargeErrorType" + "schema": { + "$ref": "#/components/schemas/SummaryViewSchema" } }, "required": [ - "status", - "title", - "type" + "format" ] }, - "PayloadTooLargeErrorType": { + "SummaryViewOutputFormat": { "type": "string", - "description": "Problem type URI for payload-too-large errors.", + "description": "Summary View output format.", "enum": [ - "/errors/payload-too-large" + "text", + "structured" ] }, - "ResourceSuspendedErrorResponseContent": { + "SummaryViewQuotaCode": { + "type": "string", + "description": "Stable machine-readable Summary View quota code.", + "enum": [ + "DEFINITION_LIMIT_EXCEEDED", + "PARTITION_LIMIT_EXCEEDED", + "SOURCE_LIMIT_EXCEEDED", + "SCHEDULED_GENERATION_LIMIT_EXCEEDED", + "CONCURRENCY_LIMIT_EXCEEDED" + ] + }, + "SummaryViewQuotaErrorResponseContent": { "type": "object", - "description": "The requested resource exists but is suspended by an administrator.", + "description": "A Summary View operation exceeded an enabled store quota. Exact ceilings are deployment configuration established from benchmark evidence.", "properties": { "title": { "type": "string", @@ -4464,7 +9027,7 @@ }, "status": { "type": "integer", - "default": 423, + "default": 429, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -4477,272 +9040,187 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/ResourceSuspendedErrorType" + "$ref": "#/components/schemas/SummaryViewQuotaErrorType" + }, + "code": { + "$ref": "#/components/schemas/SummaryViewQuotaCode" } }, "required": [ + "code", "status", "title", "type" ] }, - "ResourceSuspendedErrorType": { + "SummaryViewQuotaErrorType": { "type": "string", - "description": "Problem type URI for suspended resources.", + "description": "Problem type URI for Summary View quota errors.", "enum": [ - "/errors/resource-suspended" + "/errors/summary-view-quota-exceeded" ] }, - "SearchLongTermMemoryRequestContent": { + "SummaryViewRefreshMode": { + "type": "string", + "description": "Summary View refresh mode.", + "enum": [ + "manual", + "continuous" + ] + }, + "SummaryViewRefreshPolicy": { "type": "object", - "description": "Semantic search request for long-term memory records.", + "description": "Refresh policy. Continuous views require reconcileIntervalSeconds; manual views omit both interval fields.", "properties": { - "text": { - "type": "string", - "description": "Text to use for semantic search.", - "example": "preferred theme" - }, - "similarityThreshold": { - "type": "number", - "maximum": 1, - "minimum": 0, - "description": "The minimum similarity threshold for vector search results (normalized cosine similarity). Allowed range: 0-1.", - "format": "float" - }, - "filter": { - "$ref": "#/components/schemas/LongTermMemoryFilter" + "mode": { + "$ref": "#/components/schemas/SummaryViewRefreshMode" }, - "filterOp": { - "$ref": "#/components/schemas/FilterConjunction" - }, - "limit": { + "debounceSeconds": { "type": "integer", - "default": 10, - "maximum": 100, "minimum": 1, - "description": "Maximum number of results to return. Defaults to 10. Allowed range: 1-100.", "format": "int32" }, - "pageToken": { - "type": "string", - "description": "Opaque token from a previous response for the next page." + "reconcileIntervalSeconds": { + "type": "integer", + "minimum": 1, + "format": "int32" } }, - "example": { - "text": "preferred theme", - "filter": { - "namespaceRef": { - "eq": "ns-product" - } - } - } + "required": [ + "mode" + ] }, - "SearchLongTermMemoryResponseContent": { - "type": "object", - "description": "Semantic search result for long-term memory records.", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MemoryRecord" + "SummaryViewRefreshTarget": { + "type": "string", + "description": "Manual refresh target. Partition refreshes require an exact configured scope; full refreshes reconcile every current and prior partition.", + "enum": [ + "partition", + "full" + ] + }, + "SummaryViewResultErrorCode": { + "type": "string", + "description": "Customer-safe refresh failure code. Provider details and source content are never returned.", + "enum": [ + "SOURCE_LIMIT_EXCEEDED", + "GENERATION_FAILED", + "OUTPUT_VALIDATION_FAILED", + "REFRESH_RETRIES_EXHAUSTED", + "INTERNAL_ERROR" + ] + }, + "SummaryViewResultStatus": { + "type": "string", + "description": "Materialization refresh state.", + "enum": [ + "cold", + "pending", + "ready", + "empty", + "refreshing", + "failed" + ] + }, + "SummaryViewResultValue": { + "description": "Exactly one generated result value. It is omitted for cold, pending, and empty results, and may be omitted for failed results with no last good value.", + "oneOf": [ + { + "type": "object", + "title": "text", + "properties": { + "text": { + "type": "string" + } }, - "description": "List of matching memory records. Always returned; empty when no records match." + "required": [ + "text" + ] }, - "nextPageToken": { - "type": "string", - "description": "Opaque token for fetching the next page. Omitted when no more results." + { + "type": "object", + "title": "data", + "properties": { + "data": {} + }, + "required": [ + "data" + ] } - }, - "required": [ - "items" ] - }, - "SessionEvent": { - "type": "object", - "description": "A single event in the session history.", - "properties": { - "eventId": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "Auto-generated event ID." - }, - "actorId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant." - }, - "sessionId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (client-provided or server-generated)." - }, - "role": { - "$ref": "#/components/schemas/MessageRole" - }, - "content": { + }, + "SummaryViewSchema": { + "type": "object", + "description": "Bounded structured-output schema. Nested object schemas, required fields, and arbitrary JSON Schema are not supported in the MVP.", + "properties": { + "fields": { "type": "array", "items": { - "$ref": "#/components/schemas/Content" + "$ref": "#/components/schemas/SummaryViewField" }, - "description": "The message content as an array of typed content parts." - }, - "createdAt": { - "type": "string", - "description": "Client-supplied timestamp for when the event happened (UTC).", - "format": "date-time" - }, - "systemTimestamp": { - "type": "string", - "description": "Server-set timestamp for when the dataplane ingested the event (UTC).", - "format": "date-time" - }, - "metadata": { - "description": "Optional metadata as any valid JSON value." + "maxItems": 50, + "minItems": 1 } }, "required": [ - "actorId", - "content", - "createdAt", - "eventId", - "role", - "sessionId", - "systemTimestamp" + "fields" ] }, - "SessionIdFilter": { + "SummaryViewScope": { "type": "object", - "description": "Filter by the long-term memory sessionId field. Matching is case-sensitive.", + "description": "An exact partition scope. The service requires exactly the fields configured by groupBy; singleton views accept none.", "properties": { - "eq": { + "ownerId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value." + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." }, - "ne": { + "sessionId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Not equal to the given value." - }, - "in": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." - }, - "description": "Matches any of the given values." - }, - "all": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." - }, - "description": "Matches all of the given values." - } - } - }, - "SessionSummary": { - "type": "object", - "description": "Compacted summary of older session history for a session.\n\nThis is derived session state maintained by the background summarization workflow, not a conversation event. It is present only after at least one summarization cycle has completed.", - "properties": { - "text": { - "type": "string", - "description": "The summary text, a single coherent block." - }, - "summarizedUpToEventId": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "Boundary event ID. This event and all events before it (by array position) are covered by the summary; `events` contains the retained turns after this boundary." + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." }, - "createdAt": { + "namespaceRef": { "type": "string", - "description": "Timestamp when the summary was first created (UTC). Preserved across re-summarization cycles.", - "format": "date-time" + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." }, - "updatedAt": { + "memoryType": { "type": "string", - "description": "Timestamp when the summary was last regenerated (UTC). Updated on every summarization cycle. Equal to createdAt on the first cycle.", - "format": "date-time" - }, - "metadata": { - "description": "Metadata as a JSON object (e.g., model name, token count). Always present; defaults to an empty object when no metadata was supplied." - }, - "summarizedEvents": { - "type": "integer", - "minimum": 1, - "description": "Cumulative number of session events folded into this summary across all summarization cycles. Always positive. A value of 1 may indicate a summary created before this field was tracked; it self-corrects to a real count on the next summarization cycle.", - "format": "int64" + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, `message`, and `session_summary_view`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes." } - }, - "required": [ - "createdAt", - "metadata", - "summarizedEvents", - "summarizedUpToEventId", - "text", - "updatedAt" - ] + } }, - "StoreHealthFeature": { - "type": "object", - "description": "Feature-level store health.", - "properties": { - "status": { - "$ref": "#/components/schemas/StoreHealthStatus" - } - }, - "required": [ - "status" + "SummaryViewSource": { + "type": "string", + "description": "The source record family summarized by a Summary View. The MVP supports long-term memory only.", + "enum": [ + "long_term" ] }, - "StoreHealthFeatureMap": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/StoreHealthFeature" - } - }, - "StoreHealthResponseContent": { - "type": "object", - "description": "Store-scoped RAM feature health.", - "properties": { - "status": { - "$ref": "#/components/schemas/StoreHealthStatus" - }, - "features": { - "$ref": "#/components/schemas/StoreHealthFeatureMap" - } - }, - "required": [ - "features", - "status" + "SummaryViewState": { + "type": "string", + "description": "Summary View definition lifecycle state.", + "enum": [ + "active", + "disabled", + "deleting" ] }, - "StoreHealthStatus": { + "SummaryViewVisibility": { "type": "string", - "description": "Store health status.", + "description": "The immutable audience of a Summary View.", "enum": [ - "healthy", - "degraded", - "unavailable" + "PRIVATE", + "SHARED" ] }, "TimeoutErrorResponseContent": { @@ -5042,6 +9520,21 @@ "type": "string", "description": "Timestamp when the memory was last updated (UTC).", "format": "date-time" + }, + "sources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemorySourceLink" + }, + "description": "Generic source links for this memory. Document sources include the customer document ID and revision that produced the memory." + }, + "sourcesNextPageToken": { + "type": "string", + "description": "Opaque token for additional source links when this response truncated attribution. Absent when every link is included." + }, + "documentManaged": { + "type": "boolean", + "description": "True when this memory was created by document ingestion and is protected from generic overwrite." } }, "required": [ @@ -5076,18 +9569,156 @@ "required": [ "namespace" ] + }, + "UpdateSummaryViewRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "New description. Send an empty string to clear it." + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "clearFilter": { + "type": "boolean", + "description": "Clear the saved filter. Mutually exclusive with filter." + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "clearTimeWindow": { + "type": "boolean", + "description": "Clear the rolling time window. Mutually exclusive with timeWindowSeconds." + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "state": { + "$ref": "#/components/schemas/MutableSummaryViewState" + } + } + }, + "UpdateSummaryViewResponseContent": { + "type": "object", + "description": "An owned Summary View definition. Results are separate derived state and never enter long-term-memory search.", + "properties": { + "id": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Immutable canonical user ID assigned by the server. Clients cannot set ownership." + }, + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "createdAt", + "definitionVersion", + "filterOp", + "groupBy", + "id", + "name", + "output", + "ownerId", + "refreshPolicy", + "source", + "state", + "updatedAt", + "visibility" + ] } - }, - "securitySchemes": { - "apiKey": { - "type": "http", - "scheme": "bearer" - } - } - }, - "security": [ - { - "apiKey": [] } - ] + } } diff --git a/content/develop/ai/context-engine/agent-memory/long-term-memory.md b/content/develop/ai/context-engine/agent-memory/long-term-memory.md index a5c40386e4..312a5ee2b7 100644 --- a/content/develop/ai/context-engine/agent-memory/long-term-memory.md +++ b/content/develop/ai/context-engine/agent-memory/long-term-memory.md @@ -57,6 +57,54 @@ For domain-specific fields and extraction instructions, see [Custom memory types For controls that apply during automatic extraction, see [Exclude sensitive data from automatic extraction]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#exclude-sensitive-data-from-automatic-extraction" >}}). +### Filter and sort without semantic search + +Omit `text` to retrieve records by filters without a semantic query. For example, retrieve the user's most recently updated memories: + +{{< multitabs id="filter-only-memory-search" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + recent_memories = agent_memory.search_long_term_memory(request={ + "filter_": {"owner_id": {"eq": USER_ID}}, + "sort_by": "updatedAt", + "sort_order": "desc", + "limit": 10, + }) + show("Recent memories", recent_memories) +``` + +-tab-sep- + +```typescript + const recentMemories = await agentMemory.searchLongTermMemory({ + filter: { ownerId: { eq: userId } }, + sortBy: "updatedAt", sortOrder: "desc", limit: 10, + }); + console.dir(recentMemories, { depth: null }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data "$(jq -n --arg owner "$OWNER_ID" \ + '{filter: {ownerId: {eq: $owner}}, sortBy: "updatedAt", sortOrder: "desc", limit: 10}')" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/search" | jq +``` + +{{< /multitabs >}} + +Use the client and identifiers from the [quickstart](/content/develop/ai/context-engine/agent-memory/quickstart.md). Add the Python snippet inside the `with` block and the TypeScript snippet inside `run`. + +`sortBy` accepts `createdAt` or `updatedAt` only when `text` is absent. `sortOrder` accepts `asc` or `desc`, defaults to `desc`, and requires `sortBy`. Semantic searches remain ranked by similarity and cannot use these sort fields. `limit` accepts 1–100 and defaults to 10. Pass `nextPageToken` as `pageToken` with the same query to continue. + +Top-level filters use `filterOp: "all"` (AND) by default; `"any"` uses OR. To restrict a query to a user, keep the owner condition mandatory. Do not use `"any"` with separate owner and namespace conditions when both must match. These retrieval filters do not replace application access checks. + +For custom fields, use `filter.attributes`, keyed by the field name, with the operators described in [`AttributeFilterClause`](/content/develop/ai/context-engine/agent-memory/api-reference.md#operation/SearchLongTermMemory). Combine it with `memoryType` to select the registered custom type. + ## Custom memory types Custom memory types capture structured information specific to your business domain. diff --git a/content/develop/ai/context-engine/agent-memory/namespaces.md b/content/develop/ai/context-engine/agent-memory/namespaces.md index 0ad0684934..36e584639c 100644 --- a/content/develop/ai/context-engine/agent-memory/namespaces.md +++ b/content/develop/ai/context-engine/agent-memory/namespaces.md @@ -44,7 +44,7 @@ Without server-side ownership enforcement, your application must check that the | Operation | Behavior | |:----------|:---------| -| List | List roots, or pass `parentId` to list direct children. Follow `nextPageToken` for additional pages. | +| List | List roots, or pass `parentId` to list direct children. Sort by `id`, `name`, `createdAt`, or `updatedAt` with `sortBy`; `sortOrder` accepts `asc` or `desc`. Defaults are `id` and `asc`. Follow `nextPageToken` with the same filters and sort options. | | Get | Retrieve the current name, path, scope, and state by ID. | | Rename | Update `name`; continue using the same ID for placement and retrieval. | | Archive | Set `state` to `ARCHIVED` to stop new children and placements. The update application programming interface (API) does not offer an unarchive transition. | @@ -234,3 +234,7 @@ curl --fail-with-body --silent --show-error \ {{< /multitabs >}} The namespace filter selects that exact namespace. To search its children too, list them and include their IDs with `filter.namespaceRef.in`. + +## Route and admit shared memories + +An explicit namespace destination must pass its admission rules. Direct creation without a namespace stays unassigned. To route an existing shared memory, omit `namespaceRef` from a move request. Personal and unassigned memories are not candidates for automatic routing. See [Namespace routing](/content/develop/ai/context-engine/agent-memory/namespace-routing.md) for policy setup, examples, and per-item errors. diff --git a/content/develop/ai/context-engine/agent-memory/quickstart.md b/content/develop/ai/context-engine/agent-memory/quickstart.md index bb43b43a29..135c0b3fcc 100644 --- a/content/develop/ai/context-engine/agent-memory/quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/quickstart.md @@ -79,6 +79,8 @@ The endpoint must include `https://`. Keep the API key out of source control, ap ## Install the SDK +These examples use the beta software development kits (SDKs): Python `0.5.0b1` and TypeScript `0.4.0-beta.1`. Install these versions explicitly; an unversioned install can select a stable release. Your service must support the operation you call. Installing a beta SDK does not enable server features. + {{< multitabs id="installation" tab1="Python" tab2="TypeScript" tab3="curl" >}} Create and activate a virtual environment with [uv](https://docs.astral.sh/uv/): @@ -86,7 +88,7 @@ Create and activate a virtual environment with [uv](https://docs.astral.sh/uv/): ```sh uv venv source .venv/bin/activate -uv pip install "redis-agent-memory==0.4.0" +uv pip install "redis-agent-memory==0.5.0b1" ``` -tab-sep- @@ -95,7 +97,7 @@ uv pip install "redis-agent-memory==0.4.0" mkdir agent-memory-quickstart cd agent-memory-quickstart npm init -y -npm install @redis-iris/agent-memory@0.3.0 +npm install @redis-iris/agent-memory@0.4.0-beta.1 npm install --save-dev tsx ``` @@ -137,8 +139,8 @@ def main(): store_id=STORE_ID, api_key=os.environ["API_KEY"], ) as agent_memory: - health = agent_memory.health() - show("Service health", health) + health = agent_memory.store_health() + show("Store health", health) if __name__ == "__main__": @@ -175,8 +177,8 @@ const agentMemory = new AgentMemory({ }); async function run() { - const health = await agentMemory.health(); - console.log("Service health:"); + const health = await agentMemory.storeHealth(); + console.log("Store health:"); console.dir(health, { depth: null }); } @@ -199,12 +201,12 @@ Verify that the service is available: ```sh curl --fail-with-body --silent --show-error \ --header "Authorization: Bearer $API_KEY" \ - "$AGENT_MEMORY_URL/health" | jq + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/health" | jq ``` {{< /multitabs >}} -A healthy response confirms that the client can reach Redis Agent Memory and authenticate with the API key. The first store request validates the Store ID. +The store health response checks the configured Store ID and reports the status of its memory features. Inspect the overall `status` and individual `features` before continuing. The next steps extend the client you just created: diff --git a/content/develop/ai/context-engine/agent-memory/sessions.md b/content/develop/ai/context-engine/agent-memory/sessions.md index c5853c3da0..75e09d9c68 100644 --- a/content/develop/ai/context-engine/agent-memory/sessions.md +++ b/content/develop/ai/context-engine/agent-memory/sessions.md @@ -57,6 +57,8 @@ Without server-side ownership enforcement, your application must check that the ## List sessions +To retrieve metadata rows ordered by recent activity, use `GET /v1/stores/{storeId}/sessions`, `list_session_summaries` in Python, or `listSessionSummaries` in TypeScript. These rows do not contain events or generated summary text. See [List and sort sessions](/content/develop/ai/context-engine/agent-memory/list-sessions.md) for SDK examples, pagination, and legacy field handling. + To let a user reopen an earlier conversation, first find its session ID. `GET /v1/stores/{storeId}/session-memory` lists IDs by owner or namespace; it does not return the conversation events. Reuse the curl connection values from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#save-the-connection-values" >}}). For a user's personal conversations, set `filterOwnerId` to that user's authenticated ID. A namespace-only query is suitable for shared conversations only after your application checks access to that namespace. Reserve `includeAll=true` for callers your application authorizes to list the whole store. See [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). From b4df6ab9353e57121c8bee9cdb34fe72aa49abe9 Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Wed, 21 Oct 2026 12:18:54 -0700 Subject: [PATCH 12/26] Keep routing guidance aligned with the beta API --- .../ai/context-engine/agent-memory/_index.md | 2 + .../api-reference/openapi-agent-memory.json | 7850 +++++++++++++---- .../agent-memory/namespace-routing.md | 167 + 3 files changed, 6204 insertions(+), 1815 deletions(-) create mode 100644 content/develop/ai/context-engine/agent-memory/namespace-routing.md diff --git a/content/develop/ai/context-engine/agent-memory/_index.md b/content/develop/ai/context-engine/agent-memory/_index.md index 92a828ae16..8fc7f17e7a 100644 --- a/content/develop/ai/context-engine/agent-memory/_index.md +++ b/content/develop/ai/context-engine/agent-memory/_index.md @@ -140,3 +140,5 @@ After your Redis Agent Memory service is ready, choose a client. Each quickstart For shared integration concepts, identifiers, and authentication, see the [Redis Agent Memory developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md). To generate a complete conversational agent that uses Agent Memory, open the [AI agent builder](/content/develop/ai/agent-builder/_index.md) and select **Redis Iris Conversational Assistant**. + +To configure shared memory placement and admission rules, see [Namespace routing](/content/develop/ai/context-engine/agent-memory/namespace-routing.md). diff --git a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json index e6ee18679c..c3095f3194 100644 --- a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json +++ b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json @@ -6,53 +6,50 @@ "description": "API for storing, retrieving, and searching Redis Agent Memory session memory and long-term memory.\n\nAuthentication depends on your deployment. Use the credential issued for your Redis Agent Memory service or store. For self-managed deployments, see the self-managed Redis Agent Memory guide for authentication options.\n" }, "paths": { - "/health": { - "get": { - "description": "Return information about the operational status of the service.", - "operationId": "Health", - "responses": { - "200": { - "description": "Health 200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HealthResponseContent" - } + "/v1/stores/{storeId}/document-ingestions": { + "post": { + "description": "Submit one revisioned document for extraction.", + "operationId": "CreateDocumentIngestion", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateDocumentIngestionRequestContent" } } - } + }, + "required": true }, - "tags": [ - "Service Health" - ] - } - }, - "/v1/stores/{storeId}/health": { - "get": { - "description": "Returns read-only health for applicable store-scoped RAM features.", - "operationId": "StoreHealth", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { - "200": { - "description": "StoreHealth 200 response", + "202": { + "description": "CreateDocumentIngestion 202 response", + "headers": { + "Location": { + "schema": { + "type": "string" + }, + "required": true + } + }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StoreHealthResponseContent" + "$ref": "#/components/schemas/CreateDocumentIngestionResponseContent" } } } @@ -107,6 +104,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -117,6 +124,16 @@ } } }, + "422": { + "description": "IngestionHookStoppedError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookStoppedErrorResponseContent" + } + } + } + }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -128,11 +145,11 @@ } }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -156,49 +173,58 @@ } } } + }, + "503": { + "description": "IngestionHookRetryError 503 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookRetryErrorResponseContent" + } + } + } } - }, - "tags": [ - "Store Health" - ] + } } }, - "/v1/stores/{storeId}/long-term-memory": { - "delete": { - "description": "Deletes long-term memories in bulk by their IDs.", - "operationId": "BulkDeleteLongTermMemories", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BulkDeleteLongTermMemoriesRequestContent" - } - } - }, - "required": true - }, + "/v1/stores/{storeId}/document-ingestions/{ingestionId}": { + "get": { + "operationId": "GetDocumentIngestion", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "ingestionId", + "in": "path", + "description": "Server-generated document ingestion identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." }, "required": true } ], "responses": { "200": { - "description": "BulkDeleteLongTermMemories 200 response", + "description": "GetDocumentIngestion 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BulkDeleteLongTermMemoriesResponseContent" + "$ref": "#/components/schemas/GetDocumentIngestionResponseContent" } } } @@ -253,16 +279,6 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" - } - } - } - }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -283,16 +299,6 @@ } } }, - "429": { - "description": "TooManyRequestsError 429 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" - } - } - } - }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -303,46 +309,63 @@ } } } - }, - "tags": [ - "Long-Term Memory" - ] - }, - "post": { - "description": "Creates long-term memories in bulk. Records without namespace or namespaceRef use automatic routing when the store's routing policy is enabled. If routing is disabled or no destination qualifies, the record is created without a namespace. An explicit namespaceRef must pass destination admission and is never rerouted. Routing and admission failures are reported per record in errors.", - "operationId": "BulkCreateLongTermMemories", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BulkCreateLongTermMemoriesRequestContent" - } - } - }, - "required": true - }, + } + } + }, + "/v1/stores/{storeId}/document-ingestions/{ingestionId}/outputs": { + "get": { + "operationId": "ListDocumentIngestionOutputs", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true + }, + { + "name": "ingestionId", + "in": "path", + "description": "Server-generated document ingestion identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } } ], "responses": { - "201": { - "description": "BulkCreateLongTermMemories 201 response", + "200": { + "description": "ListDocumentIngestionOutputs 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BulkCreateLongTermMemoriesResponseContent" + "$ref": "#/components/schemas/ListDocumentIngestionOutputsResponseContent" } } } @@ -397,16 +420,6 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" - } - } - } - }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -418,21 +431,11 @@ } }, "424": { - "description": "MemoryFailedDependencyError 424 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" - } - } - } - }, - "429": { - "description": "TooManyRequestsError 429 response", + "description": "FailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" } } } @@ -447,48 +450,35 @@ } } } - }, - "tags": [ - "Long-Term Memory" - ] + } } }, - "/v1/stores/{storeId}/long-term-memory/move": { - "post": { - "description": "Moves long-term memories to an explicit namespaceRef or automatically routes each memory when namespaceRef is omitted. If routing is disabled or no destination qualifies, the memory keeps its current placement and receives a per-item error. An explicit destination must pass admission and is never rerouted. Returns 409 when the explicit destination is archived; automatic routing and admission failures are reported per item in errors.", - "operationId": "MoveLongTermMemories", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MoveLongTermMemoriesRequestContent" - } - } - }, - "required": true - }, + "/v1/stores/{storeId}/health": { + "get": { + "description": "Returns read-only health for applicable store-scoped RAM features.", + "operationId": "StoreHealth", "parameters": [ { "name": "storeId", "in": "path", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "description": "The store instance ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + "description": "The store instance ID." }, "required": true } ], "responses": { "200": { - "description": "MoveLongTermMemories 200 response", + "description": "StoreHealth 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MoveLongTermMemoriesResponseContent" + "$ref": "#/components/schemas/StoreHealthResponseContent" } } } @@ -543,16 +533,6 @@ } } }, - "409": { - "description": "ConflictError 409 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ConflictErrorResponseContent" - } - } - } - }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -605,45 +585,54 @@ } }, "tags": [ - "Long-Term Memory" + "Store Health" ] } }, - "/v1/stores/{storeId}/long-term-memory/search": { - "post": { - "description": "Runs a semantic search on long-term memory with filtering options.", - "operationId": "SearchLongTermMemory", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SearchLongTermMemoryRequestContent" - } - } - } - }, + "/v1/stores/{storeId}/ingestion-hooks/events": { + "get": { + "description": "List hook decisions in stable newest-first order. Pages can be empty after owner filtering; continue while nextCursor is present. Internal run reservations are omitted.", + "operationId": "ListIngestionHookStatuses", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 20, + "maximum": 100, + "minimum": 1, + "format": "int32" + } + }, + { + "name": "cursor", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { "200": { - "description": "SearchLongTermMemory 200 response", + "description": "ListIngestionHookStatuses 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SearchLongTermMemoryResponseContent" + "$ref": "#/components/schemas/ListIngestionHookStatusesResponseContent" } } } @@ -678,36 +667,6 @@ } } }, - "404": { - "description": "NotFoundError 404 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotFoundErrorResponseContent" - } - } - } - }, - "408": { - "description": "TimeoutError 408 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TimeoutErrorResponseContent" - } - } - } - }, - "413": { - "description": "PayloadTooLargeError 413 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" - } - } - } - }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -728,16 +687,6 @@ } } }, - "429": { - "description": "TooManyRequestsError 429 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" - } - } - } - }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -750,49 +699,44 @@ } }, "tags": [ - "Long-Term Memory" + "Ingestion Hooks" ] } }, - "/v1/stores/{storeId}/long-term-memory/{memoryId}": { + "/v1/stores/{storeId}/ingestion-hooks/events/{eventId}": { "get": { - "description": "Returns a long-term memory by its ID.", - "operationId": "GetLongTermMemory", + "description": "Read a hook decision and application status. Requires authenticated store access and, when enforced, matching owner scope. Content and secrets are never returned.", + "operationId": "GetIngestionHookStatus", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true }, { - "name": "memoryId", + "name": "eventId", "in": "path", - "description": "The memory record ID.", "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The memory record ID." + "type": "string" }, "required": true } ], "responses": { "200": { - "description": "GetLongTermMemory 200 response", + "description": "GetIngestionHookStatus 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetLongTermMemoryResponseContent" + "$ref": "#/components/schemas/GetIngestionHookStatusResponseContent" } } } @@ -837,26 +781,6 @@ } } }, - "408": { - "description": "TimeoutError 408 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TimeoutErrorResponseContent" - } - } - } - }, - "413": { - "description": "PayloadTooLargeError 413 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" - } - } - } - }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -868,21 +792,11 @@ } }, "424": { - "description": "FailedDependencyError 424 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" - } - } - } - }, - "429": { - "description": "TooManyRequestsError 429 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -899,20 +813,23 @@ } }, "tags": [ - "Long-Term Memory" + "Ingestion Hooks" ] - }, - "patch": { - "description": "Partially updates a long-term memory by its ID. The merged record must pass admission at its destination. Updates never select a different destination automatically.", - "operationId": "UpdateLongTermMemory", + } + }, + "/v1/stores/{storeId}/long-term-memory": { + "delete": { + "description": "Deletes long-term memories in bulk by their IDs.", + "operationId": "BulkDeleteLongTermMemories", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateLongTermMemoryRequestContent" + "$ref": "#/components/schemas/BulkDeleteLongTermMemoriesRequestContent" } } - } + }, + "required": true }, "parameters": [ { @@ -927,28 +844,15 @@ "description": "The store instance ID." }, "required": true - }, - { - "name": "memoryId", - "in": "path", - "description": "The memory record ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The memory record ID." - }, - "required": true } ], "responses": { "200": { - "description": "UpdateLongTermMemory 200 response", + "description": "BulkDeleteLongTermMemories 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateLongTermMemoryResponseContent" + "$ref": "#/components/schemas/BulkDeleteLongTermMemoriesResponseContent" } } } @@ -1003,16 +907,6 @@ } } }, - "409": { - "description": "ConflictError 409 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ConflictErrorResponseContent" - } - } - } - }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -1034,11 +928,11 @@ } }, "424": { - "description": "MemoryFailedDependencyError 424 response", + "description": "FailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" } } } @@ -1067,17 +961,15 @@ "tags": [ "Long-Term Memory" ] - } - }, - "/v1/stores/{storeId}/long-term-memory/{memoryId}/fields": { - "patch": { - "description": "Atomically updates a custom-typed long-term memory's text and/or attributes, scoped to the caller's bound memory type. Used by the custom-extraction worker. New text must pass destination admission before the update is applied.", - "operationId": "UpdateLongTermMemoryFields", + }, + "post": { + "description": "Creates long-term memories in bulk. Records without namespace or namespaceRef stay un-namespaced. Automatic routing does not place them in a shared namespace. An explicit namespaceRef must pass destination admission and is never rerouted. Admission failures are reported per record in errors.", + "operationId": "BulkCreateLongTermMemories", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateLongTermMemoryFieldsRequestContent" + "$ref": "#/components/schemas/BulkCreateLongTermMemoriesRequestContent" } } }, @@ -1096,24 +988,18 @@ "description": "The store instance ID." }, "required": true - }, - { - "name": "memoryId", - "in": "path", - "description": "The memory record ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The memory record ID." - }, - "required": true } ], "responses": { - "200": { - "description": "UpdateLongTermMemoryFields 200 response" + "201": { + "description": "BulkCreateLongTermMemories 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkCreateLongTermMemoriesResponseContent" + } + } + } }, "400": { "description": "BadRequestError 400 response", @@ -1165,12 +1051,22 @@ } } }, - "409": { - "description": "ConflictError 409 response", + "413": { + "description": "PayloadTooLargeError 413 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ConflictErrorResponseContent" + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "422": { + "description": "IngestionHookStoppedError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookStoppedErrorResponseContent" } } } @@ -1214,6 +1110,16 @@ } } } + }, + "503": { + "description": "IngestionHookRetryError 503 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookRetryErrorResponseContent" + } + } + } } }, "tags": [ @@ -1221,10 +1127,20 @@ ] } }, - "/v1/stores/{storeId}/namespace-routing": { - "get": { - "description": "Returns the store's namespace routing policy. An empty disabled policy is returned when none has been saved.", - "operationId": "GetNamespaceRouting", + "/v1/stores/{storeId}/long-term-memory/move": { + "post": { + "description": "Moves long-term memories to an explicit namespaceRef, or automatically routes a memory that is already in a shared namespace when namespaceRef is omitted. Personal and un-namespaced memories are not automatic-routing candidates and keep their current placement with a per-item error. If routing is disabled or no shared destination qualifies, the memory also keeps its current placement and receives a per-item error. An explicit destination must pass admission and is never rerouted. Returns 409 when the explicit destination is archived; automatic routing and admission failures are reported per item in errors.", + "operationId": "MoveLongTermMemories", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MoveLongTermMemoriesRequestContent" + } + } + }, + "required": true + }, "parameters": [ { "name": "storeId", @@ -1242,11 +1158,11 @@ ], "responses": { "200": { - "description": "GetNamespaceRouting 200 response", + "description": "MoveLongTermMemories 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetNamespaceRoutingResponseContent" + "$ref": "#/components/schemas/MoveLongTermMemoriesResponseContent" } } } @@ -1301,6 +1217,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -1353,44 +1279,45 @@ } }, "tags": [ - "Namespaces" + "Long-Term Memory" ] - }, - "put": { - "description": "Replaces the store's namespace routing policy. Namespaces remain hierarchy records; routing is an ordered store-level document of plug-in strategies.", - "operationId": "PutNamespaceRouting", + } + }, + "/v1/stores/{storeId}/long-term-memory/search": { + "post": { + "description": "Runs a semantic search on long-term memory with filtering options.", + "operationId": "SearchLongTermMemory", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PutNamespaceRoutingRequestContent" + "$ref": "#/components/schemas/SearchLongTermMemoryRequestContent" } } - }, - "required": true + } }, "parameters": [ { "name": "storeId", "in": "path", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "description": "The store instance ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + "description": "The store instance ID." }, "required": true } ], "responses": { "200": { - "description": "PutNamespaceRouting 200 response", + "description": "SearchLongTermMemory 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PutNamespaceRoutingResponseContent" + "$ref": "#/components/schemas/SearchLongTermMemoryResponseContent" } } } @@ -1445,16 +1372,6 @@ } } }, - "409": { - "description": "ConflictError 409 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ConflictErrorResponseContent" - } - } - } - }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -1476,11 +1393,11 @@ } }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -1507,90 +1424,49 @@ } }, "tags": [ - "Namespaces" + "Long-Term Memory" ] } }, - "/v1/stores/{storeId}/namespaces": { + "/v1/stores/{storeId}/long-term-memory/{memoryId}": { "get": { - "description": "Lists namespace roots or direct children.", - "operationId": "ListNamespaces", + "description": "Returns a long-term memory by its ID.", + "operationId": "GetLongTermMemory", "parameters": [ { "name": "storeId", "in": "path", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "description": "The store instance ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + "description": "The store instance ID." }, "required": true }, { - "name": "parentId", - "in": "query", - "description": "List direct children of this namespace. Omit to list roots.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "List direct children of this namespace. Omit to list roots." - } - }, - { - "name": "scope", - "in": "query", - "description": "Restrict root results to one scope. Cannot be combined with parentId.", - "schema": { - "$ref": "#/components/schemas/NamespaceScope" - } - }, - { - "name": "ownerId", - "in": "query", - "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots.", + "name": "memoryId", + "in": "path", + "description": "The memory record ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots." - } - }, - { - "name": "limit", - "in": "query", - "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", - "schema": { - "type": "integer", - "default": 100, - "maximum": 1000, - "minimum": 1, - "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", - "format": "int32" - } - }, - { - "name": "pageToken", - "in": "query", - "description": "Opaque token from a previous response for the next page.", - "schema": { - "type": "string", - "description": "Opaque token from a previous response for the next page." - } + "description": "The memory record ID." + }, + "required": true } ], "responses": { "200": { - "description": "ListNamespaces 200 response", + "description": "GetLongTermMemory 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListNamespacesResponseContent" + "$ref": "#/components/schemas/GetLongTermMemoryResponseContent" } } } @@ -1697,17 +1573,17 @@ } }, "tags": [ - "Namespaces" + "Long-Term Memory" ] }, - "post": { - "description": "Creates a namespace resource as a root, direct child, or explicit hierarchy path. Returns 409 when an active or archived namespace already occupies the requested location.", - "operationId": "CreateNamespace", + "patch": { + "description": "Partially updates a long-term memory by its ID. The merged record must pass admission at its destination. Updates never select a different destination automatically.", + "operationId": "UpdateLongTermMemory", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateNamespaceRequestContent" + "$ref": "#/components/schemas/UpdateLongTermMemoryRequestContent" } } } @@ -1716,24 +1592,37 @@ { "name": "storeId", "in": "path", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "description": "The store instance ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "memoryId", + "in": "path", + "description": "The memory record ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The memory record ID." }, "required": true } ], "responses": { - "201": { - "description": "CreateNamespace 201 response", + "200": { + "description": "UpdateLongTermMemory 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateNamespaceResponseContent" + "$ref": "#/components/schemas/UpdateLongTermMemoryResponseContent" } } } @@ -1819,11 +1708,11 @@ } }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -1850,45 +1739,55 @@ } }, "tags": [ - "Namespaces" + "Long-Term Memory" ] } }, - "/v1/stores/{storeId}/namespaces/{namespaceId}": { - "delete": { - "description": "Deletes an empty leaf namespace. Returns 409 when the namespace has children or memory placements, or when it changed concurrently.", - "operationId": "DeleteNamespace", + "/v1/stores/{storeId}/long-term-memory/{memoryId}/fields": { + "patch": { + "description": "Atomically updates a custom-typed long-term memory's text and/or attributes, scoped to the caller's bound memory type. Used by the custom-extraction worker. New text must pass destination admission before the update is applied.", + "operationId": "UpdateLongTermMemoryFields", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLongTermMemoryFieldsRequestContent" + } + } + }, + "required": true + }, "parameters": [ { "name": "storeId", "in": "path", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "description": "The store instance ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + "description": "The store instance ID." }, "required": true }, { - "name": "namespaceId", + "name": "memoryId", "in": "path", - "description": "Server-generated namespace identifier.", + "description": "The memory record ID.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." + "description": "The memory record ID." }, "required": true } ], "responses": { - "204": { - "description": "DeleteNamespace 204 response" + "200": { + "description": "UpdateLongTermMemoryFields 200 response" }, "400": { "description": "BadRequestError 400 response", @@ -1950,16 +1849,6 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" - } - } - } - }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -1971,11 +1860,11 @@ } }, "424": { - "description": "FailedDependencyError 424 response", + "description": "MemoryFailedDependencyError 424 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorResponseContent" } } } @@ -2002,12 +1891,14 @@ } }, "tags": [ - "Namespaces" + "Long-Term Memory" ] - }, + } + }, + "/v1/stores/{storeId}/long-term-memory/{memoryId}/sources": { "get": { - "description": "Gets one namespace by its canonical ID.", - "operationId": "GetNamespace", + "description": "Pages source links for one long-term memory. Tokens bind this query and expire.", + "operationId": "ListMemorySourceLinks", "parameters": [ { "name": "storeId", @@ -2023,26 +1914,43 @@ "required": true }, { - "name": "namespaceId", + "name": "memoryId", "in": "path", - "description": "Server-generated namespace identifier.", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." }, "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { "200": { - "description": "GetNamespace 200 response", + "description": "ListMemorySourceLinks 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetNamespaceResponseContent" + "$ref": "#/components/schemas/ListMemorySourceLinksResponseContent" } } } @@ -2097,16 +2005,6 @@ } } }, - "413": { - "description": "PayloadTooLargeError 413 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" - } - } - } - }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -2127,16 +2025,6 @@ } } }, - "429": { - "description": "TooManyRequestsError 429 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" - } - } - } - }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -2149,21 +2037,14 @@ } }, "tags": [ - "Namespaces" + "Long-Term Memory" ] - }, - "patch": { - "description": "Renames or archives a namespace. Returns 409 when the namespace changed concurrently, an active or archived sibling already uses the requested name, or the namespace is archived.", - "operationId": "UpdateNamespace", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateNamespaceRequestContent" - } - } - } - }, + } + }, + "/v1/stores/{storeId}/long-term-memory/{memoryId}/sources/{sourceId}/evidence": { + "get": { + "description": "Pages extraction evidence for one source of a memory, in append order. Tokens bind the store, current owner, memory and source, and expire.", + "operationId": "ListMemorySourceEvidence", "parameters": [ { "name": "storeId", @@ -2179,26 +2060,56 @@ "required": true }, { - "name": "namespaceId", + "name": "memoryId", "in": "path", - "description": "Server-generated namespace identifier.", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." }, "required": true - } - ], + }, + { + "name": "sourceId", + "in": "path", + "description": "Stable source identifier for one revisioned origin in a store.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable source identifier for one revisioned origin in a store." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { - "description": "UpdateNamespace 200 response", + "description": "ListMemorySourceEvidence 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateNamespaceResponseContent" + "$ref": "#/components/schemas/ListMemorySourceEvidenceResponseContent" } } } @@ -2253,26 +2164,6 @@ } } }, - "409": { - "description": "ConflictError 409 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ConflictErrorResponseContent" - } - } - } - }, - "413": { - "description": "PayloadTooLargeError 413 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" - } - } - } - }, "423": { "description": "ResourceSuspendedError 423 response", "content": { @@ -2293,16 +2184,6 @@ } } }, - "429": { - "description": "TooManyRequestsError 429 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" - } - } - } - }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -2315,91 +2196,36 @@ } }, "tags": [ - "Namespaces" + "Long-Term Memory" ] } }, - "/v1/stores/{storeId}/session-memory": { + "/v1/stores/{storeId}/namespace-routing": { "get": { - "description": "Returns a paginated list of session IDs for a store.", - "operationId": "ListSessions", + "description": "Returns the store's namespace routing policy. An empty disabled policy is returned when none has been saved.", + "operationId": "GetNamespaceRouting", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true - }, - { - "name": "limit", - "in": "query", - "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", - "schema": { - "type": "integer", - "default": 100, - "maximum": 1000, - "minimum": 1, - "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", - "format": "int32" - } - }, - { - "name": "pageToken", - "in": "query", - "description": "Opaque token from a previous response for the next page.", - "schema": { - "type": "string", - "description": "Opaque token from a previous response for the next page." - } - }, - { - "name": "filterOwnerId", - "in": "query", - "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Filter sessions by owner. Matching is case-sensitive. Mutually exclusive with includeAll." - } - }, - { - "name": "namespaceRef", - "in": "query", - "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll." - } - }, - { - "name": "includeAll", - "in": "query", - "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters.", - "schema": { - "type": "boolean", - "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters." - } } ], "responses": { "200": { - "description": "ListSessions 200 response", + "description": "GetNamespaceRouting 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListSessionsResponseContent" + "$ref": "#/components/schemas/GetNamespaceRoutingResponseContent" } } } @@ -2506,19 +2332,17 @@ } }, "tags": [ - "Session Memory" + "Namespaces" ] - } - }, - "/v1/stores/{storeId}/session-memory/events": { - "post": { - "description": "Appends a single event to a session. Creates the session if it does not exist. If sessionId is omitted, the server generates one.", - "operationId": "AddSessionEvent", + }, + "put": { + "description": "Replaces the store's namespace routing policy. Namespaces remain hierarchy records; routing is an ordered store-level document of plug-in strategies.", + "operationId": "PutNamespaceRouting", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddSessionEventRequestContent" + "$ref": "#/components/schemas/PutNamespaceRoutingRequestContent" } } }, @@ -2528,24 +2352,24 @@ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true } ], "responses": { - "201": { - "description": "AddSessionEvent 201 response", + "200": { + "description": "PutNamespaceRouting 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddSessionEventResponseContent" + "$ref": "#/components/schemas/PutNamespaceRoutingResponseContent" } } } @@ -2600,6 +2424,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -2652,45 +2486,109 @@ } }, "tags": [ - "Session Memory" + "Namespaces" ] } }, - "/v1/stores/{storeId}/session-memory/{sessionId}": { - "delete": { - "description": "Deletes the session memory for a session.", - "operationId": "DeleteSessionMemory", + "/v1/stores/{storeId}/namespaces": { + "get": { + "description": "Lists namespace roots or direct children.", + "operationId": "ListNamespaces", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true }, { - "name": "sessionId", - "in": "path", - "description": "The session ID.", + "name": "parentId", + "in": "query", + "description": "List direct children of this namespace. Omit to list roots.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "required": true + "description": "List direct children of this namespace. Omit to list roots." + } + }, + { + "name": "scope", + "in": "query", + "description": "Restrict root results to one scope. Cannot be combined with parentId.", + "schema": { + "$ref": "#/components/schemas/NamespaceScope" + } + }, + { + "name": "ownerId", + "in": "query", + "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Restrict personal root results to an owner. Cannot be combined with parentId; ignored for shared roots." + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Field to sort by. Defaults to id. Name sorts by the indexed display name using Redis text collation, not the namespace path. Equal values are ordered by ID ascending.", + "schema": { + "$ref": "#/components/schemas/NamespaceSortBy" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort direction. Defaults to asc. Use the same sort options for each page; concurrent changes can shift offset-based pages.", + "schema": { + "$ref": "#/components/schemas/NamespaceSortOrder" + } + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of namespaces to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page." + } } ], "responses": { - "204": { - "description": "DeleteSessionMemory 204 response" + "200": { + "description": "ListNamespaces 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListNamespacesResponseContent" + } + } + } }, "400": { "description": "BadRequestError 400 response", @@ -2794,56 +2692,43 @@ } }, "tags": [ - "Session Memory" + "Namespaces" ] }, - "get": { - "description": "Returns the session memory for a session.", - "operationId": "GetSessionMemory", + "post": { + "description": "Creates a namespace resource as a root, direct child, or explicit hierarchy path. Returns 409 when an active or archived namespace already occupies the requested location.", + "operationId": "CreateNamespace", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateNamespaceRequestContent" + } + } + } + }, "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", - "schema": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." - }, - "required": true - }, - { - "name": "sessionId", - "in": "path", - "description": "The session ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true - }, - { - "name": "includeSummarisedEvents", - "in": "query", - "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage.", - "schema": { - "type": "boolean", - "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage." - } } ], "responses": { - "200": { - "description": "GetSessionMemory 200 response", + "201": { + "description": "CreateNamespace 201 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetSessionMemoryResponseContent" + "$ref": "#/components/schemas/CreateNamespaceResponseContent" } } } @@ -2898,6 +2783,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -2950,58 +2845,45 @@ } }, "tags": [ - "Session Memory" + "Namespaces" ] } }, - "/v1/stores/{storeId}/session-memory/{sessionId}/events/{eventId}": { + "/v1/stores/{storeId}/namespaces/{namespaceId}": { "delete": { - "description": "Deletes a single event from a session by event ID.", - "operationId": "DeleteSessionEvent", + "description": "Deletes an empty leaf namespace. Returns 409 when the namespace has children or memory placements, or when it changed concurrently.", + "operationId": "DeleteNamespace", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true }, { - "name": "sessionId", + "name": "namespaceId", "in": "path", - "description": "The session ID.", + "description": "Server-generated namespace identifier.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "required": true - }, - { - "name": "eventId", - "in": "path", - "description": "The event ID.", - "schema": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "The event ID." + "description": "Server-generated namespace identifier." }, "required": true } ], "responses": { "204": { - "description": "DeleteSessionEvent 204 response" + "description": "DeleteNamespace 204 response" }, "400": { "description": "BadRequestError 400 response", @@ -3053,6 +2935,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "413": { "description": "PayloadTooLargeError 413 response", "content": { @@ -3105,60 +2997,47 @@ } }, "tags": [ - "Session Memory" + "Namespaces" ] }, "get": { - "description": "Returns a single event from a session by event ID.", - "operationId": "GetSessionEvent", + "description": "Gets one namespace by its canonical ID.", + "operationId": "GetNamespace", "parameters": [ { "name": "storeId", "in": "path", - "description": "The store instance ID.", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The store instance ID." + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, "required": true }, { - "name": "sessionId", + "name": "namespaceId", "in": "path", - "description": "The session ID.", + "description": "Server-generated namespace identifier.", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "required": true - }, - { - "name": "eventId", - "in": "path", - "description": "The event ID.", - "schema": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "The event ID." + "description": "Server-generated namespace identifier." }, "required": true } ], "responses": { "200": { - "description": "GetSessionEvent 200 response", + "description": "GetNamespace 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetSessionEventResponseContent" + "$ref": "#/components/schemas/GetNamespaceResponseContent" } } } @@ -3265,155 +3144,3979 @@ } }, "tags": [ - "Session Memory" - ] - } - } - }, - "components": { - "schemas": { - "AddSessionEventRequestContent": { - "type": "object", - "properties": { - "sessionId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional session ID. If omitted, the server generates a unique ID." - }, - "actorId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant." - }, - "namespace": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional deprecated namespace label for the session. This label does not create or resolve a namespace resource. Do not supply it with namespaceRef.\n\nLike `ownerId`, this is a session-level value taken from the first event: the value sent with the first event of a session is stored on the session, and values sent with later events are ignored. Omit it to leave extracted memories without a namespace.", - "deprecated": true - }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRefInput" - }, - "role": { - "$ref": "#/components/schemas/MessageRole" - }, - "content": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Content" - }, - "description": "The message content as an array of typed content parts." - }, - "createdAt": { - "type": "string", - "description": "Client-supplied timestamp for when the event happened (UTC).", - "format": "date-time" - }, - "metadata": { - "description": "Optional metadata as any valid JSON value." - } - }, - "required": [ - "actorId", - "content", - "createdAt", - "role" + "Namespaces" ] }, - "AddSessionEventResponseContent": { - "type": "object", - "description": "Response returned after appending a session event.", - "properties": { - "event": { - "$ref": "#/components/schemas/SessionEvent" + "patch": { + "description": "Renames or archives a namespace. Returns 409 when the namespace changed concurrently, an active or archived sibling already uses the requested name, or the namespace is archived.", + "operationId": "UpdateNamespace", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateNamespaceRequestContent" + } + } } }, - "required": [ - "event" - ] - }, - "AttributeFilterClause": { - "description": "Filter clause for a single record attribute. Exactly one member is set; the member must match the attribute's declared field type.", - "oneOf": [ + "parameters": [ { - "type": "object", - "title": "string", - "properties": { - "string": { - "type": "string", - "description": "Match a string attribute equal to this value." - } + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." }, - "required": [ - "string" - ] + "required": true }, { - "type": "object", - "title": "number", - "properties": { - "number": { - "type": "number", - "description": "Match a numeric attribute equal to this value.", - "format": "double" - } + "name": "namespaceId", + "in": "path", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." }, - "required": [ - "number" - ] - }, - { - "type": "object", - "title": "boolean", - "properties": { - "boolean": { - "type": "boolean", - "description": "Match a boolean attribute equal to this value." + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateNamespace 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateNamespaceResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Namespaces" + ] + } + }, + "/v1/stores/{storeId}/session-memory": { + "get": { + "description": "Returns a paginated list of session IDs for a store.", + "operationId": "ListSessions", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page." + } + }, + { + "name": "filterOwnerId", + "in": "query", + "description": "Filter sessions by owner. The owner of a session is the actorId of its first event. Matching is exact and case-sensitive. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by owner. The owner of a session is the actorId of its first event. Matching is exact and case-sensitive. Mutually exclusive with includeAll." + } + }, + { + "name": "namespaceRef", + "in": "query", + "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by stable namespace ID. Matching is exact and case-sensitive, excludes deprecated namespace labels and sessions without a namespace resource, and does not include descendants. Combined with filterOwnerId using AND. Mutually exclusive with includeAll." + } + }, + { + "name": "includeAll", + "in": "query", + "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters.", + "schema": { + "type": "boolean", + "description": "Set to true to list all sessions. Required when no filter is given; mutually exclusive with filters." + } + } + ], + "responses": { + "200": { + "description": "ListSessions 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSessionsResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/session-memory/events": { + "post": { + "description": "Appends a single event to a session. Creates the session if it does not exist. If sessionId is omitted, the server generates one.", + "operationId": "AddSessionEvent", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddSessionEventRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + } + ], + "responses": { + "201": { + "description": "AddSessionEvent 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddSessionEventResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "422": { + "description": "IngestionHookStoppedError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookStoppedErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + }, + "503": { + "description": "IngestionHookRetryError 503 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IngestionHookRetryErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/session-memory/{sessionId}": { + "delete": { + "description": "Deletes the session memory for a session.", + "operationId": "DeleteSessionMemory", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteSessionMemory 204 response" + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + }, + "get": { + "description": "Returns the session memory for a session.", + "operationId": "GetSessionMemory", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + }, + { + "name": "includeSummarisedEvents", + "in": "query", + "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage.", + "schema": { + "type": "boolean", + "description": "When true, the response includes events that have already been covered by the session summary and have not yet been cleaned up. This flag is not expected to be set under normal usage." + } + } + ], + "responses": { + "200": { + "description": "GetSessionMemory 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSessionMemoryResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/session-memory/{sessionId}/events/{eventId}": { + "delete": { + "description": "Deletes a single event from a session by event ID.", + "operationId": "DeleteSessionEvent", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + }, + { + "name": "eventId", + "in": "path", + "description": "The event ID.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "The event ID." + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteSessionEvent 204 response" + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + }, + "get": { + "description": "Returns a single event from a session by event ID.", + "operationId": "GetSessionEvent", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "The store instance ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The store instance ID." + }, + "required": true + }, + { + "name": "sessionId", + "in": "path", + "description": "The session ID.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "required": true + }, + { + "name": "eventId", + "in": "path", + "description": "The event ID.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "The event ID." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetSessionEvent 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSessionEventResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/sessions": { + "get": { + "description": "Returns paginated session rows with metadata and caller-selected order. The ID-only ListSessions operation remains unchanged.", + "operationId": "ListSessionSummaries", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 1, + "description": "Maximum number of sessions to return. Defaults to 100. Allowed range: 1-1000.", + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "description": "Opaque token from a previous response for the next page. Bound to the effective filters and sort.", + "schema": { + "type": "string", + "description": "Opaque token from a previous response for the next page. Bound to the effective filters and sort." + } + }, + { + "name": "filterOwnerId", + "in": "query", + "description": "Filter sessions by owner. The owner of a session is the actorId of its first event. Matching is exact and case-sensitive. Combined with namespaceRef using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter sessions by owner. The owner of a session is the actorId of its first event. Matching is exact and case-sensitive. Combined with namespaceRef using AND. Mutually exclusive with includeAll." + } + }, + { + "name": "namespaceRef", + "in": "query", + "description": "Filter by stable namespace ID. Combined with filterOwnerId using AND. Mutually exclusive with includeAll.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Filter by stable namespace ID. Combined with filterOwnerId using AND. Mutually exclusive with includeAll." + } + }, + { + "name": "includeAll", + "in": "query", + "description": "Set to true to list all visible sessions. Required when no filter is given; mutually exclusive with filters.", + "schema": { + "type": "boolean", + "description": "Set to true to list all visible sessions. Required when no filter is given; mutually exclusive with filters." + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Sort field. Defaults to updatedAt.", + "schema": { + "$ref": "#/components/schemas/SessionListSortBy" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort direction. Defaults to desc for timestamps and asc for sessionId.", + "schema": { + "$ref": "#/components/schemas/SessionListSortOrder" + } + } + ], + "responses": { + "200": { + "description": "ListSessionSummaries 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSessionSummariesResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Session Memory" + ] + } + }, + "/v1/stores/{storeId}/sources/{sourceId}/memories": { + "get": { + "description": "Pages memories linked to one source. A source link does not grant access to a memory in another owner or namespace.", + "operationId": "ListSourceMemories", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "sourceId", + "in": "path", + "description": "Stable source identifier for one revisioned origin in a store.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable source identifier for one revisioned origin in a store." + }, + "required": true + }, + { + "name": "ownerId", + "in": "query", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ListSourceMemories 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSourceMemoriesResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "423": { + "description": "ResourceSuspendedError 423 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceSuspendedErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Long-Term Memory" + ] + } + }, + "/v1/stores/{storeId}/summary-views": { + "get": { + "description": "Lists Summary View definitions and high-level state. Partition values are not included.", + "operationId": "ListSummaryViews", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 100, + "maximum": 100, + "minimum": 1, + "format": "int32" + } + }, + { + "name": "pageToken", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ListSummaryViews 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSummaryViewsResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + }, + "post": { + "description": "Creates a Summary View definition and asynchronously enqueues its initial reconciliation.", + "operationId": "CreateSummaryView", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSummaryViewRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateSummaryView 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "SummaryViewQuotaError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewQuotaErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + } + }, + "/v1/stores/{storeId}/summary-views/{viewId}": { + "delete": { + "description": "Marks a Summary View deleting and asynchronously removes its schedule and materializations in bounded batches. Repeated requests are idempotent.", + "operationId": "DeleteSummaryView", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + } + ], + "responses": { + "202": { + "description": "DeleteSummaryView 202 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + }, + "get": { + "description": "Returns one Summary View definition without partition values.", + "operationId": "GetSummaryView", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetSummaryView 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + }, + "patch": { + "description": "Updates mutable Summary View fields. Source is immutable. Material changes increment definitionVersion; name- or description-only changes do not enqueue refresh work.", + "operationId": "UpdateSummaryView", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSummaryViewRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateSummaryView 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "413": { + "description": "PayloadTooLargeError 413 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayloadTooLargeErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "SummaryViewQuotaError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewQuotaErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + } + }, + "/v1/stores/{storeId}/summary-views/{viewId}/refresh": { + "post": { + "description": "Asynchronously enqueues one exact partition refresh or a complete view reconciliation. It never performs generation synchronously.", + "operationId": "RefreshSummaryView", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RefreshSummaryViewRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + } + ], + "responses": { + "202": { + "description": "RefreshSummaryView 202 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RefreshSummaryViewResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "SummaryViewQuotaError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewQuotaErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + } + }, + "/v1/stores/{storeId}/summary-views/{viewId}/result": { + "get": { + "description": "Returns one authorized exact Summary View partition after validating its contributing source records. Invalid output is withheld while repair is pending.", + "operationId": "GetSummaryViewResult", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated store IDs are typically 32-character UUIDs without dashes." + }, + "required": true + }, + { + "name": "viewId", + "in": "path", + "description": "Server-generated Summary View identifier.", + "schema": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "required": true + }, + { + "name": "ownerId", + "in": "query", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + } + }, + { + "name": "sessionId", + "in": "query", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + } + }, + { + "name": "namespaceRef", + "in": "query", + "description": "Server-generated namespace identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." + } + }, + { + "name": "memoryType", + "in": "query", + "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, `message`, and `session_summary_view`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes.\n", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, `message`, and `session_summary_view`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes." + } + }, + { + "name": "includeSourceMemoryIds", + "in": "query", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "GetSummaryViewResult 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSummaryViewResultResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "408": { + "description": "TimeoutError 408 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimeoutErrorResponseContent" + } + } + } + }, + "409": { + "description": "SummaryViewConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummaryViewConflictErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "429": { + "description": "TooManyRequestsError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyRequestsErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Summary Views" + ] + } + } + }, + "components": { + "schemas": { + "AddSessionEventRequestContent": { + "type": "object", + "properties": { + "idempotencyKey": { + "type": "string", + "maxLength": 128, + "minLength": 1, + "pattern": "^[ -~]+$", + "description": "Required when the store selects session.append input hooks. Reuse this key only for an identical request; accepted retries reuse the saved decision and event identity." + }, + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Optional session ID. If omitted, the server generates a unique ID." + }, + "actorId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Optional deprecated namespace label for the session. This label does not create or resolve a namespace resource. Do not supply it with namespaceRef.\n\nLike `ownerId`, this is a session-level value taken from the first event: the value sent with the first event of a session is stored on the session, and values sent with later events are ignored. Omit it to leave extracted memories without a namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" + }, + "role": { + "$ref": "#/components/schemas/MessageRole" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Content" + }, + "description": "The message content as an array of typed content parts." + }, + "createdAt": { + "type": "string", + "description": "Client-supplied timestamp for when the event happened (UTC).", + "format": "date-time" + }, + "metadata": { + "description": "Optional metadata as any valid JSON value." + } + }, + "required": [ + "actorId", + "content", + "createdAt", + "role" + ] + }, + "AddSessionEventResponseContent": { + "type": "object", + "description": "Response returned after appending a session event.", + "properties": { + "event": { + "$ref": "#/components/schemas/SessionEvent" + } + }, + "required": [ + "event" + ] + }, + "AttributeFilterClause": { + "description": "Filter clause for a single record attribute. Exactly one member is set; the member must match the attribute's declared field type.", + "oneOf": [ + { + "type": "object", + "title": "string", + "properties": { + "string": { + "type": "string", + "description": "Match a string attribute equal to this value." + } + }, + "required": [ + "string" + ] + }, + { + "type": "object", + "title": "number", + "properties": { + "number": { + "type": "number", + "description": "Match a numeric attribute equal to this value.", + "format": "double" + } + }, + "required": [ + "number" + ] + }, + { + "type": "object", + "title": "boolean", + "properties": { + "boolean": { + "type": "boolean", + "description": "Match a boolean attribute equal to this value." + } + }, + "required": [ + "boolean" + ] + }, + { + "type": "object", + "title": "list", + "properties": { + "list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Match a list attribute that contains all of these values." + } + }, + "required": [ + "list" + ] + } + ] + }, + "AttributeFilterMap": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/AttributeFilterClause" + }, + "description": "Per-attribute filter clauses keyed by the custom memory type's field name." + }, + "AttributeMap": { + "type": "object", + "additionalProperties": {}, + "description": "Structured record attributes: a map from the custom memory type's field name to that field's value. Values are arbitrary JSON matching the field's declared type (str, int, float, bool, list[str], list[float], or object)." + }, + "AuthenticationErrorResponseContent": { + "type": "object", + "description": "Authentication credentials are missing, malformed, or invalid.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 401, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/AuthenticationErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "AuthenticationErrorType": { + "type": "string", + "description": "Problem type URI for authentication errors.", + "enum": [ + "/errors/authentication-failed" + ] + }, + "BadRequestErrorResponseContent": { + "type": "object", + "description": "Request validation or input decoding failed.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 400, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/BadRequestErrorType" + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FieldError" + }, + "description": "Optional field-level validation errors." + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "BadRequestErrorType": { + "type": "string", + "description": "Problem type URI for bad request errors.", + "enum": [ + "/errors/invalid-data" + ] + }, + "BulkCreateLongTermMemoriesRequestContent": { + "type": "object", + "description": "Bulk create request for long-term memory records.", + "properties": { + "idempotencyKey": { + "type": "string", + "maxLength": 128, + "minLength": 1, + "pattern": "^[ -~]+$", + "description": "Required when the memory.create input hook is selected. Reuse the same key and original request to resume its saved decision." + }, + "memories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateMemoryRecord" + }, + "maxItems": 100, + "minItems": 1, + "description": "List of memory records to create." + } + }, + "required": [ + "memories" + ] + }, + "BulkCreateLongTermMemoriesResponseContent": { + "type": "object", + "description": "Bulk create result for long-term memory records.", + "properties": { + "created": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "description": "IDs of successfully created memory records. Always returned; empty when no records were created." + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkOperationError" + }, + "description": "Errors for memory records that failed to be created." + } + }, + "required": [ + "created" + ] + }, + "BulkDeleteLongTermMemoriesRequestContent": { + "type": "object", + "description": "Bulk delete request for long-term memory records.", + "properties": { + "memoryIds": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "maxItems": 100, + "minItems": 1, + "description": "List of memory record IDs to delete (1-100 IDs)." + } + }, + "required": [ + "memoryIds" + ] + }, + "BulkDeleteLongTermMemoriesResponseContent": { + "type": "object", + "description": "Bulk delete result for long-term memory records.", + "properties": { + "deleted": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "description": "IDs of successfully deleted memory records. Always returned; empty when no records were deleted." + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkOperationError" + }, + "description": "Errors for memory records that failed to be deleted." + } + }, + "required": [ + "deleted" + ] + }, + "BulkOperationError": { + "type": "object", + "description": "Error detail for a single item in a bulk operation.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record ID of the item that failed." + }, + "error": { + "type": "string", + "description": "Error message describing the failure." + } + }, + "required": [ + "error", + "id" + ] + }, + "ConflictErrorResponseContent": { + "type": "object", + "description": "The request conflicts with the current state of the resource.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 409, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/ConflictErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "ConflictErrorType": { + "type": "string", + "description": "Problem type URI for conflict errors.", + "enum": [ + "/errors/conflict" + ] + }, + "Content": { + "description": "A single content item in a message. Exactly one member is set.", + "oneOf": [ + { + "type": "object", + "title": "text", + "properties": { + "text": { + "type": "string", + "description": "Text content." + } + }, + "required": [ + "text" + ] + } + ] + }, + "CreateDocumentIngestionRequestContent": { + "type": "object", + "properties": { + "documentId": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "Customer-controlled logical document ID. Opaque and case-sensitive." + }, + "revision": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "Customer-controlled immutable revision token. Opaque and case-sensitive." + }, + "content": { + "type": "string", + "format": "byte" + }, + "mediaType": { + "$ref": "#/components/schemas/DocumentMediaType" + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + }, + "extractorId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Extractor identifier (1-64 chars, alphanumeric and dashes)." + }, + "idempotencyKey": { + "type": "string", + "maxLength": 128, + "minLength": 1, + "description": "Customer idempotency key for one document submission." + } + }, + "required": [ + "content", + "documentId", + "extractorId", + "idempotencyKey", + "mediaType", + "ownerId", + "revision" + ] + }, + "CreateDocumentIngestionResponseContent": { + "type": "object", + "properties": { + "ingestionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." + }, + "status": { + "$ref": "#/components/schemas/DocumentIngestionStatus" + } + }, + "required": [ + "ingestionId", + "status" + ] + }, + "CreateMemoryRecord": { + "type": "object", + "description": "A memory record to create.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Client-provided memory record ID for idempotent creation." + }, + "text": { + "type": "string", + "maxLength": 50000, + "minLength": 1, + "description": "The memory content (1-50000 chars)." + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Type of memory. Built-in or a custom type registered on the store." + }, + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session ID associated with this memory (1-64 chars, alphanumeric and dashes)." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner ID associated with this memory (1-64 chars, alphanumeric and dashes)." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Optional deprecated namespace label. It does not create or resolve a namespace resource. Do not supply it with namespaceRef. If both fields are omitted, the record is created without a namespace. Automatic routing does not place an un-namespaced record in a shared namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" + }, + "topics": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Topic name (1-100 chars)." + }, + "maxItems": 50, + "description": "Topic tags for categorization (max 50 topics, each 1-100 chars)." + }, + "attributes": { + "$ref": "#/components/schemas/AttributeMap" + } + }, + "required": [ + "id", + "ownerId", + "text" + ] + }, + "CreateNamespaceRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One namespace segment. Use with scope for a root or parentId for a child." + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "A complete hierarchy path to create. Missing segments are created as needed. Mutually exclusive with name and parentId. The path does not encode scope." + }, + "scope": { + "$ref": "#/components/schemas/NamespaceScope" + }, + "parentId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Parent namespace for a direct child." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner for a personal root or path. Required for personal scope; omit for shared roots and children." + } + } + }, + "CreateNamespaceResponseContent": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" + } + }, + "required": [ + "namespace" + ] + }, + "CreateSummaryViewRequestContent": { + "type": "object", + "properties": { + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + } + }, + "required": [ + "groupBy", + "name", + "output", + "refreshPolicy", + "source" + ] + }, + "CreateSummaryViewResponseContent": { + "type": "object", + "description": "An owned Summary View definition. Results are separate derived state and never enter long-term-memory search.", + "properties": { + "id": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Immutable canonical user ID assigned by the server. Clients cannot set ownership." + }, + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "createdAt", + "definitionVersion", + "filterOp", + "groupBy", + "id", + "name", + "output", + "ownerId", + "refreshPolicy", + "source", + "state", + "updatedAt", + "visibility" + ] + }, + "CreatedAtFilter": { + "type": "object", + "description": "Filter by creation timestamp.", + "properties": { + "gt": { + "type": "string", + "description": "After the given timestamp.", + "format": "date-time" + }, + "gte": { + "type": "string", + "description": "At or after the given timestamp.", + "format": "date-time" + }, + "lt": { + "type": "string", + "description": "Before the given timestamp.", + "format": "date-time" + }, + "lte": { + "type": "string", + "description": "At or before the given timestamp.", + "format": "date-time" + }, + "eq": { + "type": "string", + "description": "Exactly at the given timestamp.", + "format": "date-time" + } + } + }, + "DeleteSummaryViewResponseContent": { + "type": "object", + "properties": { + "viewId": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + } + }, + "required": [ + "state", + "viewId" + ] + }, + "DocumentIngestionCleanupState": { + "type": "string", + "enum": [ + "pending", + "deleted", + "failed" + ] + }, + "DocumentIngestionProcessingState": { + "type": "string", + "enum": [ + "queued", + "running", + "succeeded", + "failed", + "partial" + ] + }, + "DocumentIngestionRecord": { + "type": "object", + "properties": { + "ingestionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated document ingestion identifier." + }, + "status": { + "$ref": "#/components/schemas/DocumentIngestionStatus" + } + }, + "required": [ + "ingestionId", + "status" + ] + }, + "DocumentIngestionStatus": { + "type": "object", + "properties": { + "processing": { + "$ref": "#/components/schemas/DocumentIngestionProcessingState" + }, + "cleanup": { + "$ref": "#/components/schemas/DocumentIngestionCleanupState" + }, + "extractorId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Extractor identifier (1-64 chars, alphanumeric and dashes)." + }, + "extractorRevision": { + "type": "integer", + "format": "int64" + }, + "completedChunks": { + "type": "integer", + "format": "int32" + }, + "totalChunks": { + "type": "integer", + "format": "int32" + }, + "createdCount": { + "type": "integer", + "format": "int32" + }, + "filteredCount": { + "type": "integer", + "format": "int32" + }, + "error": { + "type": "string" + }, + "retentionDeadline": { + "type": "number", + "format": "double" + }, + "deletedAt": { + "type": "number", + "format": "double" + } + }, + "required": [ + "cleanup", + "processing" + ] + }, + "DocumentMediaType": { + "type": "string", + "enum": [ + "text/plain", + "text/markdown" + ] + }, + "DocumentSourceIdentity": { + "type": "object", + "properties": { + "documentId": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "Customer-controlled logical document ID. Opaque and case-sensitive." + }, + "revision": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "description": "Customer-controlled immutable revision token. Opaque and case-sensitive." + } + }, + "required": [ + "documentId", + "revision" + ] + }, + "EvidenceProducer": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 128, + "minLength": 1 + } + }, + "required": [ + "name" + ] + }, + "FailedDependencyErrorResponseContent": { + "type": "object", + "description": "A dependent resource required to process the request is unavailable or unhealthy.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 424, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/FailedDependencyErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "FailedDependencyErrorType": { + "type": "string", + "description": "Problem type URI for failed-dependency errors.", + "enum": [ + "/errors/resource-unavailable", + "/errors/database-out-of-memory" + ] + }, + "FieldError": { + "type": "object", + "description": "Validation error details for a single request field.", + "properties": { + "field": { + "type": "string", + "description": "Name of the invalid request field." + }, + "rule": { + "type": "string", + "description": "Validation rule that was violated." + }, + "message": { + "type": "string", + "description": "Human-readable validation error message." + } + }, + "required": [ + "field", + "message", + "rule" + ] + }, + "FilterConjunction": { + "type": "string", + "description": "Filter conjunction mode.", + "enum": [ + "all", + "any" + ] + }, + "ForbiddenErrorResponseContent": { + "type": "object", + "description": "The caller is authenticated but not allowed to access the requested resource.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 403, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/ForbiddenErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "ForbiddenErrorType": { + "type": "string", + "description": "Problem type URI for authorization errors.", + "enum": [ + "/errors/insufficient-permissions" + ] + }, + "GetDocumentIngestionResponseContent": { + "type": "object", + "properties": { + "ingestion": { + "$ref": "#/components/schemas/DocumentIngestionRecord" + } + }, + "required": [ + "ingestion" + ] + }, + "GetIngestionHookStatusResponseContent": { + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/IngestionHookStatus" + } + }, + "required": [ + "status" + ] + }, + "GetLongTermMemoryResponseContent": { + "type": "object", + "description": "A long-term memory record.", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Unique memory record ID." + }, + "text": { + "type": "string", + "description": "The memory content." + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Type of memory. Built-in or a custom type registered on the store." + }, + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Session ID associated with this memory." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Owner ID associated with this memory." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" + }, + "topics": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Topic name (1-100 chars)." + }, + "maxItems": 50, + "description": "Topic tags for categorization." + }, + "attributes": { + "$ref": "#/components/schemas/AttributeMap" + }, + "createdAt": { + "type": "string", + "description": "Timestamp when the memory was created (UTC).", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "description": "Timestamp when the memory was last updated (UTC).", + "format": "date-time" + }, + "sources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemorySourceLink" + }, + "description": "Generic source links for this memory. Document sources include the customer document ID and revision that produced the memory." + }, + "sourcesNextPageToken": { + "type": "string", + "description": "Opaque token for additional source links when this response truncated attribution. Absent when every link is included." + }, + "documentManaged": { + "type": "boolean", + "description": "True when this memory was created by document ingestion and is protected from generic overwrite." + } + }, + "required": [ + "createdAt", + "id", + "text", + "updatedAt" + ] + }, + "GetNamespaceResponseContent": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" + } + }, + "required": [ + "namespace" + ] + }, + "GetNamespaceRoutingResponseContent": { + "type": "object", + "properties": { + "policy": { + "$ref": "#/components/schemas/NamespaceRoutingPolicy" + } + }, + "required": [ + "policy" + ] + }, + "GetSessionEventResponseContent": { + "type": "object", + "description": "Response containing a single requested session event.", + "properties": { + "event": { + "$ref": "#/components/schemas/SessionEvent" + } + }, + "required": [ + "event" + ] + }, + "GetSessionMemoryResponseContent": { + "type": "object", + "description": "Session memory state for a session.", + "properties": { + "sessionId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The session ID." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The owner of the session, set from the actorId of the first event." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated namespace label for the session, set from the first event. Absent when the session uses namespaceRef or has no namespace.", + "deprecated": true + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" + }, + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionEvent" }, - "required": [ - "boolean" - ] + "description": "Conversation events retained for this session. When summarization is enabled, events already covered by the summary are excluded unless `includeSummarisedEvents` is set; the compacted summary is returned separately in `summary`." }, - { - "type": "object", - "title": "list", - "properties": { - "list": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Match a list attribute that contains all of these values." - } + "summary": { + "$ref": "#/components/schemas/SessionSummary" + } + }, + "required": [ + "events", + "ownerId", + "sessionId" + ] + }, + "GetSummaryViewResponseContent": { + "type": "object", + "description": "An owned Summary View definition. Results are separate derived state and never enter long-term-memory search.", + "properties": { + "id": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Immutable canonical user ID assigned by the server. Clients cannot set ownership." + }, + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" }, - "required": [ - "list" - ] + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" } + }, + "required": [ + "createdAt", + "definitionVersion", + "filterOp", + "groupBy", + "id", + "name", + "output", + "ownerId", + "refreshPolicy", + "source", + "state", + "updatedAt", + "visibility" ] }, - "AttributeFilterMap": { + "GetSummaryViewResultResponseContent": { "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/AttributeFilterClause" + "properties": { + "viewId": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "scope": { + "$ref": "#/components/schemas/SummaryViewScope" + }, + "status": { + "$ref": "#/components/schemas/SummaryViewResultStatus" + }, + "stale": { + "type": "boolean" + }, + "format": { + "$ref": "#/components/schemas/SummaryViewOutputFormat" + }, + "value": { + "$ref": "#/components/schemas/SummaryViewResultValue" + }, + "sourceMemoryCount": { + "type": "integer", + "minimum": 0, + "description": "Number of matching source memories. When selection stops early, a separate count query measures the matching total without reading the remaining source pages. Concurrent writes can change the source set between queries; the count is never less than the number of records already observed.", + "format": "int64" + }, + "summarizedMemoryCount": { + "type": "integer", + "minimum": 0, + "description": "Number of matching source memories included in the generated value after bounded deterministic selection.", + "format": "int64" + }, + "truncated": { + "type": "boolean", + "description": "Combined signal for sourcesOmitted or sourceContentTruncated." + }, + "sourcesOmitted": { + "type": "boolean", + "description": "True when matching source memories were excluded from generation, including when further source pages were not read after reaching an input budget." + }, + "sourceContentTruncated": { + "type": "boolean", + "description": "True when content from an included source memory was clipped to fit a source or generation input budget. This can be true even when sourceMemoryCount equals summarizedMemoryCount." + }, + "sourceMemoryIds": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "description": "The memory IDs selected for the materialized value. Included only when requested with includeSourceMemoryIds=true." + }, + "sourceUpdatedAt": { + "type": "string", + "format": "date-time" + }, + "computedAt": { + "type": "string", + "format": "date-time" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "errorCode": { + "$ref": "#/components/schemas/SummaryViewResultErrorCode" + } }, - "description": "Per-attribute filter clauses keyed by the custom memory type's field name." + "required": [ + "definitionVersion", + "format", + "scope", + "sourceContentTruncated", + "sourceMemoryCount", + "sourcesOmitted", + "stale", + "status", + "summarizedMemoryCount", + "truncated", + "viewId" + ] }, - "AttributeMap": { + "HookStatusSource": { "type": "object", - "additionalProperties": {}, - "description": "Structured record attributes: a map from the custom memory type's field name to that field's value. Values are arbitrary JSON matching the field's declared type (str, int, float, bool, list[str], list[float], or object)." + "additionalProperties": { + "type": "string" + } }, - "AuthenticationErrorResponseContent": { + "IngestionHookRetryErrorResponseContent": { "type": "object", - "description": "Authentication credentials are missing, malformed, or invalid.", + "description": "A saved ingestion hook decision is waiting for its next bounded attempt.", "properties": { "title": { "type": "string", @@ -3421,7 +7124,7 @@ }, "status": { "type": "integer", - "default": 401, + "default": 503, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -3434,25 +7137,116 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/AuthenticationErrorType" + "$ref": "#/components/schemas/IngestionHookRetryErrorType" + }, + "retryAt": { + "type": "string", + "format": "date-time" } }, "required": [ + "retryAt", "status", "title", "type" ] }, - "AuthenticationErrorType": { + "IngestionHookRetryErrorType": { "type": "string", - "description": "Problem type URI for authentication errors.", "enum": [ - "/errors/authentication-failed" + "/errors/ingestion-hook-retry" ] }, - "BadRequestErrorResponseContent": { + "IngestionHookStatus": { "type": "object", - "description": "Request validation or input decoding failed.", + "description": "Compact hook metadata, retained for seven days after completion or expiry. Application state is independent of the decision state; earlier writes may have completed before a later failure.", + "properties": { + "eventId": { + "type": "string" + }, + "ingestionOperationId": { + "type": "string" + }, + "phase": { + "type": "string" + }, + "path": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/HookStatusSource" + }, + "configRevision": { + "type": "integer", + "format": "int64" + }, + "state": { + "type": "string" + }, + "applyState": { + "type": "string" + }, + "attempts": { + "type": "integer", + "format": "int32" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "deadline": { + "type": "string", + "format": "date-time" + }, + "nextAttemptAt": { + "type": "string", + "format": "date-time" + }, + "lastError": { + "type": "string" + }, + "applied": { + "type": "integer", + "format": "int32" + }, + "filtered": { + "type": "integer", + "format": "int32" + }, + "conflicts": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "applied", + "applyState", + "attempts", + "configRevision", + "conflicts", + "createdAt", + "deadline", + "eventId", + "filtered", + "ingestionOperationId", + "ownerId", + "path", + "phase", + "source", + "state", + "updatedAt" + ] + }, + "IngestionHookStoppedErrorResponseContent": { + "type": "object", + "description": "The ingestion hook stopped this operation without applying unapproved content.", "properties": { "title": { "type": "string", @@ -3460,7 +7254,7 @@ }, "status": { "type": "integer", - "default": 400, + "default": 422, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -3473,147 +7267,292 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/BadRequestErrorType" + "$ref": "#/components/schemas/IngestionHookStoppedErrorType" }, - "fields": { + "state": { + "$ref": "#/components/schemas/IngestionHookStoppedState" + } + }, + "required": [ + "state", + "status", + "title", + "type" + ] + }, + "IngestionHookStoppedErrorType": { + "type": "string", + "enum": [ + "/errors/ingestion-hook-stopped" + ] + }, + "IngestionHookStoppedState": { + "type": "string", + "enum": [ + "rejected", + "failed", + "expired", + "cancelled", + "conflict" + ] + }, + "ListDocumentIngestionOutputsResponseContent": { + "type": "object", + "properties": { + "memoryIds": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + } + } + }, + "required": [ + "memoryIds" + ] + }, + "ListIngestionHookStatusesResponseContent": { + "type": "object", + "properties": { + "statuses": { "type": "array", "items": { - "$ref": "#/components/schemas/FieldError" - }, - "description": "Optional field-level validation errors." + "$ref": "#/components/schemas/IngestionHookStatus" + } + }, + "nextCursor": { + "type": "string" } }, "required": [ - "status", - "title", - "type" + "statuses" ] }, - "BadRequestErrorType": { - "type": "string", - "description": "Problem type URI for bad request errors.", - "enum": [ - "/errors/invalid-data" + "ListMemorySourceEvidenceResponseContent": { + "type": "object", + "properties": { + "source": { + "$ref": "#/components/schemas/MemorySourceLink" + } + }, + "required": [ + "source" ] }, - "BulkCreateLongTermMemoriesRequestContent": { + "ListMemorySourceLinksResponseContent": { "type": "object", - "description": "Bulk create request for long-term memory records.", "properties": { - "memories": { + "items": { "type": "array", "items": { - "$ref": "#/components/schemas/CreateMemoryRecord" - }, - "maxItems": 100, - "minItems": 1, - "description": "List of memory records to create." + "$ref": "#/components/schemas/MemorySourceLink" + } + }, + "nextPageToken": { + "type": "string" } }, "required": [ - "memories" + "items" ] }, - "BulkCreateLongTermMemoriesResponseContent": { + "ListNamespacesResponseContent": { "type": "object", - "description": "Bulk create result for long-term memory records.", "properties": { - "created": { + "items": { "type": "array", "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." - }, - "description": "IDs of successfully created memory records. Always returned; empty when no records were created." + "$ref": "#/components/schemas/Namespace" + } }, - "errors": { + "nextPageToken": { + "type": "string", + "description": "Opaque token for fetching the next page. Omitted when no more results." + } + }, + "required": [ + "items" + ] + }, + "ListSessionSummariesResponseContent": { + "type": "object", + "description": "Paginated session rows for a store.", + "properties": { + "items": { "type": "array", "items": { - "$ref": "#/components/schemas/BulkOperationError" - }, - "description": "Errors for memory records that failed to be created." + "$ref": "#/components/schemas/SessionListItem" + } + }, + "total": { + "type": "integer", + "format": "int32" + }, + "nextPageToken": { + "type": "string" } }, "required": [ - "created" + "items", + "total" ] }, - "BulkDeleteLongTermMemoriesRequestContent": { + "ListSessionsResponseContent": { "type": "object", - "description": "Bulk delete request for long-term memory records.", + "description": "Paginated list of session IDs for a store.", "properties": { - "memoryIds": { + "items": { "type": "array", "items": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." }, - "maxItems": 100, - "minItems": 1, - "description": "List of memory record IDs to delete (1-100 IDs)." + "description": "List of session IDs. Always returned; empty when the store has no sessions." + }, + "total": { + "type": "integer", + "description": "Total number of matching sessions.", + "format": "int32" + }, + "nextPageToken": { + "type": "string", + "description": "Opaque token for fetching the next page. Omitted when no more results." } }, "required": [ - "memoryIds" + "items", + "total" ] }, - "BulkDeleteLongTermMemoriesResponseContent": { + "ListSourceMemoriesResponseContent": { "type": "object", - "description": "Bulk delete result for long-term memory records.", "properties": { - "deleted": { + "items": { "type": "array", "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." - }, - "description": "IDs of successfully deleted memory records. Always returned; empty when no records were deleted." + "$ref": "#/components/schemas/MemoryRecord" + } }, - "errors": { + "nextPageToken": { + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "ListSummaryViewsResponseContent": { + "type": "object", + "properties": { + "items": { "type": "array", "items": { - "$ref": "#/components/schemas/BulkOperationError" - }, - "description": "Errors for memory records that failed to be deleted." + "$ref": "#/components/schemas/SummaryView" + } + }, + "nextPageToken": { + "type": "string" } }, "required": [ - "deleted" + "items" ] }, - "BulkOperationError": { + "LlmPromptSpec": { "type": "object", - "description": "Error detail for a single item in a bulk operation.", + "description": "Customer-authored prompt evaluated by the LLM routing strategy.", "properties": { - "id": { + "prompt": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Memory record ID of the item that failed." + "maxLength": 2000, + "minLength": 1 + } + }, + "required": [ + "prompt" + ] + }, + "LongTermMemoryFilter": { + "type": "object", + "description": "Grouping of filter conditions for long-term memory search.", + "properties": { + "sessionId": { + "$ref": "#/components/schemas/SessionIdFilter" }, - "error": { - "type": "string", - "description": "Error message describing the failure." + "ownerId": { + "$ref": "#/components/schemas/OwnerIdFilter" + }, + "namespace": { + "$ref": "#/components/schemas/NamespaceFilter" + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefFilter" + }, + "unnamespaced": { + "type": "boolean", + "description": "When true, matches only records that have neither a deprecated namespace label nor a namespace resource placement. This scope is always combined with every other filter condition. Do not supply namespace or namespaceRef with this field." + }, + "topics": { + "$ref": "#/components/schemas/TopicsFilter" + }, + "memoryType": { + "$ref": "#/components/schemas/MemoryTypeFilter" + }, + "createdAt": { + "$ref": "#/components/schemas/CreatedAtFilter" + }, + "attributes": { + "$ref": "#/components/schemas/AttributeFilterMap" + } + } + }, + "LongTermMemorySearchSortBy": { + "type": "string", + "description": "Timestamp field used to order long-term memory search results.", + "enum": [ + "createdAt", + "updatedAt" + ] + }, + "LongTermMemorySearchSortOrder": { + "type": "string", + "description": "Direction used to order long-term memory search results.", + "enum": [ + "asc", + "desc" + ] + }, + "MatcherKind": { + "type": "string", + "description": "The matching techniques this API supports.", + "enum": [ + "regex" + ] + }, + "MatcherSpec": { + "type": "object", + "description": "How a detector matches, as a tagged union of techniques.\n\nExactly the block naming the declared kind must be present; a block belonging to\nany other kind is rejected rather than guessed at. Further kinds are additive.", + "properties": { + "kind": { + "$ref": "#/components/schemas/MatcherKind" + }, + "regex": { + "$ref": "#/components/schemas/RegexSpec" } }, "required": [ - "error", - "id" + "kind" ] }, - "ConflictErrorResponseContent": { + "MemoryFailedDependencyErrorResponseContent": { "type": "object", - "description": "The request conflicts with the current state of the resource.", + "description": "A resource or model-provider dependency required by Agent Memory could not complete the request.", "properties": { "title": { "type": "string", @@ -3621,7 +7560,7 @@ }, "status": { "type": "integer", - "default": 409, + "default": 424, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -3634,7 +7573,7 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/ConflictErrorType" + "$ref": "#/components/schemas/MemoryFailedDependencyErrorType" } }, "required": [ @@ -3643,47 +7582,30 @@ "type" ] }, - "ConflictErrorType": { - "type": "string", - "description": "Problem type URI for conflict errors.", - "enum": [ - "/errors/conflict" - ] - }, - "Content": { - "description": "A single content item in a message. Exactly one member is set.", - "oneOf": [ - { - "type": "object", - "title": "text", - "properties": { - "text": { - "type": "string", - "description": "Text content." - } - }, - "required": [ - "text" - ] - } + "MemoryFailedDependencyErrorType": { + "type": "string", + "description": "Problem type URI for Agent Memory failed-dependency errors.", + "enum": [ + "/errors/resource-unavailable", + "/errors/database-out-of-memory", + "/errors/invalid-model-credentials", + "/errors/model-quota-exceeded" ] }, - "CreateMemoryRecord": { + "MemoryRecord": { "type": "object", - "description": "A memory record to create.", + "description": "A long-term memory record.", "properties": { "id": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Client-provided memory record ID for idempotent creation." + "description": "Unique memory record ID." }, "text": { "type": "string", - "maxLength": 50000, - "minLength": 1, - "description": "The memory content (1-50000 chars)." + "description": "The memory content." }, "memoryType": { "type": "string", @@ -3697,25 +7619,25 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session ID associated with this memory (1-64 chars, alphanumeric and dashes)." + "description": "Session ID associated with this memory." }, "ownerId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner ID associated with this memory (1-64 chars, alphanumeric and dashes)." + "description": "Owner ID associated with this memory." }, "namespace": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Optional deprecated namespace label. It does not create or resolve a namespace resource. Do not supply it with namespaceRef. If both fields are omitted, the record uses automatic routing when enabled; if routing is disabled or no destination qualifies, it is created without a namespace.", + "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", "deprecated": true }, "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRefInput" + "$ref": "#/components/schemas/NamespaceRef" }, "topics": { "type": "array", @@ -3726,504 +7648,513 @@ "description": "Topic name (1-100 chars)." }, "maxItems": 50, - "description": "Topic tags for categorization (max 50 topics, each 1-100 chars)." + "description": "Topic tags for categorization." }, "attributes": { "$ref": "#/components/schemas/AttributeMap" + }, + "createdAt": { + "type": "string", + "description": "Timestamp when the memory was created (UTC).", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "description": "Timestamp when the memory was last updated (UTC).", + "format": "date-time" + }, + "sources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemorySourceLink" + }, + "description": "Generic source links for this memory. Document sources include the customer document ID and revision that produced the memory." + }, + "sourcesNextPageToken": { + "type": "string", + "description": "Opaque token for additional source links when this response truncated attribution. Absent when every link is included." + }, + "documentManaged": { + "type": "boolean", + "description": "True when this memory was created by document ingestion and is protected from generic overwrite." } }, "required": [ + "createdAt", "id", - "ownerId", - "text" + "text", + "updatedAt" ] }, - "CreateNamespaceRequestContent": { + "MemorySourceEvidence": { "type": "object", "properties": { - "name": { + "evidenceId": { "type": "string", - "maxLength": 64, + "maxLength": 128, "minLength": 1, - "pattern": "^[a-zA-Z0-9_-]+$", - "description": "One namespace segment. Use with scope for a root or parentId for a child." + "description": "Identity of a logical extraction occurrence. Delivery retries retain this ID; a new extraction receives a new ID. Absent on legacy evidence." }, - "path": { + "schemaVersion": { + "type": "integer", + "description": "Version of the evidence metadata contract, currently 1. Absent on legacy evidence.", + "format": "int32" + }, + "capturedAt": { "type": "string", - "maxLength": 1024, - "minLength": 1, - "description": "A complete hierarchy path to create. Missing segments are created as needed. Mutually exclusive with name and parentId. The path does not encode scope." + "description": "UTC time when the producer prepared the input for extraction, before model invocation. Preserved through checkpoint replay. This is not the source event time.", + "format": "date-time" }, - "scope": { - "$ref": "#/components/schemas/NamespaceScope" + "producer": { + "$ref": "#/components/schemas/EvidenceProducer" }, - "parentId": { + "ingestionId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Parent namespace for a direct child." + "description": "Server-generated document ingestion identifier." }, - "ownerId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner for a personal root or path. Required for personal scope; omit for shared roots and children." - } - } - }, - "CreateNamespaceResponseContent": { - "type": "object", - "properties": { - "namespace": { - "$ref": "#/components/schemas/Namespace" + "chunkId": { + "type": "string" + }, + "start": { + "type": "integer", + "format": "int32" + }, + "end": { + "type": "integer", + "format": "int32" } }, "required": [ - "namespace" + "chunkId", + "ingestionId" ] }, - "CreatedAtFilter": { + "MemorySourceKind": { + "type": "string", + "enum": [ + "document", + "session" + ] + }, + "MemorySourceLink": { "type": "object", - "description": "Filter by creation timestamp.", "properties": { - "gt": { - "type": "string", - "description": "After the given timestamp.", - "format": "date-time" + "sourceId": { + "type": "string" }, - "gte": { - "type": "string", - "description": "At or after the given timestamp.", - "format": "date-time" + "kind": { + "$ref": "#/components/schemas/MemorySourceKind" }, - "lt": { - "type": "string", - "description": "Before the given timestamp.", - "format": "date-time" + "identity": { + "$ref": "#/components/schemas/DocumentSourceIdentity" }, - "lte": { - "type": "string", - "description": "At or before the given timestamp.", - "format": "date-time" + "session": { + "$ref": "#/components/schemas/SessionSourceIdentity" }, - "eq": { + "evidence": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemorySourceEvidence" + } + }, + "sessionEvidence": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionSourceEvidence" + } + }, + "evidenceNextPageToken": { "type": "string", - "description": "Exactly at the given timestamp.", - "format": "date-time" + "description": "Opaque token for GET /v1/stores/{storeId}/long-term-memory/{memoryId}/sources/{sourceId}/evidence. Continues evidence or sessionEvidence for this source; absent when every item is included." } - } + }, + "required": [ + "kind", + "sourceId" + ] }, - "FailedDependencyErrorResponseContent": { + "MemoryTypeFilter": { "type": "object", - "description": "A dependent resource required to process the request is unavailable or unhealthy.", + "description": "Filter by memory type. Matching is case-insensitive.", "properties": { - "title": { + "eq": { "type": "string", - "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." - }, - "status": { - "type": "integer", - "default": 424, - "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", - "format": "int32" + "description": "Equals the given value." }, - "detail": { + "ne": { "type": "string", - "description": "A human-readable explanation specific to this occurrence of the problem." + "description": "Not equal to the given value." }, - "instance": { - "type": "string", - "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + "in": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Matches any of the given values." }, - "type": { - "$ref": "#/components/schemas/FailedDependencyErrorType" + "all": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Matches all of the given values." } - }, - "required": [ - "status", - "title", - "type" - ] + } }, - "FailedDependencyErrorType": { + "MessageRole": { "type": "string", - "description": "Problem type URI for failed-dependency errors.", + "description": "Message role for session events.", "enum": [ - "/errors/resource-unavailable", - "/errors/database-out-of-memory" + "USER", + "ASSISTANT", + "SYSTEM" ] }, - "FieldError": { + "MoveLongTermMemoriesRequestContent": { "type": "object", - "description": "Validation error details for a single request field.", "properties": { - "field": { - "type": "string", - "description": "Name of the invalid request field." - }, - "rule": { - "type": "string", - "description": "Validation rule that was violated." + "memoryIds": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + }, + "maxItems": 100, + "minItems": 1 }, - "message": { - "type": "string", - "description": "Human-readable validation error message." + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRefInput" } }, "required": [ - "field", - "message", - "rule" - ] - }, - "FilterConjunction": { - "type": "string", - "description": "Filter conjunction mode.", - "enum": [ - "all", - "any" + "memoryIds" ] }, - "ForbiddenErrorResponseContent": { + "MoveLongTermMemoriesResponseContent": { "type": "object", - "description": "The caller is authenticated but not allowed to access the requested resource.", "properties": { - "title": { - "type": "string", - "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." - }, - "status": { - "type": "integer", - "default": 403, - "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", - "format": "int32" - }, - "detail": { - "type": "string", - "description": "A human-readable explanation specific to this occurrence of the problem." - }, - "instance": { - "type": "string", - "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + "moved": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." + } }, - "type": { - "$ref": "#/components/schemas/ForbiddenErrorType" + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkOperationError" + } } }, "required": [ - "status", - "title", - "type" + "moved" ] }, - "ForbiddenErrorType": { + "MutableSummaryViewState": { "type": "string", - "description": "Problem type URI for authorization errors.", + "description": "States a caller may set directly. Deleting is entered only by the delete operation.", "enum": [ - "/errors/insufficient-permissions" + "active", + "disabled" ] }, - "GetLongTermMemoryResponseContent": { + "Namespace": { "type": "object", - "description": "A long-term memory record.", + "description": "A namespace hierarchy record.", "properties": { - "id": { + "namespaceId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique memory record ID." - }, - "text": { - "type": "string", - "description": "The memory content." + "description": "Server-generated namespace identifier." }, - "memoryType": { + "name": { "type": "string", "maxLength": 64, "minLength": 1, - "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Type of memory. Built-in or a custom type registered on the store." + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "One case-sensitive namespace path segment." }, - "sessionId": { + "parentId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session ID associated with this memory." + "description": "Server-generated namespace identifier." }, - "ownerId": { + "rootId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner ID associated with this memory." + "description": "Server-generated namespace identifier." }, - "namespace": { + "ownerId": { "type": "string", - "maxLength": 64, "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", - "deprecated": true + "description": "An opaque application-user ID selected by the platform for a namespace root." }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRef" + "scope": { + "$ref": "#/components/schemas/NamespaceScope" }, - "topics": { - "type": "array", - "items": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "description": "Topic name (1-100 chars)." - }, - "maxItems": 50, - "description": "Topic tags for categorization." + "state": { + "$ref": "#/components/schemas/NamespaceState" }, - "attributes": { - "$ref": "#/components/schemas/AttributeMap" + "revision": { + "type": "integer", + "format": "int64" }, "createdAt": { "type": "string", - "description": "Timestamp when the memory was created (UTC).", "format": "date-time" }, "updatedAt": { "type": "string", - "description": "Timestamp when the memory was last updated (UTC).", "format": "date-time" + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "Current case-sensitive path, derived from the live hierarchy." } }, "required": [ "createdAt", - "id", - "text", + "name", + "namespaceId", + "path", + "revision", + "rootId", + "scope", + "state", "updatedAt" ] }, - "GetNamespaceResponseContent": { - "type": "object", - "properties": { - "namespace": { - "$ref": "#/components/schemas/Namespace" - } - }, - "required": [ - "namespace" - ] - }, - "GetNamespaceRoutingResponseContent": { - "type": "object", - "properties": { - "policy": { - "$ref": "#/components/schemas/NamespaceRoutingPolicy" - } - }, - "required": [ - "policy" - ] - }, - "GetSessionEventResponseContent": { - "type": "object", - "description": "Response containing a single requested session event.", - "properties": { - "event": { - "$ref": "#/components/schemas/SessionEvent" - } - }, - "required": [ - "event" - ] - }, - "GetSessionMemoryResponseContent": { + "NamespaceFilter": { "type": "object", - "description": "Session memory state for a session.", + "description": "Filter by namespace. Matching is case-insensitive.\n\nNamespace is optional, so records without one need care. The positive operators (`eq`, `in`, `all`) require the field to be present and therefore never match a record that has no namespace \u2014 including memories extracted from a session that has none. `ne` is a plain negation and *does* match those records, so use `eq` or `in` when you need strict namespace isolation rather than `ne` against the namespaces you want to exclude.\n\nOmit this filter to search across every namespace, and use it to keep recall inside one project, workspace, team, environment, or tenant.", "properties": { - "sessionId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "The session ID." - }, - "ownerId": { + "eq": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "The owner of the session, set from the actorId of the first event." + "description": "Equals the given value.", + "deprecated": true }, - "namespace": { + "ne": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Deprecated namespace label for the session, set from the first event. Absent when the session uses namespaceRef or has no namespace.", + "description": "Not equal to the given value.", "deprecated": true }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRef" - }, - "events": { + "in": { "type": "array", "items": { - "$ref": "#/components/schemas/SessionEvent" + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true }, - "description": "Conversation events retained for this session. When summarization is enabled, events already covered by the summary are excluded unless `includeSummarisedEvents` is set; the compacted summary is returned separately in `summary`." + "description": "Matches any of the given values." }, - "summary": { - "$ref": "#/components/schemas/SessionSummary" - } - }, - "required": [ - "events", - "ownerId", - "sessionId" - ] - }, - "HealthResponseContent": { - "type": "object", - "description": "Health check response.", - "properties": { - "status": { - "$ref": "#/components/schemas/HealthStatus" + "all": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", + "deprecated": true + }, + "description": "Matches all of the given values." } - }, - "required": [ - "status" - ] - }, - "HealthStatus": { - "type": "string", - "description": "Health status returned by the /health endpoint.", - "enum": [ - "healthy" - ] + } }, - "ListNamespacesResponseContent": { + "NamespaceRef": { "type": "object", + "description": "Reference to a namespace resource with its stable identifier and current display data.", "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Namespace" - } + "namespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable namespace resource identifier." }, - "nextPageToken": { + "name": { "type": "string", - "description": "Opaque token for fetching the next page. Omitted when no more results." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Current namespace name." + }, + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "Current namespace path." } }, "required": [ - "items" + "name", + "namespaceId", + "path" ] }, - "ListSessionsResponseContent": { + "NamespaceRefFilter": { "type": "object", - "description": "Paginated list of session IDs for a store.", + "description": "Filter by stable namespace resource identifier. Supports exact or any-of matching and matches only resource-backed records, never deprecated namespace labels.", "properties": { - "items": { + "eq": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Equals the given value." + }, + "in": { "type": "array", "items": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." + "description": "Server-generated namespace identifier." }, - "description": "List of session IDs. Always returned; empty when the store has no sessions." - }, - "total": { - "type": "integer", - "description": "Total number of matching sessions.", - "format": "int32" - }, - "nextPageToken": { + "description": "Matches any of the given values." + } + } + }, + "NamespaceRefInput": { + "type": "object", + "description": "Reference to an existing namespace resource for a write or namespace-scoped operation.", + "properties": { + "namespaceId": { "type": "string", - "description": "Opaque token for fetching the next page. Omitted when no more results." + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Stable namespace resource identifier." } }, "required": [ - "items", - "total" + "namespaceId" ] }, - "LlmPromptSpec": { + "NamespaceRoutingPolicy": { "type": "object", - "description": "Customer-authored prompt evaluated by the LLM routing strategy.", + "description": "Store-level automatic namespace routing and admission policy. Global rules target shared namespaces only. Accept rules select candidates in list order. All enabled reject rules for a destination apply regardless of their position or how the destination was selected.", "properties": { - "prompt": { - "type": "string", - "maxLength": 2000, - "minLength": 1 + "enabled": { + "type": "boolean" + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NamespaceRoutingRule" + }, + "maxItems": 32 } }, "required": [ - "prompt" + "enabled" ] }, - "LongTermMemoryFilter": { + "NamespaceRoutingRule": { "type": "object", - "description": "Grouping of filter conditions for long-term memory search.", + "description": "One routing or admission rule. action is accept or reject. Strategies run in list order and the first match fires the rule. An accept with no strategies always matches; a reject with no strategies never matches. A matching accept selects a candidate. Every enabled reject for that destination must pass before placement. A veto skips that candidate during automatic routing and fails explicit placement.", "properties": { - "sessionId": { - "$ref": "#/components/schemas/SessionIdFilter" - }, - "ownerId": { - "$ref": "#/components/schemas/OwnerIdFilter" - }, - "namespace": { - "$ref": "#/components/schemas/NamespaceFilter" - }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRefFilter" - }, - "unnamespaced": { - "type": "boolean", - "description": "When true, matches only records that have neither a deprecated namespace label nor a namespace resource placement. This scope is always combined with every other filter condition. Do not supply namespace or namespaceRef with this field." + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Routing rule identifier (1-64 chars). Published in logs, so it is an identifier rather than free text." }, - "topics": { - "$ref": "#/components/schemas/TopicsFilter" + "destination": { + "$ref": "#/components/schemas/NamespaceRefInput" }, - "memoryType": { - "$ref": "#/components/schemas/MemoryTypeFilter" + "enabled": { + "type": "boolean" }, - "createdAt": { - "$ref": "#/components/schemas/CreatedAtFilter" + "action": { + "$ref": "#/components/schemas/RoutingRuleAction" }, - "attributes": { - "$ref": "#/components/schemas/AttributeFilterMap" + "strategies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RouteCondition" + }, + "maxItems": 8 } - } + }, + "required": [ + "action", + "destination", + "enabled", + "id" + ] }, - "MatcherKind": { + "NamespaceScope": { "type": "string", - "description": "The matching techniques this API supports.", + "description": "The root scope of a namespace hierarchy.", "enum": [ - "regex" + "PERSONAL", + "SHARED" ] }, - "MatcherSpec": { - "type": "object", - "description": "How a detector matches, as a tagged union of techniques.\n\nExactly the block naming the declared kind must be present; a block belonging to\nany other kind is rejected rather than guessed at. Further kinds are additive.", - "properties": { - "kind": { - "$ref": "#/components/schemas/MatcherKind" - }, - "regex": { - "$ref": "#/components/schemas/RegexSpec" - } - }, - "required": [ - "kind" + "NamespaceSortBy": { + "type": "string", + "enum": [ + "id", + "name", + "createdAt", + "updatedAt" ] }, - "MemoryFailedDependencyErrorResponseContent": { + "NamespaceSortOrder": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "NamespaceState": { + "type": "string", + "description": "Whether a namespace accepts new children and placements.", + "enum": [ + "ACTIVE", + "ARCHIVED" + ] + }, + "NamespaceUpdateState": { + "type": "string", + "description": "The archive-only state transition accepted by UpdateNamespace.", + "enum": [ + "ARCHIVED" + ] + }, + "NotFoundErrorResponseContent": { "type": "object", - "description": "A resource or model-provider dependency required by Agent Memory could not complete the request.", + "description": "The requested resource does not exist.", "properties": { "title": { "type": "string", @@ -4231,7 +8162,7 @@ }, "status": { "type": "integer", - "default": 424, + "default": 404, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -4244,7 +8175,7 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/MemoryFailedDependencyErrorType" + "$ref": "#/components/schemas/NotFoundErrorType" } }, "required": [ @@ -4253,272 +8184,382 @@ "type" ] }, - "MemoryFailedDependencyErrorType": { + "NotFoundErrorType": { "type": "string", - "description": "Problem type URI for Agent Memory failed-dependency errors.", + "description": "Problem type URI for not-found errors.", "enum": [ - "/errors/resource-unavailable", - "/errors/database-out-of-memory", - "/errors/invalid-model-credentials", - "/errors/model-quota-exceeded" + "/errors/resource-not-found" ] }, - "MemoryRecord": { + "OwnerIdFilter": { "type": "object", - "description": "A long-term memory record.", + "description": "Filter by the long-term memory ownerId field. Matching is case-sensitive.", "properties": { - "id": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique memory record ID." - }, - "text": { - "type": "string", - "description": "The memory content." - }, - "memoryType": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Type of memory. Built-in or a custom type registered on the store." - }, - "sessionId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session ID associated with this memory." - }, - "ownerId": { + "eq": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Owner ID associated with this memory." + "description": "Equals the given value." }, - "namespace": { + "ne": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Deprecated namespace label. Absent when the record uses namespaceRef or has no namespace.", - "deprecated": true + "description": "Not equal to the given value." }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRef" + "in": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + }, + "description": "Matches any of the given values." }, - "topics": { + "all": { "type": "array", "items": { "type": "string", - "maxLength": 100, + "maxLength": 64, "minLength": 1, - "description": "Topic name (1-100 chars)." + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." }, - "maxItems": 50, - "description": "Topic tags for categorization." + "description": "Matches all of the given values." + } + } + }, + "PayloadTooLargeErrorResponseContent": { + "type": "object", + "description": "The request payload exceeds the maximum supported size.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." }, - "attributes": { - "$ref": "#/components/schemas/AttributeMap" + "status": { + "type": "integer", + "default": 413, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" }, - "createdAt": { + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/PayloadTooLargeErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "PayloadTooLargeErrorType": { + "type": "string", + "description": "Problem type URI for payload-too-large errors.", + "enum": [ + "/errors/payload-too-large" + ] + }, + "PutNamespaceRoutingRequestContent": { + "type": "object", + "properties": { + "policy": { + "$ref": "#/components/schemas/NamespaceRoutingPolicy" + } + }, + "required": [ + "policy" + ] + }, + "PutNamespaceRoutingResponseContent": { + "type": "object", + "properties": { + "policy": { + "$ref": "#/components/schemas/NamespaceRoutingPolicy" + } + }, + "required": [ + "policy" + ] + }, + "RefreshSummaryViewRequestContent": { + "type": "object", + "properties": { + "target": { + "$ref": "#/components/schemas/SummaryViewRefreshTarget" + }, + "scope": { + "$ref": "#/components/schemas/SummaryViewScope" + } + }, + "required": [ + "target" + ] + }, + "RefreshSummaryViewResponseContent": { + "type": "object", + "properties": { + "viewId": { "type": "string", - "description": "Timestamp when the memory was created (UTC).", - "format": "date-time" + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." }, - "updatedAt": { + "target": { + "$ref": "#/components/schemas/SummaryViewRefreshTarget" + }, + "accepted": { + "type": "boolean" + } + }, + "required": [ + "accepted", + "target", + "viewId" + ] + }, + "RegexSpec": { + "type": "object", + "description": "A regular expression to match against.\n\nEvaluated by RE2, which runs in linear time \u2014 so an expensive pattern is not a\ndenial-of-service risk, but lookaround and backreferences are unavailable.\nRejected on write if it does not compile, or if it can match without consuming\ntext: such a pattern matches at every position, so it would exclude every memory\nthe store writes.", + "properties": { + "pattern": { "type": "string", - "description": "Timestamp when the memory was last updated (UTC).", - "format": "date-time" + "maxLength": 512, + "minLength": 1, + "description": "The pattern, in RE2 syntax." } }, "required": [ - "createdAt", - "id", - "text", - "updatedAt" + "pattern" ] }, - "MemoryTypeFilter": { + "ResourceSuspendedErrorResponseContent": { "type": "object", - "description": "Filter by memory type. Matching is case-insensitive.", + "description": "The requested resource exists but is suspended by an administrator.", "properties": { - "eq": { + "title": { "type": "string", - "description": "Equals the given value." + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." }, - "ne": { + "status": { + "type": "integer", + "default": 423, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { "type": "string", - "description": "Not equal to the given value." + "description": "A human-readable explanation specific to this occurrence of the problem." }, - "in": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Matches any of the given values." + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, - "all": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Matches all of the given values." + "type": { + "$ref": "#/components/schemas/ResourceSuspendedErrorType" } - } + }, + "required": [ + "status", + "title", + "type" + ] }, - "MessageRole": { + "ResourceSuspendedErrorType": { "type": "string", - "description": "Message role for session events.", + "description": "Problem type URI for suspended resources.", "enum": [ - "USER", - "ASSISTANT", - "SYSTEM" + "/errors/resource-suspended" ] }, - "MoveLongTermMemoriesRequestContent": { + "RouteCondition": { "type": "object", + "description": "One plug-in strategy invocation. Exactly the block naming the declared kind must be present. List order is evaluation order.", "properties": { - "memoryIds": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." - }, - "maxItems": 100, - "minItems": 1 + "kind": { + "$ref": "#/components/schemas/RouteStrategyKind" }, - "namespaceRef": { - "$ref": "#/components/schemas/NamespaceRefInput" + "llmPrompt": { + "$ref": "#/components/schemas/LlmPromptSpec" + }, + "matcher": { + "$ref": "#/components/schemas/MatcherSpec" } }, "required": [ - "memoryIds" + "kind" ] }, - "MoveLongTermMemoriesResponseContent": { + "RouteStrategyKind": { + "type": "string", + "description": "Routing strategy kinds. Unknown kinds are rejected. New kinds register on the evaluator without changing namespace records.", + "enum": [ + "llm_prompt", + "matcher_spec" + ] + }, + "RoutingRuleAction": { + "type": "string", + "description": "Whether the rule accepts into the destination or rejects that destination.", + "enum": [ + "accept", + "reject" + ] + }, + "SearchLongTermMemoryRequestContent": { "type": "object", + "description": "Semantic search request for long-term memory records.", "properties": { - "moved": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Memory record identifier (1-64 chars, alphanumeric and dashes). Client-provided for idempotent long-term memory creation." - } + "text": { + "type": "string", + "description": "Text to use for semantic search." }, - "errors": { + "similarityThreshold": { + "type": "number", + "maximum": 1, + "minimum": 0, + "description": "The minimum similarity threshold for vector search results (normalized cosine similarity). Allowed range: 0-1.", + "format": "float" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "excludeDocumentManaged": { + "type": "boolean", + "description": "When true, omit document-managed memories from results. Automatic session reconciliation uses this; public search omits it so document memories remain visible." + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "sortBy": { + "$ref": "#/components/schemas/LongTermMemorySearchSortBy" + }, + "sortOrder": { + "$ref": "#/components/schemas/LongTermMemorySearchSortOrder" + }, + "limit": { + "type": "integer", + "default": 10, + "maximum": 100, + "minimum": 1, + "description": "Maximum number of results to return. Defaults to 10. Allowed range: 1-100.", + "format": "int32" + }, + "pageToken": { + "type": "string", + "description": "Opaque token from a previous response for the next page." + } + } + }, + "SearchLongTermMemoryResponseContent": { + "type": "object", + "description": "Semantic search result for long-term memory records.", + "properties": { + "items": { "type": "array", "items": { - "$ref": "#/components/schemas/BulkOperationError" - } + "$ref": "#/components/schemas/MemoryRecord" + }, + "description": "List of matching memory records. Always returned; empty when no records match." + }, + "nextPageToken": { + "type": "string", + "description": "Opaque token for fetching the next page. Omitted when no more results." } }, "required": [ - "moved" + "items" ] }, - "Namespace": { + "SessionEvent": { "type": "object", - "description": "A namespace hierarchy record.", + "description": "A single event in the session history.", "properties": { - "namespaceId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." - }, - "name": { + "eventId": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9_-]+$", - "description": "One case-sensitive namespace path segment." + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Auto-generated event ID." }, - "parentId": { + "actorId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." + "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant." }, - "rootId": { + "sessionId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." - }, - "ownerId": { - "type": "string", - "minLength": 1, - "description": "An opaque application-user ID selected by the platform for a namespace root." - }, - "scope": { - "$ref": "#/components/schemas/NamespaceScope" + "description": "Session identifier (client-provided or server-generated)." }, - "state": { - "$ref": "#/components/schemas/NamespaceState" + "role": { + "$ref": "#/components/schemas/MessageRole" }, - "revision": { - "type": "integer", - "format": "int64" + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Content" + }, + "description": "The message content as an array of typed content parts." }, "createdAt": { "type": "string", + "description": "Client-supplied timestamp for when the event happened (UTC).", "format": "date-time" }, - "updatedAt": { + "systemTimestamp": { "type": "string", + "description": "Server-set timestamp for when the dataplane ingested the event (UTC).", "format": "date-time" }, - "path": { - "type": "string", - "maxLength": 1024, - "minLength": 1, - "description": "Current case-sensitive path, derived from the live hierarchy." + "metadata": { + "description": "Optional metadata as any valid JSON value." } }, - "required": [ - "createdAt", - "name", - "namespaceId", - "path", - "revision", - "rootId", - "scope", - "state", - "updatedAt" + "required": [ + "actorId", + "content", + "createdAt", + "eventId", + "role", + "sessionId", + "systemTimestamp" ] }, - "NamespaceFilter": { + "SessionIdFilter": { "type": "object", - "description": "Filter by namespace. Matching is case-insensitive.\n\nNamespace is optional, so records without one need care. The positive operators (`eq`, `in`, `all`) require the field to be present and therefore never match a record that has no namespace \u2014 including memories extracted from a session that has none. `ne` is a plain negation and *does* match those records, so use `eq` or `in` when you need strict namespace isolation rather than `ne` against the namespaces you want to exclude.\n\nOmit this filter to search across every namespace, and use it to keep recall inside one project, workspace, team, environment, or tenant.", + "description": "Filter by the long-term memory sessionId field. Matching is case-sensitive.", "properties": { "eq": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value.", - "deprecated": true + "description": "Equals the given value." }, "ne": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Not equal to the given value.", - "deprecated": true + "description": "Not equal to the given value." }, "in": { "type": "array", @@ -4527,8 +8568,7 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", - "deprecated": true + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." }, "description": "Matches any of the given values." }, @@ -4539,245 +8579,307 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Deprecated flat namespace label for logical grouping. The value is 1-64 alphanumeric characters or dashes and has no hierarchy or scope semantics. Matching through `filter.namespace` is case-insensitive.\n\nThe field remains optional for backward compatibility. New integrations should use `namespaceRef` to reference an existing namespace resource.\n\nRecords produced by extraction inherit the namespace label stored on the session when the session was created.\n\nThis shape is deprecated: Use namespaceRef to reference a namespace resource.", - "deprecated": true + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." }, "description": "Matches all of the given values." } } }, - "NamespaceRef": { + "SessionListItem": { "type": "object", - "description": "Reference to a namespace resource with its stable identifier and current display data.", + "description": "One session row without events or summary text. Owner ID and timestamps are absent when unknown on legacy sessions.", "properties": { - "namespaceId": { + "sessionId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Stable namespace resource identifier." + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." }, - "name": { + "ownerId": { "type": "string", "maxLength": 64, "minLength": 1, - "pattern": "^[a-zA-Z0-9_-]+$", - "description": "Current namespace name." + "pattern": "^[a-zA-Z0-9-]+$", + "description": "The owner of the session, set from the actorId of the first event. Absent for a legacy session that has no recorded owner." }, - "path": { + "createdAt": { "type": "string", - "maxLength": 1024, - "minLength": 1, - "description": "Current namespace path." + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "namespaceRef": { + "$ref": "#/components/schemas/NamespaceRef" } }, "required": [ - "name", - "namespaceId", - "path" + "sessionId" ] }, - "NamespaceRefFilter": { + "SessionListSortBy": { + "type": "string", + "enum": [ + "sessionId", + "createdAt", + "updatedAt" + ] + }, + "SessionListSortOrder": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "SessionSourceEvidence": { "type": "object", - "description": "Filter by stable namespace resource identifier. Supports exact or any-of matching and matches only resource-backed records, never deprecated namespace labels.", "properties": { - "eq": { + "evidenceId": { "type": "string", - "maxLength": 64, + "maxLength": 128, "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value." + "description": "Identity of a logical extraction occurrence. Delivery retries retain this ID; a new extraction receives a new ID. Absent on legacy evidence." }, - "in": { + "schemaVersion": { + "type": "integer", + "description": "Version of the evidence metadata contract, currently 1. Absent on legacy evidence.", + "format": "int32" + }, + "capturedAt": { + "type": "string", + "description": "UTC time when the producer prepared the input for extraction, before model invocation. Preserved through checkpoint replay. This is not the source event time.", + "format": "date-time" + }, + "producer": { + "$ref": "#/components/schemas/EvidenceProducer" + }, + "eventIds": { "type": "array", "items": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Server-generated namespace identifier." + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated event identifier (exactly 32 alphanumeric characters, UUID without dashes)." }, - "description": "Matches any of the given values." + "description": "Ordered IDs of events whose content was supplied to one model request. Input attribution, not fact citations." } - } + }, + "required": [ + "eventIds" + ] }, - "NamespaceRefInput": { + "SessionSourceIdentity": { "type": "object", - "description": "Reference to an existing namespace resource for a write or namespace-scoped operation.", "properties": { - "namespaceId": { + "sessionId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Stable namespace resource identifier." - } - }, - "required": [ - "namespaceId" - ] - }, - "NamespaceRoutingPolicy": { - "type": "object", - "description": "Store-level automatic namespace routing and admission policy. Global rules target shared namespaces only. Accept rules select candidates in list order. All enabled reject rules for a destination apply regardless of their position or how the destination was selected.", - "properties": { - "enabled": { - "type": "boolean" + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." }, - "rules": { - "type": "array", - "items": { - "$ref": "#/components/schemas/NamespaceRoutingRule" - }, - "maxItems": 32 + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." } }, "required": [ - "enabled" + "ownerId", + "sessionId" ] }, - "NamespaceRoutingRule": { + "SessionSummary": { "type": "object", - "description": "One routing or admission rule. action is accept or reject. Strategies run in list order and the first match fires the rule. An accept with no strategies always matches; a reject with no strategies never matches. A matching accept selects a candidate. Every enabled reject for that destination must pass before placement. A veto skips that candidate during automatic routing and fails explicit placement.", + "description": "Compacted summary of older session history for a session.\n\nThis is derived session state maintained by the background summarization workflow, not a conversation event. It is present only after at least one summarization cycle has completed.", "properties": { - "id": { + "text": { "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Routing rule identifier (1-64 chars). Published in logs, so it is an identifier rather than free text." + "description": "The summary text, a single coherent block." }, - "destination": { - "$ref": "#/components/schemas/NamespaceRefInput" + "summarizedUpToEventId": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Boundary event ID. This event and all events before it (by array position) are covered by the summary; `events` contains the retained turns after this boundary." }, - "enabled": { - "type": "boolean" + "createdAt": { + "type": "string", + "description": "Timestamp when the summary was first created (UTC). Preserved across re-summarization cycles.", + "format": "date-time" }, - "action": { - "$ref": "#/components/schemas/RoutingRuleAction" + "updatedAt": { + "type": "string", + "description": "Timestamp when the summary was last regenerated (UTC). Updated on every summarization cycle. Equal to createdAt on the first cycle.", + "format": "date-time" }, - "strategies": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RouteCondition" - }, - "maxItems": 8 + "metadata": { + "description": "Metadata as a JSON object (e.g., model name, token count). Always present; defaults to an empty object when no metadata was supplied." + }, + "summarizedEvents": { + "type": "integer", + "minimum": 1, + "description": "Cumulative number of session events folded into this summary across all summarization cycles. Always positive. A value of 1 may indicate a summary created before this field was tracked; it self-corrects to a real count on the next summarization cycle.", + "format": "int64" } }, "required": [ - "action", - "destination", - "enabled", - "id" - ] - }, - "NamespaceScope": { - "type": "string", - "description": "The root scope of a namespace hierarchy.", - "enum": [ - "PERSONAL", - "SHARED" + "createdAt", + "metadata", + "summarizedEvents", + "summarizedUpToEventId", + "text", + "updatedAt" ] }, - "NamespaceState": { - "type": "string", - "description": "Whether a namespace accepts new children and placements.", - "enum": [ - "ACTIVE", - "ARCHIVED" + "StoreHealthFeature": { + "type": "object", + "description": "Feature-level store health.", + "properties": { + "status": { + "$ref": "#/components/schemas/StoreHealthStatus" + } + }, + "required": [ + "status" ] }, - "NamespaceUpdateState": { - "type": "string", - "description": "The archive-only state transition accepted by UpdateNamespace.", - "enum": [ - "ARCHIVED" - ] + "StoreHealthFeatureMap": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/StoreHealthFeature" + } }, - "NotFoundErrorResponseContent": { + "StoreHealthResponseContent": { "type": "object", - "description": "The requested resource does not exist.", + "description": "Store-scoped RAM feature health.", "properties": { - "title": { - "type": "string", - "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." - }, "status": { - "type": "integer", - "default": 404, - "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", - "format": "int32" - }, - "detail": { - "type": "string", - "description": "A human-readable explanation specific to this occurrence of the problem." - }, - "instance": { - "type": "string", - "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + "$ref": "#/components/schemas/StoreHealthStatus" }, - "type": { - "$ref": "#/components/schemas/NotFoundErrorType" + "features": { + "$ref": "#/components/schemas/StoreHealthFeatureMap" } }, "required": [ - "status", - "title", - "type" + "features", + "status" ] }, - "NotFoundErrorType": { + "StoreHealthStatus": { "type": "string", - "description": "Problem type URI for not-found errors.", + "description": "Store health status.", "enum": [ - "/errors/resource-not-found" + "healthy", + "degraded", + "unavailable" ] }, - "OwnerIdFilter": { + "SummaryView": { "type": "object", - "description": "Filter by the long-term memory ownerId field. Matching is case-sensitive.", + "description": "An owned Summary View definition. Results are separate derived state and never enter long-term-memory search.", "properties": { - "eq": { + "id": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "ownerId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value." + "description": "Immutable canonical user ID assigned by the server. Clients cannot set ownership." }, - "ne": { + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { "type": "string", - "maxLength": 64, + "maxLength": 100, "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Not equal to the given value." + "description": "Customer-visible Summary View name (1-100 characters)." }, - "in": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." - }, - "description": "Matches any of the given values." + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." }, - "all": { + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { "type": "array", "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." + "$ref": "#/components/schemas/SummaryViewGroupField" }, - "description": "Matches all of the given values." + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" } - } + }, + "required": [ + "createdAt", + "definitionVersion", + "filterOp", + "groupBy", + "id", + "name", + "output", + "ownerId", + "refreshPolicy", + "source", + "state", + "updatedAt", + "visibility" + ] }, - "PayloadTooLargeErrorResponseContent": { + "SummaryViewConflictErrorResponseContent": { "type": "object", - "description": "The request payload exceeds the maximum supported size.", + "description": "A Summary View operation conflicts with the current definition or partition state.", "properties": { "title": { "type": "string", @@ -4785,7 +8887,7 @@ }, "status": { "type": "integer", - "default": 413, + "default": 409, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -4798,7 +8900,7 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/PayloadTooLargeErrorType" + "$ref": "#/components/schemas/SummaryViewConflictErrorType" } }, "required": [ @@ -4807,53 +8909,117 @@ "type" ] }, - "PayloadTooLargeErrorType": { + "SummaryViewConflictErrorType": { "type": "string", - "description": "Problem type URI for payload-too-large errors.", + "description": "Problem type URI for Summary View conflicts.", "enum": [ - "/errors/payload-too-large" + "/errors/summary-view-conflict" ] }, - "PutNamespaceRoutingRequestContent": { + "SummaryViewEvictionPolicy": { "type": "object", + "description": "Selects the bounded set of grouped results. Matching long-term-memory mutations make a scope recent.", "properties": { - "policy": { - "$ref": "#/components/schemas/NamespaceRoutingPolicy" + "maxPartitions": { + "type": "integer", + "minimum": 1, + "format": "int32" } }, "required": [ - "policy" + "maxPartitions" ] }, - "PutNamespaceRoutingResponseContent": { + "SummaryViewField": { "type": "object", + "description": "A field in a bounded structured Summary View result. All fields are optional in generated results.", "properties": { - "policy": { - "$ref": "#/components/schemas/NamespaceRoutingPolicy" + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$" + }, + "description": { + "type": "string", + "maxLength": 200, + "minLength": 1 + }, + "type": { + "$ref": "#/components/schemas/SummaryViewFieldType" } }, "required": [ - "policy" + "description", + "name", + "type" ] }, - "RegexSpec": { + "SummaryViewFieldType": { + "type": "string", + "description": "Supported structured-output field types. This matches the custom-memory field vocabulary.", + "enum": [ + "str", + "int", + "float", + "bool", + "list[str]", + "list[float]", + "object" + ] + }, + "SummaryViewGroupField": { + "type": "string", + "description": "A deterministic source field used to partition a Summary View.", + "enum": [ + "ownerId", + "sessionId", + "namespaceRef", + "memoryType" + ] + }, + "SummaryViewOutputConfig": { "type": "object", - "description": "A regular expression to match against.\n\nEvaluated by RE2, which runs in linear time \u2014 so an expensive pattern is not a\ndenial-of-service risk, but lookaround and backreferences are unavailable.\nRejected on write if it does not compile, or if it can match without consuming\ntext: such a pattern matches at every position, so it would exclude every memory\nthe store writes.", + "description": "Text or structured generation configuration. Schema is required exactly when format is structured.", "properties": { - "pattern": { + "format": { + "$ref": "#/components/schemas/SummaryViewOutputFormat" + }, + "instructions": { "type": "string", - "maxLength": 512, - "minLength": 1, - "description": "The pattern, in RE2 syntax." + "maxLength": 10000, + "description": "Customer-authored generation instructions. Prompt-security validation is enforced by the service." + }, + "schema": { + "$ref": "#/components/schemas/SummaryViewSchema" } }, "required": [ - "pattern" + "format" ] }, - "ResourceSuspendedErrorResponseContent": { + "SummaryViewOutputFormat": { + "type": "string", + "description": "Summary View output format.", + "enum": [ + "text", + "structured" + ] + }, + "SummaryViewQuotaCode": { + "type": "string", + "description": "Stable machine-readable Summary View quota code.", + "enum": [ + "DEFINITION_LIMIT_EXCEEDED", + "PARTITION_LIMIT_EXCEEDED", + "SOURCE_LIMIT_EXCEEDED", + "SCHEDULED_GENERATION_LIMIT_EXCEEDED", + "CONCURRENCY_LIMIT_EXCEEDED" + ] + }, + "SummaryViewQuotaErrorResponseContent": { "type": "object", - "description": "The requested resource exists but is suspended by an administrator.", + "description": "A Summary View operation exceeded an enabled store quota. Exact ceilings are deployment configuration established from benchmark evidence.", "properties": { "title": { "type": "string", @@ -4861,7 +9027,7 @@ }, "status": { "type": "integer", - "default": 423, + "default": 429, "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", "format": "int32" }, @@ -4874,297 +9040,187 @@ "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." }, "type": { - "$ref": "#/components/schemas/ResourceSuspendedErrorType" + "$ref": "#/components/schemas/SummaryViewQuotaErrorType" + }, + "code": { + "$ref": "#/components/schemas/SummaryViewQuotaCode" } }, "required": [ + "code", "status", "title", "type" ] }, - "ResourceSuspendedErrorType": { + "SummaryViewQuotaErrorType": { "type": "string", - "description": "Problem type URI for suspended resources.", + "description": "Problem type URI for Summary View quota errors.", "enum": [ - "/errors/resource-suspended" + "/errors/summary-view-quota-exceeded" ] }, - "RouteCondition": { + "SummaryViewRefreshMode": { + "type": "string", + "description": "Summary View refresh mode.", + "enum": [ + "manual", + "continuous" + ] + }, + "SummaryViewRefreshPolicy": { "type": "object", - "description": "One plug-in strategy invocation. Exactly the block naming the declared kind must be present. List order is evaluation order.", + "description": "Refresh policy. Continuous views require reconcileIntervalSeconds; manual views omit both interval fields.", "properties": { - "kind": { - "$ref": "#/components/schemas/RouteStrategyKind" + "mode": { + "$ref": "#/components/schemas/SummaryViewRefreshMode" }, - "llmPrompt": { - "$ref": "#/components/schemas/LlmPromptSpec" + "debounceSeconds": { + "type": "integer", + "minimum": 1, + "format": "int32" }, - "matcher": { - "$ref": "#/components/schemas/MatcherSpec" + "reconcileIntervalSeconds": { + "type": "integer", + "minimum": 1, + "format": "int32" } }, "required": [ - "kind" + "mode" ] }, - "RouteStrategyKind": { + "SummaryViewRefreshTarget": { "type": "string", - "description": "Routing strategy kinds. Unknown kinds are rejected. New kinds register on the evaluator without changing namespace records.", + "description": "Manual refresh target. Partition refreshes require an exact configured scope; full refreshes reconcile every current and prior partition.", "enum": [ - "llm_prompt", - "matcher_spec" + "partition", + "full" ] }, - "RoutingRuleAction": { + "SummaryViewResultErrorCode": { "type": "string", - "description": "Whether the rule accepts into the destination or rejects that destination.", + "description": "Customer-safe refresh failure code. Provider details and source content are never returned.", "enum": [ - "accept", - "reject" + "SOURCE_LIMIT_EXCEEDED", + "GENERATION_FAILED", + "OUTPUT_VALIDATION_FAILED", + "REFRESH_RETRIES_EXHAUSTED", + "INTERNAL_ERROR" ] }, - "SearchLongTermMemoryRequestContent": { - "type": "object", - "description": "Semantic search request for long-term memory records.", - "properties": { - "text": { - "type": "string", - "description": "Text to use for semantic search." - }, - "similarityThreshold": { - "type": "number", - "maximum": 1, - "minimum": 0, - "description": "The minimum similarity threshold for vector search results (normalized cosine similarity). Allowed range: 0-1.", - "format": "float" - }, - "filter": { - "$ref": "#/components/schemas/LongTermMemoryFilter" - }, - "filterOp": { - "$ref": "#/components/schemas/FilterConjunction" - }, - "limit": { - "type": "integer", - "default": 10, - "maximum": 100, - "minimum": 1, - "description": "Maximum number of results to return. Defaults to 10. Allowed range: 1-100.", - "format": "int32" - }, - "pageToken": { - "type": "string", - "description": "Opaque token from a previous response for the next page." - } - } - }, - "SearchLongTermMemoryResponseContent": { - "type": "object", - "description": "Semantic search result for long-term memory records.", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MemoryRecord" - }, - "description": "List of matching memory records. Always returned; empty when no records match." - }, - "nextPageToken": { - "type": "string", - "description": "Opaque token for fetching the next page. Omitted when no more results." - } - }, - "required": [ - "items" + "SummaryViewResultStatus": { + "type": "string", + "description": "Materialization refresh state.", + "enum": [ + "cold", + "pending", + "ready", + "empty", + "refreshing", + "failed" ] }, - "SessionEvent": { - "type": "object", - "description": "A single event in the session history.", - "properties": { - "eventId": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "Auto-generated event ID." - }, - "actorId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Unique actor identifier (1-255 chars). Can represent a user, agent, or any participant." - }, - "sessionId": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (client-provided or server-generated)." - }, - "role": { - "$ref": "#/components/schemas/MessageRole" + "SummaryViewResultValue": { + "description": "Exactly one generated result value. It is omitted for cold, pending, and empty results, and may be omitted for failed results with no last good value.", + "oneOf": [ + { + "type": "object", + "title": "text", + "properties": { + "text": { + "type": "string" + } + }, + "required": [ + "text" + ] }, - "content": { + { + "type": "object", + "title": "data", + "properties": { + "data": {} + }, + "required": [ + "data" + ] + } + ] + }, + "SummaryViewSchema": { + "type": "object", + "description": "Bounded structured-output schema. Nested object schemas, required fields, and arbitrary JSON Schema are not supported in the MVP.", + "properties": { + "fields": { "type": "array", "items": { - "$ref": "#/components/schemas/Content" + "$ref": "#/components/schemas/SummaryViewField" }, - "description": "The message content as an array of typed content parts." - }, - "createdAt": { - "type": "string", - "description": "Client-supplied timestamp for when the event happened (UTC).", - "format": "date-time" - }, - "systemTimestamp": { - "type": "string", - "description": "Server-set timestamp for when the dataplane ingested the event (UTC).", - "format": "date-time" - }, - "metadata": { - "description": "Optional metadata as any valid JSON value." + "maxItems": 50, + "minItems": 1 } }, "required": [ - "actorId", - "content", - "createdAt", - "eventId", - "role", - "sessionId", - "systemTimestamp" + "fields" ] }, - "SessionIdFilter": { + "SummaryViewScope": { "type": "object", - "description": "Filter by the long-term memory sessionId field. Matching is case-sensitive.", + "description": "An exact partition scope. The service requires exactly the fields configured by groupBy; singleton views accept none.", "properties": { - "eq": { + "ownerId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Equals the given value." + "description": "Actor/owner identifier (1-64 chars, alphanumeric and dashes). Used for users, agents, or other participants." }, - "ne": { + "sessionId": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z0-9-]+$", - "description": "Not equal to the given value." - }, - "in": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." - }, - "description": "Matches any of the given values." - }, - "all": { - "type": "array", - "items": { - "type": "string", - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-]+$", - "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." - }, - "description": "Matches all of the given values." - } - } - }, - "SessionSummary": { - "type": "object", - "description": "Compacted summary of older session history for a session.\n\nThis is derived session state maintained by the background summarization workflow, not a conversation event. It is present only after at least one summarization cycle has completed.", - "properties": { - "text": { - "type": "string", - "description": "The summary text, a single coherent block." + "description": "Session identifier (1-64 chars, alphanumeric and dashes). When the server generates one, it uses a 32-character UUID without dashes." }, - "summarizedUpToEventId": { - "type": "string", - "maxLength": 32, - "minLength": 32, - "pattern": "^[a-zA-Z0-9]+$", - "description": "Boundary event ID. This event and all events before it (by array position) are covered by the summary; `events` contains the retained turns after this boundary." - }, - "createdAt": { + "namespaceRef": { "type": "string", - "description": "Timestamp when the summary was first created (UTC). Preserved across re-summarization cycles.", - "format": "date-time" + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Server-generated namespace identifier." }, - "updatedAt": { + "memoryType": { "type": "string", - "description": "Timestamp when the summary was last regenerated (UTC). Updated on every summarization cycle. Equal to createdAt on the first cycle.", - "format": "date-time" - }, - "metadata": { - "description": "Metadata as a JSON object (e.g., model name, token count). Always present; defaults to an empty object when no metadata was supplied." - }, - "summarizedEvents": { - "type": "integer", - "minimum": 1, - "description": "Cumulative number of session events folded into this summary across all summarization cycles. Always positive. A value of 1 may indicate a summary created before this field was tracked; it self-corrects to a real count on the next summarization cycle.", - "format": "int64" + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, `message`, and `session_summary_view`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes." } - }, - "required": [ - "createdAt", - "metadata", - "summarizedEvents", - "summarizedUpToEventId", - "text", - "updatedAt" - ] + } }, - "StoreHealthFeature": { - "type": "object", - "description": "Feature-level store health.", - "properties": { - "status": { - "$ref": "#/components/schemas/StoreHealthStatus" - } - }, - "required": [ - "status" + "SummaryViewSource": { + "type": "string", + "description": "The source record family summarized by a Summary View. The MVP supports long-term memory only.", + "enum": [ + "long_term" ] }, - "StoreHealthFeatureMap": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/StoreHealthFeature" - } - }, - "StoreHealthResponseContent": { - "type": "object", - "description": "Store-scoped RAM feature health.", - "properties": { - "status": { - "$ref": "#/components/schemas/StoreHealthStatus" - }, - "features": { - "$ref": "#/components/schemas/StoreHealthFeatureMap" - } - }, - "required": [ - "features", - "status" + "SummaryViewState": { + "type": "string", + "description": "Summary View definition lifecycle state.", + "enum": [ + "active", + "disabled", + "deleting" ] }, - "StoreHealthStatus": { + "SummaryViewVisibility": { "type": "string", - "description": "Store health status.", + "description": "The immutable audience of a Summary View.", "enum": [ - "healthy", - "degraded", - "unavailable" + "PRIVATE", + "SHARED" ] }, "TimeoutErrorResponseContent": { @@ -5464,6 +9520,21 @@ "type": "string", "description": "Timestamp when the memory was last updated (UTC).", "format": "date-time" + }, + "sources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MemorySourceLink" + }, + "description": "Generic source links for this memory. Document sources include the customer document ID and revision that produced the memory." + }, + "sourcesNextPageToken": { + "type": "string", + "description": "Opaque token for additional source links when this response truncated attribution. Absent when every link is included." + }, + "documentManaged": { + "type": "boolean", + "description": "True when this memory was created by document ingestion and is protected from generic overwrite." } }, "required": [ @@ -5498,6 +9569,155 @@ "required": [ "namespace" ] + }, + "UpdateSummaryViewRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "New description. Send an empty string to clear it." + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "clearFilter": { + "type": "boolean", + "description": "Clear the saved filter. Mutually exclusive with filter." + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "clearTimeWindow": { + "type": "boolean", + "description": "Clear the rolling time window. Mutually exclusive with timeWindowSeconds." + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "state": { + "$ref": "#/components/schemas/MutableSummaryViewState" + } + } + }, + "UpdateSummaryViewResponseContent": { + "type": "object", + "description": "An owned Summary View definition. Results are separate derived state and never enter long-term-memory search.", + "properties": { + "id": { + "type": "string", + "maxLength": 32, + "minLength": 32, + "pattern": "^[a-zA-Z0-9]+$", + "description": "Server-generated Summary View identifier." + }, + "ownerId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Immutable canonical user ID assigned by the server. Clients cannot set ownership." + }, + "visibility": { + "$ref": "#/components/schemas/SummaryViewVisibility" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "Customer-visible Summary View name (1-100 characters)." + }, + "description": { + "type": "string", + "maxLength": 200, + "description": "Optional Summary View description (up to 200 characters)." + }, + "source": { + "$ref": "#/components/schemas/SummaryViewSource" + }, + "filter": { + "$ref": "#/components/schemas/LongTermMemoryFilter" + }, + "filterOp": { + "$ref": "#/components/schemas/FilterConjunction" + }, + "groupBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummaryViewGroupField" + }, + "maxItems": 4 + }, + "evictionPolicy": { + "$ref": "#/components/schemas/SummaryViewEvictionPolicy" + }, + "timeWindowSeconds": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "output": { + "$ref": "#/components/schemas/SummaryViewOutputConfig" + }, + "refreshPolicy": { + "$ref": "#/components/schemas/SummaryViewRefreshPolicy" + }, + "definitionVersion": { + "type": "integer", + "minimum": 1, + "format": "int64" + }, + "state": { + "$ref": "#/components/schemas/SummaryViewState" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "createdAt", + "definitionVersion", + "filterOp", + "groupBy", + "id", + "name", + "output", + "ownerId", + "refreshPolicy", + "source", + "state", + "updatedAt", + "visibility" + ] } } } diff --git a/content/develop/ai/context-engine/agent-memory/namespace-routing.md b/content/develop/ai/context-engine/agent-memory/namespace-routing.md new file mode 100644 index 0000000000..5f64f14fa3 --- /dev/null +++ b/content/develop/ai/context-engine/agent-memory/namespace-routing.md @@ -0,0 +1,167 @@ +--- +alwaysopen: false +categories: +- docs +- develop +- ai +description: Configure shared namespace routing and admission rules for long-term memories. +hideListLinks: true +linktitle: Namespace routing +title: Namespace routing +weight: 14 +--- + +Namespace routing selects a shared namespace for an eligible memory. Admission rules check whether a memory can enter a destination. Use routing to organize shared memories by content, and admission rules to reject content that does not belong in a namespace. + +The policy applies to one store. It targets existing shared namespace resources, using their stable `namespaceId` values. It does not create namespaces or change access permissions. Your service must support namespace routing and have it enabled. The beta software development kits (SDKs) expose these operations in Python `0.5.0b1` and TypeScript `0.4.0-beta.1`; installing an SDK does not enable routing on the service. + +## Understand placement and admission + +| Request | Behavior | +|:--------|:---------| +| Create a memory with `namespaceRef` | Use that destination after admission checks. The service never chooses another destination. | +| Create a memory without `namespace` or `namespaceRef` | Create it without a namespace. Automatic routing does not place it in a shared namespace. | +| Move a memory with `namespaceRef` | Use that destination after admission checks. | +| Move a memory without `namespaceRef` | Evaluate automatic routing only if the memory is already in a shared namespace. | +| Update a memory | Check the merged record against admission at its current destination. An update never chooses another destination. | + +An automatic move keeps the current placement and returns a per-item error if routing is disabled, no destination qualifies, or the memory is personal or has no namespace. Inspect both `moved` and `errors` in a move response. Bulk creation also reports failures per record in `errors`. An explicit move to an archived namespace returns `409 Conflict`. + +An enabled `accept` rule selects a candidate destination. Accept rules run in list order. Every enabled `reject` rule for that destination applies, including rules later in the list. A matching reject vetoes the candidate. Automatic routing then tries another candidate; explicit placement fails rather than choosing another namespace. + +Strategies within a rule run in list order; the first match fires the rule. An accept rule with no strategies always matches. A reject rule with no strategies never matches. Use `matcher_spec` with a `regex` matcher for text patterns, or `llm_prompt` with `llmPrompt` for a model-evaluated instruction. Each strategy must contain only the block for its declared `kind`. + +## Configure a policy + +Create a shared namespace with `CreateNamespace`, and save its returned ID as `SHARED_NAMESPACE_ID`. Use `scope: "SHARED"` and omit `ownerId`. Install the beta SDK for your language, then set `AGENT_MEMORY_URL`, `STORE_ID`, and `API_KEY` as described in the [developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md#connect-to-a-redis-agent-memory-service). + +This example accepts text containing `travel` into that namespace. The regular expression is case-insensitive. **PUT replaces the entire policy**, so read the current policy first and retain any rules you still need. + +{{< multitabs id="namespace-routing-policy" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python +import asyncio +import os + +from redis_agent_memory import AgentMemory + + +async def main(): + async with AgentMemory( + os.environ["AGENT_MEMORY_URL"], + store_id=os.environ["STORE_ID"], + api_key=os.environ["API_KEY"], + ) as agent_memory: + current = await agent_memory.get_namespace_routing_async() + print(current.model_dump_json(by_alias=True, indent=2)) + result = await agent_memory.put_namespace_routing_async(policy={ + "enabled": True, + "rules": [{ + "id": "travel", + "enabled": True, + "action": "accept", + "destination": {"namespace_id": os.environ["SHARED_NAMESPACE_ID"]}, + "strategies": [{ + "kind": "matcher_spec", + "matcher": {"kind": "regex", "regex": {"pattern": "(?i)travel"}}, + }], + }], + }) + print(result.model_dump_json(by_alias=True, indent=2)) + + +asyncio.run(main()) +``` + +-tab-sep- + +```typescript +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function main() { + const { AGENT_MEMORY_URL, STORE_ID, API_KEY, SHARED_NAMESPACE_ID } = process.env; + if (!AGENT_MEMORY_URL || !STORE_ID || !API_KEY || !SHARED_NAMESPACE_ID) { + throw new Error("Set AGENT_MEMORY_URL, STORE_ID, API_KEY, and SHARED_NAMESPACE_ID."); + } + const agentMemory = new AgentMemory({ + serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY, + }); + console.dir(await agentMemory.getNamespaceRouting(), { depth: null }); + const result = await agentMemory.putNamespaceRouting({ + policy: { + enabled: true, + rules: [{ + id: "travel", enabled: true, action: "accept", + destination: { namespaceId: SHARED_NAMESPACE_ID }, + strategies: [{ + kind: "matcher_spec", + matcher: { kind: "regex", regex: { pattern: "(?i)travel" } }, + }], + }], + }, + }); + console.dir(result, { depth: null }); +} + +main().catch((error) => { console.error(error); process.exitCode = 1; }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $API_KEY" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespace-routing" | jq + +curl --fail-with-body --silent --show-error \ + --request PUT \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data "$(jq -n --arg ns "$SHARED_NAMESPACE_ID" \ + '{policy: {enabled: true, rules: [{id: "travel", enabled: true, + action: "accept", destination: {namespaceId: $ns}, strategies: [{ + kind: "matcher_spec", matcher: {kind: "regex", regex: {pattern: "(?i)travel"}} + }]}]}}')" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespace-routing" | jq +``` + +{{< /multitabs >}} + +A store without a saved policy returns an empty, disabled policy. Regular expressions use RE2 syntax. Lookaround, backreferences, invalid expressions, and patterns that can match empty text are not supported. A policy supports up to 32 rules and 8 strategies per rule. See [`PutNamespaceRouting`](/content/develop/ai/context-engine/agent-memory/api-reference.md#operation/PutNamespaceRouting) for validation and error responses. + +## Move memories with the policy + +To evaluate the policy for an existing shared memory, omit the destination from `MoveLongTermMemories`. Use IDs returned by a previous create or search request: + +{{< multitabs id="namespace-routing-move" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + moved = await agent_memory.move_long_term_memories_async( + memory_ids=[os.environ["MEMORY_ID"]], + ) + print(moved.model_dump_json(by_alias=True, indent=2)) +``` + +-tab-sep- + +```typescript + const memoryId = process.env.MEMORY_ID; + if (!memoryId) throw new Error("Set MEMORY_ID to an existing shared memory ID."); + const moved = await agentMemory.moveLongTermMemories({ memoryIds: [memoryId] }); + console.dir(moved, { depth: null }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data "$(jq -n --arg id "$MEMORY_ID" '{memoryIds: [$id]}')" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/move" | jq +``` + +{{< /multitabs >}} + +Add the SDK snippet inside the client block or `main` function from [Configure a policy](/content/develop/ai/context-engine/agent-memory/namespace-routing.md#configure-a-policy). Set `MEMORY_ID` before running it. The example memory must already belong to a shared namespace and contain matching text. The request accepts 1–100 IDs. To choose a destination explicitly, also pass `namespace_ref={"namespace_id": ""}` in Python or `namespaceRef: { namespaceId: "" }` in TypeScript or the request body. Explicit placement still runs admission checks. From b479234e1f0faacfdb21a6bbdd9ba41ca176ca4d Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Wed, 21 Oct 2026 12:20:39 -0700 Subject: [PATCH 13/26] Honor hidden footers in SDK examples --- layouts/partials/tabbed-clients-example.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layouts/partials/tabbed-clients-example.html b/layouts/partials/tabbed-clients-example.html index 27600358fe..a5bda26b0c 100644 --- a/layouts/partials/tabbed-clients-example.html +++ b/layouts/partials/tabbed-clients-example.html @@ -8,7 +8,7 @@ {{ $cliTabName := (or (.Scratch.Get "cli_tab_name") ">_ Redis CLI") }} {{ $cliFooterLinkText := .Scratch.Get "cli_footer_link_text" }} {{ $cliFooterLinkUrl := .Scratch.Get "cli_footer_link_url" }} -{{ $showFooter := .Scratch.Get "show_footer" | default true }} +{{ $showFooter := .Scratch.Get "show_footer" }} {{/* runnable defaults to true; "false" renders a static, non-executable cli tab */}} {{ $runnable := ne (.Scratch.Get "runnable") "false" }} {{/* try_it defaults to true; "false" hides the external redis.io/cli button but keeps the on-page tab interactive */}} From abbdbbeb546faf45c9bb6a32e271b70a2455cddc Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Wed, 21 Oct 2026 12:22:19 -0700 Subject: [PATCH 14/26] Keep quickstart links valid after consolidation --- content/develop/ai/context-engine/agent-memory/_index.md | 4 ++-- .../ai/context-engine/agent-memory/interactive-demo.md | 2 +- content/develop/ai/context-engine/agent-memory/overview.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/content/develop/ai/context-engine/agent-memory/_index.md b/content/develop/ai/context-engine/agent-memory/_index.md index 878a5d552e..336e3b319f 100644 --- a/content/develop/ai/context-engine/agent-memory/_index.md +++ b/content/develop/ai/context-engine/agent-memory/_index.md @@ -122,12 +122,12 @@ After your Redis Agent Memory service is ready, choose a client. Each quickstart

Python SDK

Explore the Redis Agent Memory workflow with the Python SDK.

-

}}">Open the Python quickstart

+

}}">Open the Python quickstart

TypeScript SDK

Explore the Redis Agent Memory workflow with the TypeScript SDK.

-

}}">Open the TypeScript quickstart

+

}}">Open the TypeScript quickstart

REST API

diff --git a/content/develop/ai/context-engine/agent-memory/interactive-demo.md b/content/develop/ai/context-engine/agent-memory/interactive-demo.md index e24a472693..b759d72791 100644 --- a/content/develop/ai/context-engine/agent-memory/interactive-demo.md +++ b/content/develop/ai/context-engine/agent-memory/interactive-demo.md @@ -33,4 +33,4 @@ Try different messages to see how they change what the service remembers. Open * ## Next steps - [Create an Agent Memory service](/content/operate/iris/agent-memory/create-service.md) on Redis Cloud. -- Follow the [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md) or the [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/python-sdk-quickstart.md) to try the same flow with a real service. +- Follow the [REST API quickstart](/content/develop/ai/context-engine/agent-memory/rest-api-quickstart.md) or the [Python SDK quickstart](/content/develop/ai/context-engine/agent-memory/sdk-quickstart.md) to try the same flow with a real service. diff --git a/content/develop/ai/context-engine/agent-memory/overview.md b/content/develop/ai/context-engine/agent-memory/overview.md index 442f14cf4f..3698f2fce2 100644 --- a/content/develop/ai/context-engine/agent-memory/overview.md +++ b/content/develop/ai/context-engine/agent-memory/overview.md @@ -68,6 +68,6 @@ Both paths are supported. Use direct writes for bulk imports or external knowled ## Next steps -- Try the [Agent Memory interactive demo](content/develop/ai/context-engine/agent-memory/interactive-demo.md) to watch session memory and long-term memory change during a conversation. +- Try the [Agent Memory interactive demo](/content/develop/ai/context-engine/agent-memory/interactive-demo.md) to watch session memory and long-term memory change during a conversation. - [Developer guide]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide" >}}) to connect an application and start writing session events. -- [Python SDK quickstart]({{< relref "/develop/ai/context-engine/agent-memory/python-sdk-quickstart" >}}), [TypeScript SDK quickstart]({{< relref "/develop/ai/context-engine/agent-memory/typescript-sdk-quickstart" >}}), or [REST API quickstart]({{< relref "/develop/ai/context-engine/agent-memory/rest-api-quickstart" >}}) to see session memory, extraction, and summarization in action. +- [SDK quickstart]({{< relref "/develop/ai/context-engine/agent-memory/sdk-quickstart" >}}) or [REST API quickstart]({{< relref "/develop/ai/context-engine/agent-memory/rest-api-quickstart" >}}) to see session memory, extraction, and summarization in action. From ecd52ad1720c338c6ed8f5b6b3245c47395c201f Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Wed, 21 Oct 2026 12:23:04 -0700 Subject: [PATCH 15/26] Show the beta namespace sorting call shapes --- .../context-engine/agent-memory/namespaces.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/content/develop/ai/context-engine/agent-memory/namespaces.md b/content/develop/ai/context-engine/agent-memory/namespaces.md index 36e584639c..c03f373c54 100644 --- a/content/develop/ai/context-engine/agent-memory/namespaces.md +++ b/content/develop/ai/context-engine/agent-memory/namespaces.md @@ -52,6 +52,42 @@ Without server-side ownership enforcement, your application must check that the Creating a namespace at an occupied location returns `409 Conflict`, including when the existing namespace is archived. Save and reuse the returned ID instead of creating the same namespace on each agent turn. For request fields and error responses, see the [namespace API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}#operation/CreateNamespace). +## Sort namespaces + +Use the client from the [quickstart](/content/develop/ai/context-engine/agent-memory/quickstart.md). This example lists shared roots by name: + +{{< multitabs id="namespace-sort" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + namespaces = agent_memory.list_namespaces( + scope="SHARED", limit=20, sort_by="name", sort_order="asc", + ) + show("Shared namespaces", namespaces) +``` + +-tab-sep- + +```typescript + const namespaces = await agentMemory.listNamespaces( + undefined, "SHARED", undefined, 20, undefined, undefined, "name", "asc", + ); + console.dir(namespaces, { depth: null }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error --get \ + --header "Authorization: Bearer $API_KEY" \ + --data-urlencode 'scope=SHARED' --data-urlencode 'limit=20' \ + --data-urlencode 'sortBy=name' --data-urlencode 'sortOrder=asc' \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespaces" | jq +``` + +{{< /multitabs >}} + +Add the Python snippet inside the `with` block and the TypeScript snippet inside `run`. In TypeScript, request options remain the sixth argument; `sortBy` and `sortOrder` are seventh and eighth. Name sorting uses the display name with Redis text collation, rather than the hierarchy path. Equal sort values use namespace ID ascending as a tie-breaker. + ## Create a personal namespace Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first. Reuse its client, connection values, and user and session identifiers. Add Python snippets inside the `with` block and TypeScript snippets inside `run`. Run curl commands in the same shell. Run each write once; comment out completed writes and their output statements before rerunning an SDK file. From 0d4468dc75ea09a0485885798d9ce788a7440433 Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Wed, 21 Oct 2026 12:27:22 -0700 Subject: [PATCH 16/26] Reuse SDK examples for the unified quickstart --- .../context-engine/agent-memory/quickstart.md | 6 +- .../agent-memory/agent_memory_quickstart.py | 129 --------- .../agent-memory/agent_memory_quickstart.ts | 122 -------- .../agent-memory/agent_memory_sdk.py | 226 +++------------ .../agent-memory/agent_memory_sdk.ts | 268 ++++-------------- 5 files changed, 96 insertions(+), 655 deletions(-) delete mode 100644 local_examples/agent-memory/agent_memory_quickstart.py delete mode 100644 local_examples/agent-memory/agent_memory_quickstart.ts diff --git a/content/develop/ai/context-engine/agent-memory/quickstart.md b/content/develop/ai/context-engine/agent-memory/quickstart.md index 850f721443..c94df03f63 100644 --- a/content/develop/ai/context-engine/agent-memory/quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/quickstart.md @@ -114,7 +114,7 @@ Use `curl` and `jq` directly. No SDK installation is required. Create `quickstart.py` or `quickstart.ts` with the complete example for your language. Replace `` and `` with your service values. The endpoint must include `https://`. -{{< clients-example set="agent_memory_quickstart" step="connect_health_check" footer="hide" dft_tab_name="curl" runnable="false" try_it="false" >}} +{{< clients-example set="agent_memory_sdk" step="connect_health_check" footer="hide" dft_tab_name="curl" runnable="false" try_it="false" >}} curl --fail-with-body --silent --show-error \ --header "Authorization: Bearer $API_KEY" \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/health" | jq @@ -130,7 +130,7 @@ Session memory is the conversation transcript. Each message is an event, stored 1. Replace your SDK quickstart file with the complete example for your language. For curl, run the commands in order. -{{< clients-example set="agent_memory_quickstart" step="add_session_event" footer="hide" dft_tab_name="curl" runnable="false" try_it="false" >}} +{{< clients-example set="agent_memory_sdk" step="add_session_event" footer="hide" dft_tab_name="curl" runnable="false" try_it="false" >}} export EVENT_CREATED_AT="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" curl --fail-with-body --silent --show-error \ @@ -172,7 +172,7 @@ Redis Agent Memory processes session events in the background and creates long-t Wait at least one minute. Replace your SDK file with the complete search example, or run the curl command: -{{< clients-example set="agent_memory_quickstart" step="search_long_term_memory_basic" footer="hide" dft_tab_name="curl" runnable="false" try_it="false" >}} +{{< clients-example set="agent_memory_sdk" step="search_long_term_memory_basic" footer="hide" dft_tab_name="curl" runnable="false" try_it="false" >}} curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ diff --git a/local_examples/agent-memory/agent_memory_quickstart.py b/local_examples/agent-memory/agent_memory_quickstart.py deleted file mode 100644 index f39219253c..0000000000 --- a/local_examples/agent-memory/agent_memory_quickstart.py +++ /dev/null @@ -1,129 +0,0 @@ -# EXAMPLE: agent_memory_quickstart - -# STEP_START connect_health_check -import os -from datetime import datetime, timezone - -from redis_agent_memory import AgentMemory, models - - -ENDPOINT = "" -STORE_ID = "" -SESSION_ID = "travel-planning-session" -USER_ID = "quickstart-user" - - -def show(label, response): - print(f"{label}:") - print(response.model_dump_json(by_alias=True, indent=2)) - - -def main(): - with AgentMemory( - ENDPOINT, - store_id=STORE_ID, - api_key=os.environ["API_KEY"], - ) as agent_memory: - health = agent_memory.store_health() - show("Store health", health) - - -if __name__ == "__main__": - main() -# STEP_END - -# STEP_START add_session_event -import os -from datetime import datetime, timezone - -from redis_agent_memory import AgentMemory, models - - -ENDPOINT = "" -STORE_ID = "" -SESSION_ID = "travel-planning-session" -USER_ID = "quickstart-user" - - -def show(label, response): - print(f"{label}:") - print(response.model_dump_json(by_alias=True, indent=2)) - - -def main(): - with AgentMemory( - ENDPOINT, - store_id=STORE_ID, - api_key=os.environ["API_KEY"], - ) as agent_memory: - health = agent_memory.store_health() - show("Store health", health) - - event = agent_memory.add_session_event( - session_id=SESSION_ID, - actor_id=USER_ID, - role=models.MessageRole.USER, - content=[models.Text( - text=( - "I am visiting Tokyo and Kyoto next month. " - "I am vegetarian and prefer spicy food." - ), - )], - created_at=datetime.now(timezone.utc), - ) - show("Created event", event) - - session = agent_memory.get_session_memory( - session_id=SESSION_ID, - ) - show("Session memory", session) - - -if __name__ == "__main__": - main() -# STEP_END - -# STEP_START search_long_term_memory_basic -import os -from datetime import datetime, timezone - -from redis_agent_memory import AgentMemory, models - - -ENDPOINT = "" -STORE_ID = "" -SESSION_ID = "travel-planning-session" -USER_ID = "quickstart-user" - - -def show(label, response): - print(f"{label}:") - print(response.model_dump_json(by_alias=True, indent=2)) - - -def main(): - with AgentMemory( - ENDPOINT, - store_id=STORE_ID, - api_key=os.environ["API_KEY"], - ) as agent_memory: - health = agent_memory.store_health() - show("Store health", health) - - results = agent_memory.search_long_term_memory( - request={ - "text": "What dietary requirements and food preferences does the user have?", - "filter_": { - "owner_id": { - "eq": USER_ID, - } - }, - "limit": 5, - }, - ) - show("Automatically extracted memories", results) - - -if __name__ == "__main__": - main() -# STEP_END diff --git a/local_examples/agent-memory/agent_memory_quickstart.ts b/local_examples/agent-memory/agent_memory_quickstart.ts deleted file mode 100644 index e31bb7699e..0000000000 --- a/local_examples/agent-memory/agent_memory_quickstart.ts +++ /dev/null @@ -1,122 +0,0 @@ -// EXAMPLE: agent_memory_quickstart - -// STEP_START connect_health_check -import { AgentMemory } from "@redis-iris/agent-memory"; - -const apiKey = process.env.API_KEY; -if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); -} - -const serverURL = ""; -const storeId = ""; -const sessionId = "travel-planning-session"; -const userId = "quickstart-user"; - -const agentMemory = new AgentMemory({ - serverURL, - storeId, - apiKey, -}); - -async function run() { - const health = await agentMemory.storeHealth(); - console.log("Store health:"); - console.dir(health, { depth: null }); -} - -run().catch((error) => { - console.error(error); - process.exitCode = 1; -}); -// STEP_END - -// STEP_START add_session_event -import { AgentMemory } from "@redis-iris/agent-memory"; - -const apiKey = process.env.API_KEY; -if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); -} - -const serverURL = ""; -const storeId = ""; -const sessionId = "travel-planning-session"; -const userId = "quickstart-user"; - -const agentMemory = new AgentMemory({ - serverURL, - storeId, - apiKey, -}); - -async function run() { - const health = await agentMemory.storeHealth(); - console.log("Store health:"); - console.dir(health, { depth: null }); - - const event = await agentMemory.addSessionEvent({ - sessionId, - actorId: userId, - role: "USER", - content: [{ - text: "I am visiting Tokyo and Kyoto next month. I am vegetarian and prefer spicy food.", - }], - createdAt: new Date(), - }); - console.log("Created event:"); - console.dir(event, { depth: null }); - - const session = await agentMemory.getSessionMemory(sessionId); - console.log("Session memory:"); - console.dir(session, { depth: null }); -} - -run().catch((error) => { - console.error(error); - process.exitCode = 1; -}); -// STEP_END - -// STEP_START search_long_term_memory_basic -import { AgentMemory } from "@redis-iris/agent-memory"; - -const apiKey = process.env.API_KEY; -if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); -} - -const serverURL = ""; -const storeId = ""; -const sessionId = "travel-planning-session"; -const userId = "quickstart-user"; - -const agentMemory = new AgentMemory({ - serverURL, - storeId, - apiKey, -}); - -async function run() { - const health = await agentMemory.storeHealth(); - console.log("Store health:"); - console.dir(health, { depth: null }); - - const results = await agentMemory.searchLongTermMemory({ - text: "What dietary requirements and food preferences does the user have?", - filter: { - ownerId: { - eq: userId, - }, - }, - limit: 5, - }); - console.log("Automatically extracted memories:"); - console.dir(results, { depth: null }); -} - -run().catch((error) => { - console.error(error); - process.exitCode = 1; -}); -// STEP_END diff --git a/local_examples/agent-memory/agent_memory_sdk.py b/local_examples/agent-memory/agent_memory_sdk.py index ab0bd72f83..8242857900 100644 --- a/local_examples/agent-memory/agent_memory_sdk.py +++ b/local_examples/agent-memory/agent_memory_sdk.py @@ -2,26 +2,34 @@ # STEP_START connect_health_check import os +from datetime import datetime, timezone + +from redis_agent_memory import AgentMemory, models -from redis_agent_memory import AgentMemory ENDPOINT = "" STORE_ID = "" +SESSION_ID = "travel-planning-session" +USER_ID = "quickstart-user" -def check_health(): +def show(label, response): + print(f"{label}:") + print(response.model_dump_json(by_alias=True, indent=2)) + + +def main(): with AgentMemory( ENDPOINT, store_id=STORE_ID, api_key=os.environ["API_KEY"], ) as agent_memory: health = agent_memory.store_health() - print("Store health:") - print(health.model_dump_json(by_alias=True, indent=2)) + show("Store health", health) if __name__ == "__main__": - check_health() + main() # STEP_END # STEP_START add_session_event @@ -30,18 +38,27 @@ def check_health(): from redis_agent_memory import AgentMemory, models + ENDPOINT = "" STORE_ID = "" SESSION_ID = "travel-planning-session" USER_ID = "quickstart-user" -def build_conversation_context(): +def show(label, response): + print(f"{label}:") + print(response.model_dump_json(by_alias=True, indent=2)) + + +def main(): with AgentMemory( ENDPOINT, store_id=STORE_ID, api_key=os.environ["API_KEY"], ) as agent_memory: + health = agent_memory.store_health() + show("Store health", health) + event = agent_memory.add_session_event( session_id=SESSION_ID, actor_id=USER_ID, @@ -54,224 +71,59 @@ def build_conversation_context(): )], created_at=datetime.now(timezone.utc), ) - print("Created event:") - print(event.model_dump_json(by_alias=True, indent=2)) + show("Created event", event) session = agent_memory.get_session_memory( session_id=SESSION_ID, ) - print("Session memory:") - print(session.model_dump_json(by_alias=True, indent=2)) + show("Session memory", session) if __name__ == "__main__": - build_conversation_context() + main() # STEP_END # STEP_START search_long_term_memory_basic import os - -from redis_agent_memory import AgentMemory - -ENDPOINT = "" -STORE_ID = "" -USER_ID = "quickstart-user" - - -def recall_extracted_memory(): - with AgentMemory( - ENDPOINT, - store_id=STORE_ID, - api_key=os.environ["API_KEY"], - ) as agent_memory: - results = agent_memory.search_long_term_memory( - request={ - "text": "What dietary requirements and food preferences does the user have?", - "filter_": { - "owner_id": { - "eq": USER_ID, - } - }, - "limit": 5, - }, - ) - print("Automatically extracted memories:") - print(results.model_dump_json(by_alias=True, indent=2)) - - -if __name__ == "__main__": - recall_extracted_memory() -# STEP_END - -# STEP_START add_conversation_turns -import os from datetime import datetime, timezone from redis_agent_memory import AgentMemory, models -ENDPOINT = "" -STORE_ID = "" -SESSION_ID = "travel-planning-session" -USER_ID = "quickstart-user" - - -def add_conversation_turns(): - turns = [ - (models.MessageRole.ASSISTANT, "What dates are you traveling?"), - (models.MessageRole.USER, "I arrive on October 10 and leave on October 18."), - (models.MessageRole.ASSISTANT, "Would you like formal or casual restaurants?"), - (models.MessageRole.USER, "Mostly casual places near public transit."), - (models.MessageRole.ASSISTANT, "Do you have a preferred budget?"), - (models.MessageRole.USER, "About 40 euros per person."), - ] - - with AgentMemory( - ENDPOINT, - store_id=STORE_ID, - api_key=os.environ["API_KEY"], - ) as agent_memory: - for role, text in turns: - agent_memory.add_session_event( - session_id=SESSION_ID, - actor_id=USER_ID if role == models.MessageRole.USER else "travel-agent", - role=role, - content=[models.Text(text=text)], - created_at=datetime.now(timezone.utc), - ) - - -if __name__ == "__main__": - add_conversation_turns() -# STEP_END - -# STEP_START retrieve_session_summary -import os - -from redis_agent_memory import AgentMemory ENDPOINT = "" STORE_ID = "" SESSION_ID = "travel-planning-session" - - -def retrieve_session_summary(): - with AgentMemory( - ENDPOINT, - store_id=STORE_ID, - api_key=os.environ["API_KEY"], - ) as agent_memory: - compacted_session = agent_memory.get_session_memory( - session_id=SESSION_ID, - ) - print("Compacted session memory:") - print(compacted_session.model_dump_json(by_alias=True, indent=2)) - - -if __name__ == "__main__": - retrieve_session_summary() -# STEP_END - -# STEP_START search_long_term_memory_custom_type -import os - -from redis_agent_memory import AgentMemory - -ENDPOINT = "" -STORE_ID = "" USER_ID = "quickstart-user" -def search_custom_memory_type(): - with AgentMemory( - ENDPOINT, - store_id=STORE_ID, - api_key=os.environ["API_KEY"], - ) as agent_memory: - custom_results = agent_memory.search_long_term_memory( - request={ - "text": "What are the requirements for the user's trip?", - "filter_": { - "owner_id": {"eq": USER_ID}, - "memory_type": {"eq": "trip_preference"}, - }, - "limit": 5, - }, - ) - print("Trip preference memories:") - print(custom_results.model_dump_json(by_alias=True, indent=2)) - - -if __name__ == "__main__": - search_custom_memory_type() -# STEP_END - -# STEP_START add_session_event_sensitive -import os -from datetime import datetime, timezone - -from redis_agent_memory import AgentMemory, models - -ENDPOINT = "" -STORE_ID = "" -SESSION_ID = "travel-planning-session" -USER_ID = "quickstart-user" +def show(label, response): + print(f"{label}:") + print(response.model_dump_json(by_alias=True, indent=2)) -def add_sensitive_event(): +def main(): with AgentMemory( ENDPOINT, store_id=STORE_ID, api_key=os.environ["API_KEY"], ) as agent_memory: - sensitive_event = agent_memory.add_session_event( - session_id=SESSION_ID, - actor_id=USER_ID, - role=models.MessageRole.USER, - content=[models.Text( - text=( - "I booked Hotel Sakura in Tokyo. For this example, " - "the fictional booking confirmation code is DEMO-7QX9." - ), - )], - created_at=datetime.now(timezone.utc), - ) - print("Event with excluded information:") - print(sensitive_event.model_dump_json(by_alias=True, indent=2)) - - -if __name__ == "__main__": - add_sensitive_event() -# STEP_END - -# STEP_START search_long_term_memory_exclusion -import os - -from redis_agent_memory import AgentMemory - -ENDPOINT = "" -STORE_ID = "" -USER_ID = "quickstart-user" - + health = agent_memory.store_health() + show("Store health", health) -def search_with_exclusion(): - with AgentMemory( - ENDPOINT, - store_id=STORE_ID, - api_key=os.environ["API_KEY"], - ) as agent_memory: - exclusion_results = agent_memory.search_long_term_memory( + results = agent_memory.search_long_term_memory( request={ - "text": "Where is the user staying in Tokyo?", + "text": "What dietary requirements and food preferences does the user have?", "filter_": { - "owner_id": {"eq": USER_ID}, + "owner_id": { + "eq": USER_ID, + } }, "limit": 5, }, ) - print("Memories after semantic exclusion:") - print(exclusion_results.model_dump_json(by_alias=True, indent=2)) + show("Automatically extracted memories", results) if __name__ == "__main__": - search_with_exclusion() + main() # STEP_END diff --git a/local_examples/agent-memory/agent_memory_sdk.ts b/local_examples/agent-memory/agent_memory_sdk.ts index 7e103f4318..ea6d12c582 100644 --- a/local_examples/agent-memory/agent_memory_sdk.ts +++ b/local_examples/agent-memory/agent_memory_sdk.ts @@ -3,24 +3,29 @@ // STEP_START connect_health_check import { AgentMemory } from "@redis-iris/agent-memory"; -async function checkHealth() { - const apiKey = process.env.API_KEY; - if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); - } +const apiKey = process.env.API_KEY; +if (!apiKey) { + throw new Error("Set the API_KEY environment variable."); +} - const agentMemory = new AgentMemory({ - serverURL: "", - storeId: "", - apiKey, - }); +const serverURL = ""; +const storeId = ""; +const sessionId = "travel-planning-session"; +const userId = "quickstart-user"; + +const agentMemory = new AgentMemory({ + serverURL, + storeId, + apiKey, +}); +async function run() { const health = await agentMemory.storeHealth(); console.log("Store health:"); console.dir(health, { depth: null }); } -checkHealth().catch((error) => { +run().catch((error) => { console.error(error); process.exitCode = 1; }); @@ -29,20 +34,26 @@ checkHealth().catch((error) => { // STEP_START add_session_event import { AgentMemory } from "@redis-iris/agent-memory"; -async function buildConversationContext() { - const apiKey = process.env.API_KEY; - if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); - } +const apiKey = process.env.API_KEY; +if (!apiKey) { + throw new Error("Set the API_KEY environment variable."); +} - const sessionId = "travel-planning-session"; - const userId = "quickstart-user"; +const serverURL = ""; +const storeId = ""; +const sessionId = "travel-planning-session"; +const userId = "quickstart-user"; - const agentMemory = new AgentMemory({ - serverURL: "", - storeId: "", - apiKey, - }); +const agentMemory = new AgentMemory({ + serverURL, + storeId, + apiKey, +}); + +async function run() { + const health = await agentMemory.storeHealth(); + console.log("Store health:"); + console.dir(health, { depth: null }); const event = await agentMemory.addSessionEvent({ sessionId, @@ -61,7 +72,7 @@ async function buildConversationContext() { console.dir(session, { depth: null }); } -buildConversationContext().catch((error) => { +run().catch((error) => { console.error(error); process.exitCode = 1; }); @@ -70,19 +81,26 @@ buildConversationContext().catch((error) => { // STEP_START search_long_term_memory_basic import { AgentMemory } from "@redis-iris/agent-memory"; -async function recallExtractedMemory() { - const apiKey = process.env.API_KEY; - if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); - } +const apiKey = process.env.API_KEY; +if (!apiKey) { + throw new Error("Set the API_KEY environment variable."); +} - const userId = "quickstart-user"; +const serverURL = ""; +const storeId = ""; +const sessionId = "travel-planning-session"; +const userId = "quickstart-user"; - const agentMemory = new AgentMemory({ - serverURL: "", - storeId: "", - apiKey, - }); +const agentMemory = new AgentMemory({ + serverURL, + storeId, + apiKey, +}); + +async function run() { + const health = await agentMemory.storeHealth(); + console.log("Store health:"); + console.dir(health, { depth: null }); const results = await agentMemory.searchLongTermMemory({ text: "What dietary requirements and food preferences does the user have?", @@ -97,185 +115,7 @@ async function recallExtractedMemory() { console.dir(results, { depth: null }); } -recallExtractedMemory().catch((error) => { - console.error(error); - process.exitCode = 1; -}); -// STEP_END - -// STEP_START add_conversation_turns -import { AgentMemory } from "@redis-iris/agent-memory"; - -async function addConversationTurns() { - const apiKey = process.env.API_KEY; - if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); - } - - const sessionId = "travel-planning-session"; - const userId = "quickstart-user"; - - const agentMemory = new AgentMemory({ - serverURL: "", - storeId: "", - apiKey, - }); - - const turns = [ - { role: "ASSISTANT", actorId: "travel-agent", text: "What dates are you traveling?" }, - { role: "USER", actorId: userId, text: "I arrive on October 10 and leave on October 18." }, - { role: "ASSISTANT", actorId: "travel-agent", text: "Would you like formal or casual restaurants?" }, - { role: "USER", actorId: userId, text: "Mostly casual places near public transit." }, - { role: "ASSISTANT", actorId: "travel-agent", text: "Do you have a preferred budget?" }, - { role: "USER", actorId: userId, text: "About 40 euros per person." }, - ] as const; - - for (const turn of turns) { - await agentMemory.addSessionEvent({ - sessionId, - actorId: turn.actorId, - role: turn.role, - content: [{ text: turn.text }], - createdAt: new Date(), - }); - } -} - -addConversationTurns().catch((error) => { - console.error(error); - process.exitCode = 1; -}); -// STEP_END - -// STEP_START retrieve_session_summary -import { AgentMemory } from "@redis-iris/agent-memory"; - -async function retrieveSessionSummary() { - const apiKey = process.env.API_KEY; - if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); - } - - const sessionId = "travel-planning-session"; - - const agentMemory = new AgentMemory({ - serverURL: "", - storeId: "", - apiKey, - }); - - const compactedSession = await agentMemory.getSessionMemory(sessionId); - console.log("Compacted session memory:"); - console.dir(compactedSession, { depth: null }); -} - -retrieveSessionSummary().catch((error) => { - console.error(error); - process.exitCode = 1; -}); -// STEP_END - -// STEP_START search_long_term_memory_custom_type -import { AgentMemory } from "@redis-iris/agent-memory"; - -async function searchCustomMemoryType() { - const apiKey = process.env.API_KEY; - if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); - } - - const userId = "quickstart-user"; - - const agentMemory = new AgentMemory({ - serverURL: "", - storeId: "", - apiKey, - }); - - const customResults = await agentMemory.searchLongTermMemory({ - text: "What are the requirements for the user's trip?", - filter: { - ownerId: { eq: userId }, - memoryType: { eq: "trip_preference" }, - }, - limit: 5, - }); - console.log("Trip preference memories:"); - console.dir(customResults, { depth: null }); -} - -searchCustomMemoryType().catch((error) => { - console.error(error); - process.exitCode = 1; -}); -// STEP_END - -// STEP_START add_session_event_sensitive -import { AgentMemory } from "@redis-iris/agent-memory"; - -async function addSensitiveEvent() { - const apiKey = process.env.API_KEY; - if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); - } - - const sessionId = "travel-planning-session"; - const userId = "quickstart-user"; - - const agentMemory = new AgentMemory({ - serverURL: "", - storeId: "", - apiKey, - }); - - const sensitiveEvent = await agentMemory.addSessionEvent({ - sessionId, - actorId: userId, - role: "USER", - content: [{ - text: "I booked Hotel Sakura in Tokyo. For this example, the fictional booking confirmation code is DEMO-7QX9.", - }], - createdAt: new Date(), - }); - console.log("Event with excluded information:"); - console.dir(sensitiveEvent, { depth: null }); -} - -addSensitiveEvent().catch((error) => { - console.error(error); - process.exitCode = 1; -}); -// STEP_END - -// STEP_START search_long_term_memory_exclusion -import { AgentMemory } from "@redis-iris/agent-memory"; - -async function searchWithExclusion() { - const apiKey = process.env.API_KEY; - if (!apiKey) { - throw new Error("Set the API_KEY environment variable."); - } - - const userId = "quickstart-user"; - - const agentMemory = new AgentMemory({ - serverURL: "", - storeId: "", - apiKey, - }); - - const exclusionResults = await agentMemory.searchLongTermMemory({ - text: "Where is the user staying in Tokyo?", - filter: { - ownerId: { eq: userId }, - }, - limit: 5, - }); - console.log("Memories after semantic exclusion:"); - console.dir(exclusionResults, { depth: null }); -} - -searchWithExclusion().catch((error) => { +run().catch((error) => { console.error(error); process.exitCode = 1; }); From 03e4cdd55fb30addcd7e21074a99f97e39239611 Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Wed, 21 Oct 2026 12:30:28 -0700 Subject: [PATCH 17/26] Preserve the developer connection link --- .../develop/ai/context-engine/agent-memory/developer-guide.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/develop/ai/context-engine/agent-memory/developer-guide.md b/content/develop/ai/context-engine/agent-memory/developer-guide.md index e2cddc8d88..c61f83e2ef 100644 --- a/content/develop/ai/context-engine/agent-memory/developer-guide.md +++ b/content/develop/ai/context-engine/agent-memory/developer-guide.md @@ -15,6 +15,8 @@ Your application brings two kinds of memory to a conversation: the messages exch This guide uses a travel assistant to show where those requests belong in your application. Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first to create a service and send your first requests. For how the two kinds of memory work, see the [overview]({{< relref "/develop/ai/context-engine/agent-memory/overview" >}}). + + ## Configure the application Every client needs the service endpoint, Store ID, and application programming interface (API) key. The endpoint tells the client where to connect. The Store ID selects the memory store, and the key authenticates the request. From a56a107c6324b39b0fc6e4157e40063b70ca724e Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Wed, 21 Oct 2026 13:30:41 -0700 Subject: [PATCH 18/26] Write routing setup for stable publication --- .../ai/context-engine/agent-memory/namespace-routing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/develop/ai/context-engine/agent-memory/namespace-routing.md b/content/develop/ai/context-engine/agent-memory/namespace-routing.md index 5f64f14fa3..68b3b26acb 100644 --- a/content/develop/ai/context-engine/agent-memory/namespace-routing.md +++ b/content/develop/ai/context-engine/agent-memory/namespace-routing.md @@ -13,7 +13,7 @@ weight: 14 Namespace routing selects a shared namespace for an eligible memory. Admission rules check whether a memory can enter a destination. Use routing to organize shared memories by content, and admission rules to reject content that does not belong in a namespace. -The policy applies to one store. It targets existing shared namespace resources, using their stable `namespaceId` values. It does not create namespaces or change access permissions. Your service must support namespace routing and have it enabled. The beta software development kits (SDKs) expose these operations in Python `0.5.0b1` and TypeScript `0.4.0-beta.1`; installing an SDK does not enable routing on the service. +The policy applies to one store. It targets existing shared namespace resources, using their stable `namespaceId` values. It does not create namespaces or change access permissions. Your service must support namespace routing and have it enabled. ## Understand placement and admission @@ -33,7 +33,7 @@ Strategies within a rule run in list order; the first match fires the rule. An a ## Configure a policy -Create a shared namespace with `CreateNamespace`, and save its returned ID as `SHARED_NAMESPACE_ID`. Use `scope: "SHARED"` and omit `ownerId`. Install the beta SDK for your language, then set `AGENT_MEMORY_URL`, `STORE_ID`, and `API_KEY` as described in the [developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md#connect-to-a-redis-agent-memory-service). +Create a shared namespace with `CreateNamespace`, and save its returned ID as `SHARED_NAMESPACE_ID`. Use `scope: "SHARED"` and omit `ownerId`. Install the software development kit (SDK) for your language, then set `AGENT_MEMORY_URL`, `STORE_ID`, and `API_KEY` as described in the [developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md#connect-to-a-redis-agent-memory-service). This example accepts text containing `travel` into that namespace. The regular expression is case-insensitive. **PUT replaces the entire policy**, so read the current policy first and retain any rules you still need. From 5e292c949ab69268d6ced1e9dc1986972e46d58b Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Wed, 21 Oct 2026 13:31:02 -0700 Subject: [PATCH 19/26] Use stable SDK packages in session setup --- content/develop/ai/context-engine/agent-memory/list-sessions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/develop/ai/context-engine/agent-memory/list-sessions.md b/content/develop/ai/context-engine/agent-memory/list-sessions.md index 959f0ee259..9ad7553acd 100644 --- a/content/develop/ai/context-engine/agent-memory/list-sessions.md +++ b/content/develop/ai/context-engine/agent-memory/list-sessions.md @@ -30,7 +30,7 @@ A request without filters must set `includeAll=true`. Do not combine `includeAll The default is `sortBy=updatedAt` and `sortOrder=desc`, so recent activity appears first. `sortBy` accepts `sessionId`, `createdAt`, or `updatedAt`. When omitted, `sortOrder` is `asc` for `sessionId` and `desc` for timestamps. `limit` accepts 1–1000 and defaults to 100. -The examples use Python `redis-agent-memory==0.5.0b1` or TypeScript `@redis-iris/agent-memory@0.4.0-beta.1`. Install that beta explicitly and use a service that supports the endpoint. Set `AGENT_MEMORY_URL`, `STORE_ID`, `API_KEY`, and `OWNER_ID` in your shell. See the [developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md#connect-to-a-redis-agent-memory-service) for connection details. +Install `redis-agent-memory` for Python or `@redis-iris/agent-memory` for TypeScript. Use a service that supports the endpoint. Set `AGENT_MEMORY_URL`, `STORE_ID`, `API_KEY`, and `OWNER_ID` in your shell. See the [developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md#connect-to-a-redis-agent-memory-service) for connection details. These examples list all pages for one owner, with the most recently updated sessions first: From 1530cb228ca15f59da1ee28d832a4f880c8479ca Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Wed, 21 Oct 2026 13:31:04 -0700 Subject: [PATCH 20/26] Install stable SDKs in the published quickstart --- .../ai/context-engine/agent-memory/sdk-quickstart.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/content/develop/ai/context-engine/agent-memory/sdk-quickstart.md b/content/develop/ai/context-engine/agent-memory/sdk-quickstart.md index a17a2a7a12..f7f364f50a 100644 --- a/content/develop/ai/context-engine/agent-memory/sdk-quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/sdk-quickstart.md @@ -42,14 +42,12 @@ Keep the API key out of source control, application logs, and other unsecured lo ## Install the SDK -These examples use the beta software development kits (SDKs): Python `0.5.0b1` and TypeScript `0.4.0-beta.1`. Install these versions explicitly; an unversioned install can select a stable release. Your service must support the operation you call. Installing a beta SDK does not enable server features. - {{< multitabs id="agent-memory-install" tab1="Python" tab2="TypeScript" >}} ```sh -python -m pip install "redis-agent-memory==0.5.0b1" +python -m pip install redis-agent-memory ``` -tab-sep- @@ -58,7 +56,7 @@ python -m pip install "redis-agent-memory==0.5.0b1" mkdir agent-memory-quickstart cd agent-memory-quickstart npm init -y -npm install @redis-iris/agent-memory@0.4.0-beta.1 +npm install @redis-iris/agent-memory npm install --save-dev tsx ``` From 319a93e09005d909c6fbd7ac14750fba09e444fb Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Thu, 22 Oct 2026 17:41:32 -0700 Subject: [PATCH 21/26] Make new Agent Memory features usable from the docs --- .../ai/context-engine/agent-memory/_index.md | 3 + .../agent-memory/developer-guide.md | 156 +- .../agent-memory/ingestion-webhooks.md | 399 +++++ .../agent-memory/list-sessions.md | 20 +- .../agent-memory/long-term-memory.md | 9 + .../context-engine/agent-memory/memory-etl.md | 423 +++++ .../agent-memory/namespace-routing.md | 22 +- .../context-engine/agent-memory/namespaces.md | 4 + .../context-engine/agent-memory/quickstart.md | 6 + .../context-engine/agent-memory/sessions.md | 5 + .../agent-memory/summary-views.md | 260 ++++ .../self-managed/authentication.md | 173 ++- .../self-managed/configuration.md | 88 +- .../openapi-control-plane.json | 1358 ++++++++++++++++- .../iris/agent-memory/self-managed/deploy.md | 10 +- .../agent-memory/agent_memory_sdk.py | 24 +- .../agent-memory/agent_memory_sdk.ts | 45 +- 17 files changed, 2904 insertions(+), 101 deletions(-) create mode 100644 content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md create mode 100644 content/develop/ai/context-engine/agent-memory/memory-etl.md create mode 100644 content/develop/ai/context-engine/agent-memory/summary-views.md diff --git a/content/develop/ai/context-engine/agent-memory/_index.md b/content/develop/ai/context-engine/agent-memory/_index.md index 56b71aae12..58c3a766a9 100644 --- a/content/develop/ai/context-engine/agent-memory/_index.md +++ b/content/develop/ai/context-engine/agent-memory/_index.md @@ -106,6 +106,9 @@ After your service is ready, follow the [quickstart]({{< relref "/develop/ai/con * [Developer guide]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide" >}}): Put the pieces together in an application. * [Sessions]({{< relref "/develop/ai/context-engine/agent-memory/sessions" >}}): Store conversation events and configure retention and summarization. * [Long-term memory]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory" >}}): Create and retrieve memories across conversations, including custom memory types. +* [MemoryETL](/content/develop/ai/context-engine/agent-memory/memory-etl.md): Extract memories from documents and inspect their source evidence. +* [Summary Views](/content/develop/ai/context-engine/agent-memory/summary-views.md): Materialize text or structured summaries of long-term memories. +* [Ingestion webhooks](/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md): Review input and generated changes before they are saved. * [Namespaces]({{< relref "/develop/ai/context-engine/agent-memory/namespaces" >}}): Organize memories and manage namespace hierarchies. * [API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}): Look up request and response schemas. diff --git a/content/develop/ai/context-engine/agent-memory/developer-guide.md b/content/develop/ai/context-engine/agent-memory/developer-guide.md index c61f83e2ef..8a775e8022 100644 --- a/content/develop/ai/context-engine/agent-memory/developer-guide.md +++ b/content/develop/ai/context-engine/agent-memory/developer-guide.md @@ -26,9 +26,82 @@ Every client needs the service endpoint, Store ID, and application programming i 1. Load the API key you saved during service creation from a secret store or environment variable. Keep it out of source control and browser code. 1. Create the client as shown in [Create the client and check the service health]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#create-the-client-and-check-the-service-health" >}}). Use the Python software development kit (SDK) for Python applications, the TypeScript SDK for JavaScript or TypeScript, or the REST API for other languages. +### Manage a service with the Control Plane SDK + +The same package supplies two independently configured clients: + +| Client | Purpose | Connection and credential | +|:-------|:--------|:--------------------------| +| `AgentMemoryService` | Manage stores and extractors. Supply the store ID on each call. | Control Plane URL and operator `token`. | +| `AgentMemory` | Read and write memory within the store selected by the constructor. | Data Plane URL, Store ID, and application credential. | + +Use the self-managed Control Plane admin token or supported operator token with `AgentMemoryService`. Its SDK uses `/v2/memory` routes; the self-managed `/v1` admin API remains available. The SDK does not derive a Data Plane URL or credential from a Control Plane response. + +Set `AGENT_MEMORY_CONTROL_PLANE_URL` and `AGENT_MEMORY_SERVICE_TOKEN` separately from `AGENT_MEMORY_URL`, `STORE_ID`, and `API_KEY`. These examples read the store and check Data Plane health: + +{{< multitabs id="memory-service-sdk" tab1="Python" tab2="TypeScript" >}} + +```python +import asyncio +import os + +from redis_agent_memory import AgentMemory, AgentMemoryService + + +async def main(): + async with AgentMemoryService( + server_url=os.environ["AGENT_MEMORY_CONTROL_PLANE_URL"], + token=os.environ["AGENT_MEMORY_SERVICE_TOKEN"], + ) as service: + store = await service.get_store_async(store_id=os.environ["STORE_ID"]) + print("Store:", store.name) + async with AgentMemory( + os.environ["AGENT_MEMORY_URL"], store_id=os.environ["STORE_ID"], + api_key=os.environ["API_KEY"], + ) as memory: + print((await memory.store_health_async()).status) + + +asyncio.run(main()) +``` + +-tab-sep- + +```typescript +import { AgentMemory, AgentMemoryService } from "@redis-iris/agent-memory"; + +async function main() { + const { AGENT_MEMORY_CONTROL_PLANE_URL, AGENT_MEMORY_SERVICE_TOKEN, + AGENT_MEMORY_URL, STORE_ID, API_KEY } = process.env; + if (!AGENT_MEMORY_CONTROL_PLANE_URL || !AGENT_MEMORY_SERVICE_TOKEN || + !AGENT_MEMORY_URL || !STORE_ID || !API_KEY) { + throw new Error("Set both planes' connection values."); + } + const service = new AgentMemoryService({ + serverURL: AGENT_MEMORY_CONTROL_PLANE_URL, token: AGENT_MEMORY_SERVICE_TOKEN, + }); + console.log("Store:", (await service.getStore(STORE_ID)).name); + const memory = new AgentMemory({ serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY }); + console.log((await memory.storeHealth()).status); +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : "Request failed"); + process.exitCode = 1; +}); +``` + +{{< /multitabs >}} + +If omitted, the Control Plane token is read from `AGENT_MEMORY_SERVICE_TOKEN`; it does not fall back to the Data Plane key. Python service models, errors, and retry helpers use `redis_agent_memory.controlplane`; Data Plane imports remain separate. Use each client's corresponding error classes. + +Store and extractor management, detectors, and memory-type reference data have Cloud and self-managed API implementations. The generated client also includes Cloud-only account database, end user license agreement (EULA), network, store-model, metrics, flush, and empty-store operations; those return `404` on-prem. The current Cloud Control Plane does not accept a programmatic credential for this SDK. Use the Cloud console for Cloud service setup rather than sending a Data Plane key or assuming a console session is a service token. + +See [MemoryETL](/content/develop/ai/context-engine/agent-memory/memory-etl.md#create-an-extractor) for extractor administration and the [self-managed Control Plane reference](/content/operate/iris/agent-memory/self-managed/control-plane-api-reference.md) for the admin REST API. Webhook-policy configuration uses the admin REST API; it is not a method on the current service SDK. + ## Identify users and conversations -Use one stable account ID for the current user across conversations. Get it from your application's authenticated identity, such as a verified identity-provider claim. Do not generate a new user ID for each chat or trust an owner ID supplied by a browser. This ID determines whose personal memories the application can return. +Use one stable account ID for the current user across conversations. Get it from your application's authenticated identity, such as its login session or a verified identity-provider claim. An external identity provider is not required when your backend handles user authentication and authorization. Do not generate a new user ID for each chat or trust an owner ID supplied by a browser. Some IDs come from your application; others come from Redis Agent Memory: @@ -41,14 +114,22 @@ Some IDs come from your application; others come from Redis Agent Memory: | `eventId` | The server. | Save it from the event response if you need to refer to that event. | | Long-term memory `id` | Your application for direct creation; the server during extraction. | Supply a stable record ID when creating a memory directly. | | `namespaceId` | The server when it creates a namespace. | Save it and use it in `namespaceRef`. Supply the user's `ownerId` when creating a personal namespace. | +| Event `createdAt` | Your application. | Send when the message occurred; it is a required event input. | +| Event `systemTimestamp` | The server. | Records when the event was stored. | +| Memory `createdAt` and `updatedAt` | The server. | Use the returned timestamps when displaying or sorting memories. | For example, the quickstart uses `quickstart-user` as the user's ID and `travel-planning-session` for one conversation. Replace the user ID with your authenticated account ID in an application. IDs must follow the format in the [API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}). -On-prem deployments can also enforce ownership on the server. When enabled, the first session event's `actorId` and any `ownerId` you supply must match the authenticated User ID, including case. Later events in an accessible session can use a different actor. See [Server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement) for configuration, identity requirements, and the limits of ordinary agent keys. +On-prem deployments enforce ownership by default in release 0.10.2. With enforcement enabled, the first session event's `actorId` and any `ownerId` you supply must match the authenticated user ID, including case. Later events in an accessible session can use a different actor. An agent key supplies store permissions; a [delegated user token](/content/operate/iris/agent-memory/self-managed/authentication.md#delegate-a-user-with-an-agent-key) supplies the user identity for personal access. See [Server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement) before upgrading an existing deployment. ### Authorize every memory request -Unless server-side ownership enforcement is enabled, your application must decide which memories the user can access. Do this in trusted application code, such as your backend, before sending a request to Redis Agent Memory. Store access alone does not restrict a caller to one user's memories. +Your application must decide which memories the user can access. Put these checks in trusted backend code before sending a request to Redis Agent Memory. Store access alone does not restrict a caller to one user's memories. Cloud store credentials also require these application checks; a Redis Cloud account user is not your application's end user. + +| Ownership enforcement | Responsibility | +|:----------------------|:---------------| +| Off | Your backend enforces personal ownership and permitted shared access on every request. Owner fields and filters are metadata, not verified user identity. | +| On | The server also restricts personal records to the authenticated user and applies that restriction to searches, lists, and direct-ID requests. Your backend still enforces application rules, including team access to shared content. | * For personal searches, set the `ownerId` filter from the authenticated user's ID. For session listing, use `filterOwnerId`. A request from `user-42` must not be able to select `user-99` as its owner. * For shared memories, check which shared namespaces that user can access, then restrict the query to those namespace IDs. Do not add a personal owner filter to that shared query: it would hide shared memories written by other users. @@ -56,6 +137,8 @@ Unless server-side ownership enforcement is enabled, your application must decid Use the authenticated user on every request to choose the owner filter or the permitted shared namespaces. Search filters limit results; they do not authenticate the user. For examples, see [Namespaces](/content/develop/ai/context-engine/agent-memory/namespaces.md). +Keep mandatory owner restrictions separate from optional search conditions. Combining an owner and another filter with `filterOp: "any"` can return either owner's records when enforcement is off. `includeAll` on a list request is also a selection option, not permission to enumerate a store. + ## Build context for each agent turn Suppose a user returns to the travel assistant and asks, "Where should I eat in Kyoto?" The current conversation may contain their travel dates. Long-term memory may contain their vegetarian diet from an earlier chat. The model needs both to give a useful answer. @@ -122,12 +205,79 @@ A failed request is different from a successful search with no matches. Handle t | Search timeout | If the assistant can answer from the current conversation, continue without recalled memories. Otherwise, tell the user that memory is unavailable and ask them to retry. | | Event-write timeout | Retrieve the session and check whether the event was stored before sending it again. Compare its actor, timestamp, and content. Repeating a write can create a duplicate. | +### Handle typed errors and partial results + +| Status or result | Action | +|:-----------------|:-------| +| `401` | Refresh or correct the credential. An expired delegated user token needs the application's login/token lifecycle. | +| `403` or a hidden personal-record `404` | Check store grants and authenticated ownership. Changing an owner filter does not grant access. | +| `409` | Correct an archived namespace destination or a stale extractor/hook-policy revision before retrying. | +| `423` | The resource is suspended. Restore its availability before retrying. | +| `424` | Inspect the typed failed-dependency error and its problem type. | +| `429` or retryable `5xx` | Use bounded backoff and honor retry timing. Retrying a write also needs its operation's idempotency or recovery contract. | +| Successful bulk response with `errors` | Inspect every item. Other records can already be created, deleted, or moved. | + +The operation's response contract determines its `424` exception class. For example, search uses `MemoryFailedDependencyErrorResponseContent`, while store health uses `FailedDependencyErrorResponseContent`. Both can report unavailable resources or database memory exhaustion; the memory response also covers model credentials and quota failures. Inspect the problem type as well as the class. Python exceptions are in `redis_agent_memory.errors`, with problem fields under `error.data`. TypeScript exceptions are in `@redis-iris/agent-memory/models/errors`. + +This complete program checks store health before recall and handles either operation's dependency error separately from an empty `items` array: + +```python +import asyncio +import os + +from redis_agent_memory import AgentMemory, errors + + +async def main(): + headers = {"X-Iris-User-Token": os.environ["USER_TOKEN"]} if os.environ.get("USER_TOKEN") else {} + async with AgentMemory( + os.environ["AGENT_MEMORY_URL"], store_id=os.environ["STORE_ID"], + api_key=os.environ["API_KEY"], + ) as memory: + try: + await memory.store_health_async(http_headers=headers) + result = await memory.search_long_term_memory_async( + request={"filter_": {"owner_id": {"eq": os.environ["OWNER_ID"]}}, "limit": 5}, + http_headers=headers, + ) + except errors.MemoryFailedDependencyErrorResponseContent as error: + print("Memory dependency unavailable:", error.data.title) + return + except errors.FailedDependencyErrorResponseContent as error: + print("Service dependency unavailable:", error.data.title) + return + print([item.text for item in result.items]) + + +asyncio.run(main()) +``` + +The ingestion-hook `503` retry error and `422` stopped error also have distinct types and recovery rules. See [webhook failures](/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md#handle-failures-and-inspect-status). Use store-scoped `store_health`/`storeHealth` for current servers; a retained SDK `health()` method does not establish that a server still serves `/health`. + +### Preserve TypeScript argument positions when upgrading + +TypeScript listing methods use positional arguments. Their new parameters do not all occupy the same position: + +| Method | Relevant positions | +|:-------|:-------------------| +| `listNamespaces` | Request options remain sixth; `sortBy` and `sortOrder` are seventh and eighth. | +| `listSessions` | `includeAll` is fourth, request options fifth, and namespace filtering sixth. | +| `listSessionSummaries` | Namespace filtering is fourth, then `includeAll`, `sortBy`, `sortOrder`, and request options. | + +Pass `undefined` for skipped positions. Python uses named arguments. See [namespace sorting](/content/develop/ai/context-engine/agent-memory/namespaces.md#sort-namespaces) and [session listing](/content/develop/ai/context-engine/agent-memory/list-sessions.md) for complete examples. + ### Create long-term memories Create memories directly when your application already has information to store, such as a preference submitted through a form. Set the owner and a stable record ID, and check the bulk response for per-record errors. Direct creation does not apply automatic extraction or sensitive-data exclusions. See [Long-term memory]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory" >}}) for creation methods, search fields, retention, and extraction controls. +For unstructured documents, use [MemoryETL](/content/develop/ai/context-engine/agent-memory/memory-etl.md) to configure an extractor, submit a revision, and track its outputs. Source links let you trace extracted memories back to document or session inputs. + +For a reusable summary of selected long-term memories, use [Summary Views](/content/develop/ai/context-engine/agent-memory/summary-views.md). Read their result status, freshness, and coverage before adding generated text or structured data to the model context. + +Use [ingestion webhooks](/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md) when an application policy must review input or generated changes before they are saved. Configure the receiver, selected paths, signing keys, and retry handling before enabling the policy. + ## Define custom memory types If your application needs structured values, define a custom memory type. For example, `trip_preference` can capture `destinations`, `travel_period`, `dietary_requirements`, and `food_preferences`. diff --git a/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md b/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md new file mode 100644 index 0000000000..56e076c284 --- /dev/null +++ b/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md @@ -0,0 +1,399 @@ +--- +alwaysopen: false +categories: +- docs +- develop +- ai +description: Configure blocking ingestion webhooks, verify signed callbacks, and recover from failures. +hideListLinks: true +linktitle: Ingestion webhooks +title: Review content with ingestion webhooks +weight: 15 +--- + +Ingestion webhooks let your application accept, reject, or transform content before Agent Memory saves it. A callback returns a decision; successful HTTP delivery alone does not approve a write. + +The supported setup uses a self-managed deployment with the Redis background-job backend. Hooks are off by default. This guide does not assume Cloud account controls or a Cloud webhook rollout. + +## Choose what to review + +Select each path your policy needs. Selecting creation does not also select updates or deletion. + +| Phase | Paths | Review | +|:------|:------|:-------| +| `before_input` | `session.append`, `memory.create`, `document.submit` | Session input, a manual memory batch, or a document before persistence and extraction dispatch. Accept, reject, or transform. | +| `before_apply` | `session.builtin`, `session.custom`, `document.chunk` | Generated creates, updates, and deletes before the current batch is applied. Whole-batch or per-operation decisions. | +| `before_input` | `memory.update`, `memory.delete`, `memory.move` | An authorized memory mutation. Accept or reject only. | +| `before_input` | `session.summary`, `session.property`, `session.delete`, `session.events.delete` | Session content changes and explicit deletion, including applicable worker writes. Accept or reject only. | +| `before_apply` | `summary_view.refresh` | A generated Summary View value before publication. Accept or reject only. | + +Configuration, reads, searches, automatic expiration, and store teardown are outside content hooks. Empty extraction batches and empty Summary View results have no generated content to review. Authorization runs before content is sent to a receiver; an acceptance cannot grant access or bypass validation. + +## Prepare the deployment + +Start with the [self-managed deployment](/content/operate/iris/agent-memory/self-managed/deploy.md). These are process settings; a client cannot enable them through a request header. + +An operator must configure both Data Plane servers and workers: + +1. Enable `memory.webhooks.enabled` in chart-rendered configuration, or `webhooks.enabled` at the root of a bring-your-own file. +1. Enable the Redis job backend and [authenticated worker callbacks](/content/operate/iris/agent-memory/self-managed/authentication.md#worker-callbacks). +1. Configure the same signing-secret backend and prefix in the Control Plane, Data Plane, and worker. Supported backends are Vault and AWS Secrets Manager. The Control Plane writes keys; Data Plane processes read them. +1. Serve the receiver over HTTPS with a certificate trusted by the Data Plane. +1. For a private receiver address, allow its range in `memory.webhooks.private_cidrs` on both servers and workers. + +For example, allow one private receiver address in Helm values: + +```yaml +memory: + webhooks: + enabled: true + private_cidrs: + - /32 +``` + +Delivery checks all resolved addresses at connection time. Redirects and environment HTTP proxies are not used. An allowlist cannot permit loopback, link-local, metadata-service, multicast, or other protected addresses. A localhost URL is not a production callback endpoint. + +For Vault, use matching application configuration in the Data Plane and Control Plane: + +```yaml +secrets: + provider: vault +vault: + address: https://:8200 + mount: secret + base_path: agent-memory +``` + +In chart-rendered values, put this under `memory` and `controlplane.configData`, respectively. Supply `vault.token` through a mounted configuration Secret. The Control Plane needs permission to write and delete signing-key versions; servers and workers need read permission. Keep real Vault tokens and callback keys out of source control. + +Before enabling output hooks on an existing deployment, upgrade workers first, then servers, and drain jobs queued by the older release. Keep hooks disabled until both sides support them. Setting the deployment flag to false stops delivery and fails selected work closed; it does not approve pending operations. + +For Active-Active backing databases, route every server and worker for a store through the same authoritative writer connection. Do not mix member addresses or use region-dependent aliases for that connection. Before changing writers, drain old writers, wait for replication, and restart the processes to clear old connections. A store with hook history fails closed if its persisted writer connection is missing or invalid; regional queue ownership does not make independent writers safe. + +## Configure a store policy + +Use the self-managed Control Plane REST API and an operator credential. The Data Plane SDKs expose status reads; `AgentMemoryService` does not currently expose hook-policy management. Set `AGENT_MEMORY_CONTROL_PLANE_URL`, `AGENT_MEMORY_SERVICE_TOKEN`, `STORE_ID`, and `HOOK_URL`. For the receiver example, use `https://:8443/hook`, or the HTTPS ingress URL that forwards to it. + +A policy PUT replaces the whole policy. Include every phase you want to retain; omitting a phase removes it. Read the current revision before writing: + +```sh +curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ + "$AGENT_MEMORY_CONTROL_PLANE_URL/v1/stores/$STORE_ID/ingestion-hooks" > hook-config.json +``` + +A new store starts at revision 0. Generate separate 32-byte signing keys for the two phases, then configure them with delivery still disabled: + +```sh +export HOOK_INPUT_KEY="$(openssl rand -base64 32)" +export HOOK_OUTPUT_KEY="$(openssl rand -base64 32)" + +jq --arg url "$HOOK_URL" \ + '{expectedRevision:.revision,enabled:false,beforeInput:{url:$url,paths:["session.append","memory.create","document.submit"],signingKey:env.HOOK_INPUT_KEY},beforeApply:{url:$url,paths:["session.builtin","session.custom","document.chunk"],signingKey:env.HOOK_OUTPUT_KEY}}' \ + hook-config.json | curl --fail-with-body --silent --show-error --request PUT \ + --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ + --header 'Content-Type: application/json' --data @- \ + "$AGENT_MEMORY_CONTROL_PLANE_URL/v1/stores/$STORE_ID/ingestion-hooks" > hook-config-new.json +``` + +The response returns each phase's `keyId` and `signingKeyConfigured`, never its key. Load those IDs and original keys into the receiver. A receiver that accepts the JSON map used in the next section can get it from: + +```sh +export HOOK_KEYS_JSON="$(jq -c '{(.beforeInput.keyId):env.HOOK_INPUT_KEY,(.beforeApply.keyId):env.HOOK_OUTPUT_KEY}' hook-config-new.json)" +``` + +After the receiver is ready, enable the same policy using the returned revision and omitting signing keys to retain them: + +```sh +jq '{expectedRevision:.revision,enabled:true,beforeInput:{url:.beforeInput.url,paths:.beforeInput.paths},beforeApply:{url:.beforeApply.url,paths:.beforeApply.paths}}' \ + hook-config-new.json | curl --fail-with-body --silent --show-error --request PUT \ + --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ + --header 'Content-Type: application/json' --data @- \ + "$AGENT_MEMORY_CONTROL_PLANE_URL/v1/stores/$STORE_ID/ingestion-hooks" | jq +``` + +A stale `expectedRevision` returns `409`. To rotate a key, supply a new `signingKey` and load its returned ID into the receiver. Retain the previous receiver key for 24 hours: pending work keeps its original endpoint and key version. Policy changes affect new work; they do not rewrite saved decisions or a pending run's policy. + +Keep the previous endpoint reachable while its pending work completes; delivery does not follow a redirect to the replacement URL. + +## Verify callbacks and return decisions + +The payload identifies `version`, `eventId`, `ingestionOperationId`, `phase`, `path`, store/owner context, configuration revision, correlation ID, and timestamp. Input appears under `input`; generated candidates appear under `operations`. A candidate's `operationId` identifies the proposed operation, separately from its target memory ID. + +Verify the exact raw request bytes before interpreting the JSON. The signature is: + +```text +v1= +``` + +Read the timestamp, event ID, key ID, and signature from `X-Redis-Webhook-Timestamp`, `X-Redis-Webhook-Event-Id`, `X-Redis-Webhook-Key-Id`, and `X-Redis-Webhook-Signature`. Check freshness and use a constant-time signature comparison. Retries keep the event ID and raw body but receive a fresh signed timestamp. + +This runnable receiver uses FastAPI and the Python standard library. It rejects the demonstration marker `[reject]` and accepts other signed content. Replace that marker policy with your application's policy. Save as `receiver.py`: + +```python +import base64 +import hashlib +import hmac +import json +import os +import time + +from fastapi import FastAPI, HTTPException, Request + +app = FastAPI() +keys = {} +for key_id, encoded in json.loads(os.environ["HOOK_KEYS_JSON"]).items(): + key = base64.b64decode(encoded, validate=True) + if not key_id or len(key) != 32 or base64.b64encode(key).decode("ascii") != encoded: + raise ValueError("Keys must be canonical base64 encodings of 32 bytes") + keys[key_id] = key +if not keys: + raise ValueError("Configure at least one signing key") +decisions = {} + + +def unique_object(pairs): + result = {} + for key, value in pairs: + if key in result: + raise ValueError("Duplicate JSON key") + result[key] = value + return result + + +@app.post("/hook") +async def hook(request: Request): + names = ["X-Redis-Webhook-Key-Id", "X-Redis-Webhook-Event-Id", + "X-Redis-Webhook-Timestamp", "X-Redis-Webhook-Signature"] + if any(len(request.headers.getlist(name)) != 1 for name in names): + raise HTTPException(401, "Invalid signature headers") + key_id, event_id, stamp, signature = [request.headers[name] for name in names] + key = keys.get(key_id) + try: + fresh = abs(time.time() - int(stamp)) <= 300 + except ValueError: + fresh = False + if not key or not event_id or not fresh: + raise HTTPException(401, "Invalid signing identity or timestamp") + body = bytearray() + async for chunk in request.stream(): + body.extend(chunk) + if len(body) > 8 * 1024 * 1024: + raise HTTPException(413, "Body too large") + expected = "v1=" + hmac.new( + key, f"{stamp}.{event_id}.".encode() + body, hashlib.sha256, + ).hexdigest() + if not signature.isascii() or not hmac.compare_digest(signature, expected): + raise HTTPException(401, "Invalid signature") + try: + payload = json.loads(body, object_pairs_hook=unique_object) + except (ValueError, UnicodeDecodeError): + raise HTTPException(400, "Invalid JSON") + if not isinstance(payload, dict) or payload.get("version") != "1" or payload.get("eventId") != event_id: + raise HTTPException(400, "Invalid envelope") + if payload.get("phase") not in {"before_input", "before_apply"}: + raise HTTPException(400, "Invalid phase") + + # ponytail: one process and at most 1000 decisions; use a durable shared + # decision store before adding replicas or policies with external effects. + now = time.time() + for identity, saved in list(decisions.items()): + if saved[2] <= now: + del decisions[identity] + identity = (key_id, event_id) + digest = hashlib.sha256(body).digest() + if identity in decisions: + saved_digest, decision, _ = decisions[identity] + if saved_digest != digest: + raise HTTPException(409, "Event content changed") + return decision + if len(decisions) >= 1000: + raise HTTPException(503, "Decision capacity reached") + decision = {"version": "1", "decision": "accept"} + if "[reject]" in json.dumps(payload, ensure_ascii=False): + decision.update(decision="reject", reasonCode="policy_denied") + decisions[identity] = (digest, decision, now + 7 * 24 * 60 * 60) + return decision +``` + +Install and run it with a certificate for the configured receiver hostname: + +```sh +uv venv +source .venv/bin/activate +uv pip install fastapi uvicorn +uvicorn receiver:app --host 0.0.0.0 --port 8443 \ + --ssl-certfile --ssl-keyfile +``` + +The example retains up to 1,000 decisions in one process for seven days. Restarting loses that cache. Use a durable shared store for production decisions and store the body fingerprint with each decision. Do not perform external side effects before saving an idempotent decision. + +Return HTTP `200` and `Content-Type: application/json` with a versioned decision. An empty body, bare success status, unknown fields, duplicate keys, or an invalid decision fails closed. Whole-request decisions are: + +```json +{"version":"1","decision":"accept"} +``` + +```json +{"version":"1","decision":"reject","reasonCode":"policy_denied"} +``` + +A document-input transform replaces its decoded text: + +```json +{"version":"1","decision":"transform","replacement":{"content":"Approved document text."}} +``` + +Session-input transforms can change `actorId`, `role`, `content`, `createdAt`, and `metadata`, while preserving session identity, owner, and placement. Manual-create transforms preserve record count, order, and IDs. They can change editable text, type, session, owner, namespace placement, topics, and attributes. Do not supply both `namespace` and `namespaceRef`. + +For `memory.create`, `session.builtin`, `session.custom`, and `document.chunk`, `decision: review` can decide each operation separately: + +```json +{ + "version":"1", + "decision":"review", + "operations":[ + {"operationId":"","decision":"accept"}, + {"operationId":"","decision":"transform","replacement":{"text":"Approved fact."}}, + {"operationId":"","decision":"reject","reasonCode":"policy_denied"} + ] +} +``` + +Return exactly one decision for every proposed operation; missing, duplicate, or unknown IDs invalidate the batch. Manual-create operation IDs are the supplied memory IDs. Generated candidates have separate operation IDs. Generated updates can transform editable fields; deletion can only be accepted or rejected. A receiver cannot add operations, change targets or kinds, or rewrite provenance. Native authorization, exclusions, namespace admission, and concurrency checks still apply. + +## Make input retries idempotent + +When selected, session append and manual memory creation require `idempotencyKey` containing 1–128 printable ASCII characters. In Python this is `idempotency_key`; TypeScript and REST use `idempotencyKey`. Document ingestion already requires this field. + +Save the complete original request, including timestamps, before sending it. After a lost response, reuse that request and key. Changing content under the same key conflicts. Saved acceptance and write receipts let a retry resume without repeating confirmed writes. A manual batch can still have per-record errors. + +To try the receiver's rejection marker, use a new memory ID and key with your authenticated owner. Save this synthetic request and repeat only the curl call to test an identical retry: + +```sh +export HOOK_TEST_MEMORY_ID="hook-test-$(openssl rand -hex 12)" +export HOOK_TEST_REQUEST_ID="hook-request-$(openssl rand -hex 12)" +jq -n --arg id "$HOOK_TEST_MEMORY_ID" --arg owner "$OWNER_ID" \ + --arg key "$HOOK_TEST_REQUEST_ID" \ + '{idempotencyKey:$key,memories:[{id:$id,ownerId:$owner,text:"[reject] synthetic webhook test"}]}' > hook-test.json +curl --fail-with-body --silent --show-error --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ + --header 'Content-Type: application/json' --data @hook-test.json \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory" | jq +``` + +With `memory.create` selected, inspect the per-record result and verify that the rejected record was not created. Use a new key for changed input; editing `hook-test.json` under the same key conflicts. + +The additional mutation paths and Summary View publication use fresh events rather than this input-replay contract. Retrying them reviews current state again; a lost response after a successful mutation can lead to another review. A callback's acceptance does not promise that its target will remain unchanged until persistence. + +## Handle failures and inspect status + +Callbacks have a five-second limit. Input callbacks have no automatic receiver retry. Interrupted undecided input can be resumed with the same original request and key; receiver failures remain terminal. Abandoned undecided input is cancelled after 60 seconds without a live lease. + +Session and document extraction output have at most three total callback attempts, with delays of 30 and 120 seconds within the job deadline. Saved candidates are resumed rather than regenerated. Explicit rejection filters candidates and permits progress; invalid responses or transport failures stop the current batch. Earlier batches may already be saved. + +For `summary_view.refresh`, a recorded rejection, invalid decision, or receiver failure stops that refresh's normal retries. Request a new refresh to generate a new proposal. Interrupted attempts can still be delivered again. Native generation or signing-key lookup failures have their own retry policy. + +Session summary and property hooks also apply to worker writes. A rejected summary is not saved and does not advance the worker's summary boundary; property hooks can also stop bookmark writes. + +| Response | Action | +|:---------|:-------| +| `503` with `/errors/ingestion-hook-retry` | Inspect `retryAt` and resume the same operation after that time. | +| `422` with `/errors/ingestion-hook-stopped` | Inspect the terminal state. Repeating the same stopped operation is not recovery. | +| `429` | Hook capacity is exhausted. Apply backoff and inspect pending work. | +| Bulk success with item errors | Check every record's result; HTTP success does not mean all records were applied. | + +Decision `state` and `applyState` are separate. Inspect `applied`, `filtered`, `conflicts`, attempts, safe errors, and retry times. A later failure or an incomplete completion record does not prove earlier writes were rolled back. There is no manual redrive endpoint. + +Use the Data Plane connection values from the [quickstart](/content/develop/ai/context-engine/agent-memory/quickstart.md). Set `HOOK_EVENT_ID` to inspect one known event, or omit it to list events. Add `USER_TOKEN` for delegated access: + +{{< multitabs id="hook-status" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python +import asyncio +import os + +from redis_agent_memory import AgentMemory + + +async def main(): + headers = {"X-Iris-User-Token": os.environ["USER_TOKEN"]} if os.environ.get("USER_TOKEN") else {} + async with AgentMemory( + os.environ["AGENT_MEMORY_URL"], store_id=os.environ["STORE_ID"], + api_key=os.environ["API_KEY"], + ) as memory: + if os.environ.get("HOOK_EVENT_ID"): + event = await memory.get_ingestion_hook_status_async( + event_id=os.environ["HOOK_EVENT_ID"], http_headers=headers, + ) + print(event.model_dump_json(by_alias=True)) + cursor = None + while True: + page = await memory.list_ingestion_hook_statuses_async( + limit=20, cursor=cursor, http_headers=headers, + ) + for status in page.statuses: + print(status.model_dump_json(by_alias=True)) + cursor = page.next_cursor + if not cursor: + break + + +asyncio.run(main()) +``` + +-tab-sep- + +```typescript +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function main() { + const { AGENT_MEMORY_URL, STORE_ID, API_KEY, USER_TOKEN, HOOK_EVENT_ID } = process.env; + if (!AGENT_MEMORY_URL || !STORE_ID || !API_KEY) { + throw new Error("Set AGENT_MEMORY_URL, STORE_ID, and API_KEY."); + } + const memory = new AgentMemory({ serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY }); + const headers: Record = USER_TOKEN ? { "X-Iris-User-Token": USER_TOKEN } : {}; + const options = { headers }; + if (HOOK_EVENT_ID) console.dir(await memory.getIngestionHookStatus(HOOK_EVENT_ID, options), { depth: null }); + let cursor: string | undefined; + do { + const page = await memory.listIngestionHookStatuses(20, cursor, options); + console.dir(page.statuses, { depth: null }); + cursor = page.nextCursor; + } while (cursor); +} + +main().catch((error) => { console.error(error instanceof Error ? error.message : "Request failed"); process.exitCode = 1; }); +``` + +-tab-sep- + +```sh +if [ -n "${HOOK_EVENT_ID:-}" ]; then + curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/ingestion-hooks/events/$HOOK_EVENT_ID" | jq +fi +cursor='' +while :; do + page=$(curl --fail-with-body --silent --show-error --get \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ + --data-urlencode 'limit=20' --data-urlencode "cursor=$cursor" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/ingestion-hooks/events") || exit 1 + printf '%s\n' "$page" | jq .statuses + cursor=$(printf '%s' "$page" | jq -r '.nextCursor // empty') + [ -n "$cursor" ] || break +done +``` + +{{< /multitabs >}} + +Lists return `statuses` and `nextCursor`, not `items` and `nextPageToken`. Continue when a page is empty but has a cursor; ownership filtering can produce that result. Keep the same store, credential, and list parameters when following a cursor. The maximum page size is 100. + +Private pending content expires within 24 hours or the earlier operation deadline. Compact status and write receipts remain for seven days after completion or expiry. Status omits private input, generated content, prompts, endpoint configuration, and keys. + +Each store permits 10,000 hook records and 64 MiB of reserved pending content. Encoded callback requests and responses are limited to 8 MiB; decoded reviewed content is limited to 1 MiB. Hooks on bulk deletion or moves reduce the request limit to four memory IDs when that mutation path is selected; otherwise the native limit is 100. Split larger selected mutations into smaller requests and inspect every result. diff --git a/content/develop/ai/context-engine/agent-memory/list-sessions.md b/content/develop/ai/context-engine/agent-memory/list-sessions.md index 9ad7553acd..13aaef1034 100644 --- a/content/develop/ai/context-engine/agent-memory/list-sessions.md +++ b/content/develop/ai/context-engine/agent-memory/list-sessions.md @@ -40,14 +40,16 @@ These examples list all pages for one owner, with the most recently updated sess import asyncio import os +import httpx from redis_agent_memory import AgentMemory async def main(): - async with AgentMemory( + headers = {"X-Iris-User-Token": os.environ["USER_TOKEN"]} if os.environ.get("USER_TOKEN") else {} + async with httpx.AsyncClient(headers=headers) as transport, AgentMemory( os.environ["AGENT_MEMORY_URL"], store_id=os.environ["STORE_ID"], - api_key=os.environ["API_KEY"], + api_key=os.environ["API_KEY"], async_client=transport, ) as agent_memory: page_token = None while True: @@ -70,14 +72,23 @@ asyncio.run(main()) ```typescript import { AgentMemory } from "@redis-iris/agent-memory"; +import { HTTPClient } from "@redis-iris/agent-memory/lib/http"; async function main() { const { AGENT_MEMORY_URL, STORE_ID, API_KEY, OWNER_ID } = process.env; if (!AGENT_MEMORY_URL || !STORE_ID || !API_KEY || !OWNER_ID) { throw new Error("Set AGENT_MEMORY_URL, STORE_ID, API_KEY, and OWNER_ID."); } + const httpClient = new HTTPClient(); + const userToken = process.env.USER_TOKEN; + if (userToken) { + httpClient.addHook("beforeRequest", (request) => { + request.headers.set("X-Iris-User-Token", userToken); + return request; + }); + } const agentMemory = new AgentMemory({ - serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY, + serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY, httpClient, }); let pageToken: string | undefined; do { @@ -89,7 +100,7 @@ async function main() { } while (pageToken); } -main().catch((error) => { console.error(error); process.exitCode = 1; }); +main().catch((error) => { console.error(error instanceof Error ? error.message : "Request failed"); process.exitCode = 1; }); ``` -tab-sep- @@ -99,6 +110,7 @@ page_token='' while :; do page=$(curl --fail-with-body --silent --show-error --get \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --data-urlencode "filterOwnerId=$OWNER_ID" \ --data-urlencode 'sortBy=updatedAt' --data-urlencode 'sortOrder=desc' \ --data-urlencode 'limit=20' --data-urlencode "pageToken=$page_token" \ diff --git a/content/develop/ai/context-engine/agent-memory/long-term-memory.md b/content/develop/ai/context-engine/agent-memory/long-term-memory.md index 312a5ee2b7..ed8d344274 100644 --- a/content/develop/ai/context-engine/agent-memory/long-term-memory.md +++ b/content/develop/ai/context-engine/agent-memory/long-term-memory.md @@ -89,6 +89,7 @@ Omit `text` to retrieve records by filters without a semantic query. For example curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data "$(jq -n --arg owner "$OWNER_ID" \ '{filter: {ownerId: {eq: $owner}}, sortBy: "updatedAt", sortOrder: "desc", limit: 10}')" \ @@ -103,6 +104,10 @@ Use the client and identifiers from the [quickstart](/content/develop/ai/context Top-level filters use `filterOp: "all"` (AND) by default; `"any"` uses OR. To restrict a query to a user, keep the owner condition mandatory. Do not use `"any"` with separate owner and namespace conditions when both must match. These retrieval filters do not replace application access checks. +Set `filter.unnamespaced: true` to match records with neither a legacy namespace label nor a namespace resource. It cannot be combined with `namespace` or `namespaceRef`, and remains mandatory even with `filterOp: "any"`. + +[MemoryETL](/content/develop/ai/context-engine/agent-memory/memory-etl.md) outputs are included in search by default. Set top-level `excludeDocumentManaged: true` to omit them. Returned `documentManaged` records cannot be overwritten through generic memory creation or PATCH requests; use the document-ingestion workflow to manage their content. + For custom fields, use `filter.attributes`, keyed by the field name, with the operators described in [`AttributeFilterClause`](/content/develop/ai/context-engine/agent-memory/api-reference.md#operation/SearchLongTermMemory). Combine it with `memoryType` to select the registered custom type. ## Custom memory types @@ -185,6 +190,7 @@ Search for the structured memory: curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data @- \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/search" <}} + +```python +import asyncio +import os + +from redis_agent_memory import AgentMemoryService + + +async def main(): + async with AgentMemoryService( + server_url=os.environ["AGENT_MEMORY_CONTROL_PLANE_URL"], + token=os.environ["AGENT_MEMORY_SERVICE_TOKEN"], + ) as service: + extractor = await service.create_extractor_async( + store_id=os.environ["STORE_ID"], + name="Travel guide facts", memory_type="document_fact", + source="documents", strategy="custom", + prompt="Extract useful travel facts. Keep each fact in one memory.", + ) + print(extractor.model_dump_json(by_alias=True)) + + +asyncio.run(main()) +``` + +-tab-sep- + +```typescript +import { AgentMemoryService } from "@redis-iris/agent-memory"; + +async function main() { + const { AGENT_MEMORY_CONTROL_PLANE_URL, AGENT_MEMORY_SERVICE_TOKEN, STORE_ID } = process.env; + if (!AGENT_MEMORY_CONTROL_PLANE_URL || !AGENT_MEMORY_SERVICE_TOKEN || !STORE_ID) { + throw new Error("Set the Control Plane connection values and STORE_ID."); + } + const service = new AgentMemoryService({ + serverURL: AGENT_MEMORY_CONTROL_PLANE_URL, token: AGENT_MEMORY_SERVICE_TOKEN, + }); + console.dir(await service.createExtractor(STORE_ID, { + name: "Travel guide facts", memoryType: "document_fact", + source: "documents", strategy: "custom", + prompt: "Extract useful travel facts. Keep each fact in one memory.", + }), { depth: null }); +} + +main().catch((error) => { console.error(error instanceof Error ? error.message : "Request failed"); process.exitCode = 1; }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error --request POST \ + --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ + --header 'Content-Type: application/json' \ + --data '{"name":"Travel guide facts","memoryType":"document_fact","source":"documents","strategy":"custom","prompt":"Extract useful travel facts. Keep each fact in one memory."}' \ + "$AGENT_MEMORY_CONTROL_PLANE_URL/v2/memory/stores/$STORE_ID/extractors" | jq +``` + +{{< /multitabs >}} + +Set `destinationNamespaceId` when creating an extractor to place its output in one namespace in the same store. Use a stable namespace ID, not a display name. Personal destinations must be compatible with the document owner; shared placement does not make the original document accessible to every reader of the extracted memories. + +List or read extractors before editing them. Updates and removal require the current `expectedRevision`; a stale revision returns `409`. An update can change the name, prompt, enabled flag, or destination. An empty destination clears it; omission keeps it. Accepted ingestions retain their extractor snapshot, so a later edit does not change an existing run. + +## Submit and track a document + +Create `travel-guide.md` containing: + +```text +Kyoto has vegetarian restaurants near its central railway station. +``` + +Only `text/plain` and `text/markdown` are accepted. Encode the UTF-8 bytes as base64 **once** before passing `content` to REST or an SDK. The SDK expects the encoded string, not a file object or raw text. + +Each submission requires `documentId`, `revision`, `content`, `mediaType`, `ownerId`, `extractorId`, and `idempotencyKey`. The document ID identifies a logical document. Its revision is immutable and case-sensitive; use a new revision when its content changes. Neither ID is the server-generated ingestion ID. + +The examples submit the file, poll for a terminal processing state, and list output memory IDs. Save the ingestion ID even if polling times out: + +{{< multitabs id="document-ingestion" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python +import asyncio +import base64 +import os +from pathlib import Path + +from redis_agent_memory import AgentMemory + + +async def main(): + headers = {"X-Iris-User-Token": os.environ["USER_TOKEN"]} if os.environ.get("USER_TOKEN") else {} + async with AgentMemory( + os.environ["AGENT_MEMORY_URL"], store_id=os.environ["STORE_ID"], + api_key=os.environ["API_KEY"], + ) as memory: + accepted = await memory.create_document_ingestion_async( + document_id="travel-guide", revision="1", + content=base64.b64encode(Path("travel-guide.md").read_bytes()).decode("ascii"), + media_type="text/markdown", owner_id=os.environ["OWNER_ID"], + extractor_id=os.environ["EXTRACTOR_ID"], + idempotency_key="travel-guide-revision-1", http_headers=headers, + ) + ingestion_id = accepted.result.ingestion_id + print("Ingestion ID:", ingestion_id) + for _ in range(60): + record = await memory.get_document_ingestion_async( + ingestion_id=ingestion_id, http_headers=headers, + ) + status = record.ingestion.status + if status.processing in {"succeeded", "partial", "failed"}: + print(status.model_dump_json(by_alias=True)) + break + await asyncio.sleep(2) + else: + raise TimeoutError(f"Continue polling ingestion {ingestion_id}") + + offset = 0 + while True: + page = await memory.list_document_ingestion_outputs_async( + ingestion_id=ingestion_id, limit=100, offset=offset, http_headers=headers, + ) + if not page.memory_ids: + break + print(page.memory_ids) + offset += len(page.memory_ids) + if status.processing != "succeeded": + raise RuntimeError(f"Inspect ingestion {ingestion_id}: {status.processing}") + + +asyncio.run(main()) +``` + +-tab-sep- + +```typescript +import { readFile } from "node:fs/promises"; +import { setTimeout as delay } from "node:timers/promises"; +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function main() { + const { AGENT_MEMORY_URL, STORE_ID, API_KEY, OWNER_ID, EXTRACTOR_ID, USER_TOKEN } = process.env; + if (!AGENT_MEMORY_URL || !STORE_ID || !API_KEY || !OWNER_ID || !EXTRACTOR_ID) { + throw new Error("Set the Data Plane values, OWNER_ID, and EXTRACTOR_ID."); + } + const memory = new AgentMemory({ serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY }); + const headers: Record = USER_TOKEN ? { "X-Iris-User-Token": USER_TOKEN } : {}; + const options = { headers }; + const accepted = await memory.createDocumentIngestion({ + documentId: "travel-guide", revision: "1", + content: (await readFile("travel-guide.md")).toString("base64"), + mediaType: "text/markdown", ownerId: OWNER_ID, extractorId: EXTRACTOR_ID, + idempotencyKey: "travel-guide-revision-1", + }, options); + const ingestionId = accepted.result.ingestionId; + console.log("Ingestion ID:", ingestionId); + let finished = false; + let processing: string | undefined; + for (let attempt = 0; attempt < 60; attempt++) { + const { ingestion } = await memory.getDocumentIngestion(ingestionId, options); + if (["succeeded", "partial", "failed"].includes(ingestion.status.processing)) { + processing = ingestion.status.processing; + console.dir(ingestion.status, { depth: null }); + finished = true; + break; + } + await delay(2000); + } + if (!finished) throw new Error(`Continue polling ingestion ${ingestionId}`); + let offset = 0; + for (;;) { + const page = await memory.listDocumentIngestionOutputs(ingestionId, 100, offset, options); + if (!page.memoryIds.length) break; + console.log(page.memoryIds); + offset += page.memoryIds.length; + } + if (processing !== "succeeded") throw new Error(`Inspect ingestion ${ingestionId}: ${processing}`); +} + +main().catch((error) => { console.error(error instanceof Error ? error.message : "Request failed"); process.exitCode = 1; }); +``` + +-tab-sep- + +```sh +jq -n --rawfile text travel-guide.md \ + --arg owner "$OWNER_ID" --arg extractor "$EXTRACTOR_ID" \ + '{documentId:"travel-guide",revision:"1",content:($text|@base64),mediaType:"text/markdown",ownerId:$owner,extractorId:$extractor,idempotencyKey:"travel-guide-revision-1"}' > ingestion.json + +response=$(curl --fail-with-body --silent --show-error --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ + --header 'Content-Type: application/json' --data @ingestion.json \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/document-ingestions") || exit 1 +INGESTION_ID=$(printf '%s' "$response" | jq -er .ingestionId) || exit 1 +printf 'Ingestion ID: %s\n' "$INGESTION_ID" + +finished=false +for attempt in $(seq 1 60); do + status=$(curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/document-ingestions/$INGESTION_ID") || exit 1 + state=$(printf '%s' "$status" | jq -r .ingestion.status.processing) + case "$state" in + succeeded|partial|failed) printf '%s\n' "$status" | jq; finished=true; break ;; + esac + sleep 2 +done +[ "$finished" = true ] || { printf 'Continue polling %s\n' "$INGESTION_ID"; exit 1; } + +offset=0 +while :; do + page=$(curl --fail-with-body --silent --show-error --get \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ + --data-urlencode 'limit=100' --data-urlencode "offset=$offset" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/document-ingestions/$INGESTION_ID/outputs") || exit 1 + count=$(printf '%s' "$page" | jq '.memoryIds | length') + [ "$count" -gt 0 ] || break + printf '%s\n' "$page" | jq .memoryIds + offset=$((offset + count)) +done +[ "$state" = succeeded ] || { printf 'Inspect ingestion %s: %s\n' "$INGESTION_ID" "$state"; exit 1; } +``` + +{{< /multitabs >}} + +REST returns `202 Accepted`; this confirms submission, not successful extraction. In the SDKs, the submission content is under `accepted.result`, while the status read returns `ingestion.status`. Output listing uses `offset` and `memoryIds`, not a page token or an `items` array. + +## Interpret results and recover + +| Processing state | Meaning and action | +|:-----------------|:-------------------| +| `queued` or `running` | Keep polling the saved ingestion ID. Do not create another submission to check progress. | +| `succeeded` | Processing finished. Inspect `createdCount` and `filteredCount`; success can produce no memories. | +| `partial` | Some chunks completed before a failure. Inspect `error`, counts, and available output IDs. Earlier writes remain. | +| `failed` | Inspect `error` and available outputs. Do not assume that every prior write was rolled back. | + +`completedChunks`, `totalChunks`, `extractorId`, and `extractorRevision` help identify what ran. `cleanup` is independent: `pending`, `deleted`, or `failed` describes temporary source cleanup. A successful extraction with failed cleanup needs operator attention; it is not a reason to submit the document again. Extracted memories and provenance can remain after source bytes are deleted. + +Reuse the same idempotency key and original request after a timeout or lost submission response. Changing the input under the same key conflicts. Reusing an immutable document revision with different content also conflicts. Use a new revision for changed content; do not change the bytes merely to force a retry of an existing run. + +The service bounds decoded bytes, estimated input tokens, chunks, output records, queued bytes, retries, execution time, and source/checkpoint retention. Inspect the deployment settings and `retentionDeadline`. Idempotency and status retention are bounded; retain your logical document IDs, revisions, ingestion IDs, and recovery decisions in your application. A new revision does not promise automatic removal of every memory from an older revision. + +Extracted records return `documentManaged: true`. They appear in ordinary long-term-memory searches unless you set `excludeDocumentManaged: true`. Their text and attributes are managed by document ingestion: generic overwrite and PATCH requests cannot replace them. Document chunk spans use UTF-8 byte offsets; do not apply those offsets to a character-indexed string. + +With [ingestion webhooks](/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md), submission and generated chunks can also be reviewed before they change data. Check hook decisions separately from ingestion processing. Receiver failure and explicit filtering have different outcomes. + +## Trace memories to their sources + +Provenance connects a memory to document or session inputs used during extraction. A `sourceId` identifies one revisioned origin in the store; an `ingestionId` identifies a particular document run. Session evidence lists input event IDs. Document evidence identifies ingestions, chunks, and optional source spans. These describe the input supplied to extraction, not a guarantee that each sentence in the resulting memory is a quoted fact. + +| Request | Python method | TypeScript method | +|:--------|:--------------|:------------------| +| Memory → sources | `list_memory_source_links_async` | `listMemorySourceLinks` | +| Memory/source → more evidence | `list_memory_source_evidence_async` | `listMemorySourceEvidence` | +| Source → memories for an owner | `list_source_memories_async` | `listSourceMemories` | + +Set `MEMORY_ID` to one returned output ID. These examples follow both source pages and each source's independent evidence pages, then list memories associated with that source for `OWNER_ID`. Add the Python snippet inside the ingestion example's `async with` block, or the TypeScript snippet inside `main`, after output listing. They reuse the same client and request headers. + +{{< multitabs id="document-provenance" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + source_page_token = None + while True: + links = await memory.list_memory_source_links_async( + memory_id=os.environ["MEMORY_ID"], limit=100, + page_token=source_page_token, http_headers=headers, + ) + for source in links.items: + print(source.model_dump_json(by_alias=True)) + evidence_token = source.evidence_next_page_token + while evidence_token: + evidence = await memory.list_memory_source_evidence_async( + memory_id=os.environ["MEMORY_ID"], source_id=source.source_id, + limit=100, page_token=evidence_token, http_headers=headers, + ) + print(evidence.source.model_dump_json(by_alias=True)) + evidence_token = evidence.source.evidence_next_page_token + reverse_token = None + while True: + related = await memory.list_source_memories_async( + source_id=source.source_id, owner_id=os.environ["OWNER_ID"], + limit=100, page_token=reverse_token, http_headers=headers, + ) + print(related.model_dump_json(by_alias=True)) + reverse_token = related.next_page_token + if not reverse_token: + break + source_page_token = links.next_page_token + if not source_page_token: + break +``` + +-tab-sep- + +```typescript + const memoryId = process.env.MEMORY_ID; + if (!memoryId) throw new Error("Set MEMORY_ID to an output memory ID."); + let sourcePageToken: string | undefined; + do { + const links = await memory.listMemorySourceLinks(memoryId, 100, sourcePageToken, options); + for (const source of links.items) { + console.dir(source, { depth: null }); + let evidenceToken = source.evidenceNextPageToken; + while (evidenceToken) { + const evidence = await memory.listMemorySourceEvidence( + memoryId, source.sourceId, 100, evidenceToken, options, + ); + console.dir(evidence.source, { depth: null }); + evidenceToken = evidence.source.evidenceNextPageToken; + } + let reverseToken: string | undefined; + do { + const related = await memory.listSourceMemories( + source.sourceId, OWNER_ID, 100, reverseToken, options, + ); + console.dir(related.items, { depth: null }); + reverseToken = related.nextPageToken; + } while (reverseToken); + } + sourcePageToken = links.nextPageToken; + } while (sourcePageToken); +``` + +-tab-sep- + +```sh +source_token='' +while :; do + links=$(curl --fail-with-body --silent --show-error --get \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ + --data-urlencode 'limit=100' --data-urlencode "pageToken=$source_token" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/$MEMORY_ID/sources") || exit 1 + printf '%s\n' "$links" | jq .items + printf '%s' "$links" | jq -c '.items[]' | while IFS= read -r source; do + source_id=$(printf '%s' "$source" | jq -r .sourceId) + evidence_token=$(printf '%s' "$source" | jq -r '.evidenceNextPageToken // empty') + while [ -n "$evidence_token" ]; do + evidence=$(curl --fail-with-body --silent --show-error --get \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ + --data-urlencode 'limit=100' --data-urlencode "pageToken=$evidence_token" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/$MEMORY_ID/sources/$source_id/evidence") || exit 1 + printf '%s\n' "$evidence" | jq .source + evidence_token=$(printf '%s' "$evidence" | jq -r '.source.evidenceNextPageToken // empty') + done + reverse_token='' + while :; do + related=$(curl --fail-with-body --silent --show-error --get \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ + --data-urlencode "ownerId=$OWNER_ID" --data-urlencode 'limit=100' \ + --data-urlencode "pageToken=$reverse_token" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/sources/$source_id/memories") || exit 1 + printf '%s\n' "$related" | jq .items + reverse_token=$(printf '%s' "$related" | jq -r '.nextPageToken // empty') + [ -n "$reverse_token" ] || break + done + done || exit 1 + source_token=$(printf '%s' "$links" | jq -r '.nextPageToken // empty') + [ -n "$source_token" ] || break +done +``` + +{{< /multitabs >}} + +The evidence response wraps `source`; it does not return a top-level `items` list. The source object contains `evidence` for documents or `sessionEvidence` for sessions. Newer evidence can also include `evidenceId`, `schemaVersion`, `capturedAt`, and `producer`. Handle their absence on legacy data. `capturedAt` is when extraction input was prepared, not when the original event occurred. A retried delivery retains its evidence identity. + +For reverse lookup, use `GET /v1/stores/{storeId}/sources/{sourceId}/memories` with `ownerId`, or the corresponding SDK method. Follow its `nextPageToken` independently. Keep every token with the exact query and endpoint that issued it. Tokens expire; restart a query when one is invalid, and URL-encode tokens in REST calls. + +A source link does not grant access to the original document, session, or another owner's memories. The service checks source and record access separately; sharing an extracted memory does not share the private document it came from. diff --git a/content/develop/ai/context-engine/agent-memory/namespace-routing.md b/content/develop/ai/context-engine/agent-memory/namespace-routing.md index 68b3b26acb..c5b10f4d5d 100644 --- a/content/develop/ai/context-engine/agent-memory/namespace-routing.md +++ b/content/develop/ai/context-engine/agent-memory/namespace-routing.md @@ -43,14 +43,16 @@ This example accepts text containing `travel` into that namespace. The regular e import asyncio import os +import httpx from redis_agent_memory import AgentMemory async def main(): - async with AgentMemory( + headers = {"X-Iris-User-Token": os.environ["USER_TOKEN"]} if os.environ.get("USER_TOKEN") else {} + async with httpx.AsyncClient(headers=headers) as transport, AgentMemory( os.environ["AGENT_MEMORY_URL"], store_id=os.environ["STORE_ID"], - api_key=os.environ["API_KEY"], + api_key=os.environ["API_KEY"], async_client=transport, ) as agent_memory: current = await agent_memory.get_namespace_routing_async() print(current.model_dump_json(by_alias=True, indent=2)) @@ -77,14 +79,23 @@ asyncio.run(main()) ```typescript import { AgentMemory } from "@redis-iris/agent-memory"; +import { HTTPClient } from "@redis-iris/agent-memory/lib/http"; async function main() { const { AGENT_MEMORY_URL, STORE_ID, API_KEY, SHARED_NAMESPACE_ID } = process.env; if (!AGENT_MEMORY_URL || !STORE_ID || !API_KEY || !SHARED_NAMESPACE_ID) { throw new Error("Set AGENT_MEMORY_URL, STORE_ID, API_KEY, and SHARED_NAMESPACE_ID."); } + const httpClient = new HTTPClient(); + const userToken = process.env.USER_TOKEN; + if (userToken) { + httpClient.addHook("beforeRequest", (request) => { + request.headers.set("X-Iris-User-Token", userToken); + return request; + }); + } const agentMemory = new AgentMemory({ - serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY, + serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY, httpClient, }); console.dir(await agentMemory.getNamespaceRouting(), { depth: null }); const result = await agentMemory.putNamespaceRouting({ @@ -103,7 +114,7 @@ async function main() { console.dir(result, { depth: null }); } -main().catch((error) => { console.error(error); process.exitCode = 1; }); +main().catch((error) => { console.error(error instanceof Error ? error.message : "Request failed"); process.exitCode = 1; }); ``` -tab-sep- @@ -111,11 +122,13 @@ main().catch((error) => { console.error(error); process.exitCode = 1; }); ```sh curl --fail-with-body --silent --show-error \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespace-routing" | jq curl --fail-with-body --silent --show-error \ --request PUT \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data "$(jq -n --arg ns "$SHARED_NAMESPACE_ID" \ '{policy: {enabled: true, rules: [{id: "travel", enabled: true, @@ -157,6 +170,7 @@ To evaluate the policy for an existing shared memory, omit the destination from curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data "$(jq -n --arg id "$MEMORY_ID" '{memoryIds: [$id]}')" \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/move" | jq diff --git a/content/develop/ai/context-engine/agent-memory/namespaces.md b/content/develop/ai/context-engine/agent-memory/namespaces.md index c03f373c54..3fe316b2ba 100644 --- a/content/develop/ai/context-engine/agent-memory/namespaces.md +++ b/content/develop/ai/context-engine/agent-memory/namespaces.md @@ -79,6 +79,7 @@ Use the client from the [quickstart](/content/develop/ai/context-engine/agent-me ```sh curl --fail-with-body --silent --show-error --get \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --data-urlencode 'scope=SHARED' --data-urlencode 'limit=20' \ --data-urlencode 'sortBy=name' --data-urlencode 'sortOrder=asc' \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespaces" | jq @@ -145,6 +146,7 @@ Create a personal namespace for the user's travel memories. Run this request onc NAMESPACE_ID=$(curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data "$(jq -n --arg owner "$OWNER_ID" \ '{name: "travel", scope: "PERSONAL", ownerId: $owner}')" \ @@ -214,6 +216,7 @@ Run this request once and save the returned ID: SHARED_NAMESPACE_ID=$(curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data '{"name":"team-travel","scope":"SHARED"}' \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespaces" | jq -er '.namespace.namespaceId') @@ -261,6 +264,7 @@ After writing memories to the shared namespace, search with its namespace ID. Th curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data "$(jq -n --arg ns "$SHARED_NAMESPACE_ID" \ '{text: "What travel requirements has the team discussed?", filter: {namespaceRef: {eq: $ns}}, limit: 5}')" \ diff --git a/content/develop/ai/context-engine/agent-memory/quickstart.md b/content/develop/ai/context-engine/agent-memory/quickstart.md index 62c901ada4..da9d42da3f 100644 --- a/content/develop/ai/context-engine/agent-memory/quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/quickstart.md @@ -78,6 +78,8 @@ export OWNER_ID='quickstart-user' The endpoint must include `https://`. Keep the API key out of source control, application logs, and other unsecured locations. +For a self-managed service with ownership enforcement enabled, [configure delegation](/content/operate/iris/agent-memory/self-managed/authentication.md#delegate-a-user-with-an-agent-key), then also export `USER_TOKEN` and set `OWNER_ID` to that token's verified user ID. The examples use these optional values. Leave `USER_TOKEN` unset for Cloud store credentials. Without `OWNER_ID`, the SDK examples use `quickstart-user` for this exercise. + ## Install the SDK {{< multitabs id="installation" tab1="Python" tab2="TypeScript" tab3="curl" >}} @@ -115,6 +117,7 @@ Create `quickstart.py` or `quickstart.ts` with the complete example for your lan {{< clients-example set="agent_memory_sdk" step="connect_health_check" footer="hide" dft_tab_name="curl" runnable="false" try_it="false" >}} curl --fail-with-body --silent --show-error \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/health" | jq {{< /clients-example >}} @@ -134,6 +137,7 @@ export EVENT_CREATED_AT="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data @- \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/events" <}} @@ -174,6 +179,7 @@ Wait at least one minute. Replace your SDK file with the complete search example curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data @- \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/search" <}} + +```python +import asyncio +import os + +from redis_agent_memory import AgentMemory + + +async def main(): + headers = {"X-Iris-User-Token": os.environ["USER_TOKEN"]} + async with AgentMemory( + os.environ["AGENT_MEMORY_URL"], store_id=os.environ["STORE_ID"], + api_key=os.environ["API_KEY"], + ) as memory: + view = await memory.create_summary_view_async( + name="Travel profile", source="long_term", group_by=[], + filter_={"owner_id": {"eq": os.environ["OWNER_ID"]}}, + output={"format_": "text", "instructions": "Summarize the user's travel preferences."}, + refresh_policy={"mode": "manual"}, http_headers=headers, + ) + print("View ID:", view.id) + accepted = await memory.refresh_summary_view_async( + view_id=view.id, target="full", http_headers=headers, + ) + print(accepted.model_dump_json(by_alias=True)) + for _ in range(60): + result = await memory.get_summary_view_result_async( + view_id=view.id, include_source_memory_ids=True, http_headers=headers, + ) + if result.status == "failed": + raise RuntimeError(f"Refresh failed: {result.error_code}") + if result.status == "empty": + print("No matching source memories.") + return + if result.status == "ready" and not result.stale: + if result.value is None: + raise RuntimeError("Ready result omitted its value") + print(result.model_dump_json(by_alias=True)) + return + await asyncio.sleep(2) + raise TimeoutError(f"Continue polling view {view.id}") + + +asyncio.run(main()) +``` + +-tab-sep- + +```typescript +import { setTimeout as delay } from "node:timers/promises"; +import { AgentMemory } from "@redis-iris/agent-memory"; + +async function main() { + const { AGENT_MEMORY_URL, STORE_ID, API_KEY, USER_TOKEN, OWNER_ID } = process.env; + if (!AGENT_MEMORY_URL || !STORE_ID || !API_KEY || !USER_TOKEN || !OWNER_ID) { + throw new Error("Set the connection values, USER_TOKEN, and OWNER_ID."); + } + const memory = new AgentMemory({ serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY }); + const options = { headers: { "X-Iris-User-Token": USER_TOKEN } }; + const view = await memory.createSummaryView({ + name: "Travel profile", source: "long_term", groupBy: [], + filter: { ownerId: { eq: OWNER_ID } }, + output: { format: "text", instructions: "Summarize the user's travel preferences." }, + refreshPolicy: { mode: "manual" }, + }, options); + console.log("View ID:", view.id); + console.dir(await memory.refreshSummaryView(view.id, { target: "full" }, options)); + for (let attempt = 0; attempt < 60; attempt++) { + const result = await memory.getSummaryViewResult( + view.id, undefined, undefined, undefined, undefined, true, options, + ); + if (result.status === "failed") throw new Error(`Refresh failed: ${result.errorCode}`); + if (result.status === "empty") { + console.log("No matching source memories."); + return; + } + if (result.status === "ready" && !result.stale) { + if (!result.value) throw new Error("Ready result omitted its value"); + console.dir(result, { depth: null }); + return; + } + await delay(2000); + } + throw new Error(`Continue polling view ${view.id}`); +} + +main().catch((error) => { console.error(error instanceof Error ? error.message : "Request failed"); process.exitCode = 1; }); +``` + +-tab-sep- + +```sh +definition=$(jq -n --arg owner "$OWNER_ID" \ + '{name:"Travel profile",source:"long_term",groupBy:[],filter:{ownerId:{eq:$owner}},output:{format:"text",instructions:"Summarize travel preferences."},refreshPolicy:{mode:"manual"}}') +view=$(curl --fail-with-body --silent --show-error --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: $USER_TOKEN" \ + --header 'Content-Type: application/json' --data "$definition" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/summary-views") || exit 1 +VIEW_ID=$(printf '%s' "$view" | jq -er .id) || exit 1 +printf 'View ID: %s\n' "$VIEW_ID" + +curl --fail-with-body --silent --show-error --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: $USER_TOKEN" \ + --header 'Content-Type: application/json' --data '{"target":"full"}' \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/summary-views/$VIEW_ID/refresh" | jq + +for attempt in $(seq 1 60); do + result=$(curl --fail-with-body --silent --show-error --get \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: $USER_TOKEN" \ + --data-urlencode 'includeSourceMemoryIds=true' \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/summary-views/$VIEW_ID/result") || exit 1 + state=$(printf '%s' "$result" | jq -r .status) + case "$state" in + failed) printf '%s\n' "$result" | jq; exit 1 ;; + empty) printf 'No matching source memories.\n'; exit 0 ;; + ready) + if printf '%s' "$result" | jq -e '.stale == false and .value != null' >/dev/null; then + printf '%s\n' "$result" | jq + exit 0 + fi ;; + esac + sleep 2 +done +printf 'Continue polling view %s\n' "$VIEW_ID" +exit 1 +``` + +{{< /multitabs >}} + +Text output uses `value.text`; structured output uses `value.data`. Python output configuration uses `format_` and `schema_`, serialized as `format` and `schema`. Do not assume every result has a `value`. + +### Handle asynchronous result states + +| Status | Application behavior | +|:-------|:---------------------| +| `cold` | No result is materialized for this scope. A grouped scope may be outside the active partition set. | +| `pending` | Work is queued, or unsafe prior output is withheld while repair is pending. | +| `ready` | A value is available. Check `stale` and coverage fields. | +| `empty` | No sources match. There is no generated value. | +| `refreshing` | New work is running. A last good value can remain. | +| `failed` | Inspect `errorCode`. A last good value may remain, or `value` may be absent. | + +`stale` is independent of status. It compares the result with the current definition and source changes. Decide whether to use a last good stale value, wait, or fall back to direct recall. Do not turn an absent value into an empty summary. + +Result reads validate contributing source records. Deleted, changed, or newly inaccessible sources can cause old output to be withheld and repair to be requested. Repair is asynchronous; an old value need not remain available. + +## Filter and group sources + +Use `filter` for owners, sessions, stable namespace IDs, topics, memory types, creation times, and typed attributes. `filterOp` defaults to `all`; `any` combines optional filters with OR. Ownership, exact partition scope, and time-window restrictions apply separately. Legacy `namespace` labels are unsupported for Summary Views; use `namespaceRef`. + +`groupBy` accepts up to four distinct fields: `ownerId`, `sessionId`, `namespaceRef`, and `memoryType`. A result read or `target: partition` refresh must supply exactly those fields. Missing or extra scope fields are invalid. `groupBy: []` creates a singleton; omit all scope fields when reading it. + +For `groupBy: ["ownerId", "namespaceRef"]`, use this partition refresh body: + +```json +{ + "target": "partition", + "scope": {"ownerId":"","namespaceRef":""} +} +``` + +Send the same scope as query parameters on result reads. Here `namespaceRef` is an ID string, not the `{"namespaceId":"..."}` object used to place a memory. `target: full` reconciles current and previous partitions. + +`evictionPolicy.maxPartitions` bounds grouped results within the deployment limit. Grouped views use the operator's partition limit when the policy is omitted; singleton views omit it. Matching source mutations make a scope recent; reads do not. Evicted scopes can return `cold`. `timeWindowSeconds` selects sources by creation time in a rolling window; it does not change source retention. + +## Choose structured output and continuous refresh + +For structured output, replace `output` with: + +```json +{ + "format": "structured", + "instructions": "Summarize only preferences supported by the selected memories.", + "schema": { + "fields": [ + {"name":"cities","type":"list[str]","description":"Cities the user wants to visit"}, + {"name":"vegetarian","type":"bool","description":"Whether the user has stated a vegetarian diet"} + ] + } +} +``` + +The schema accepts 1–50 fields of type `str`, `int`, `float`, `bool`, `list[str]`, `list[float]`, or `object`. Generated fields are optional. Arbitrary JSON Schema, required-field declarations, and nested object schemas are unsupported. Text output must omit `schema`; structured output requires it. Invalid generated data is reported as `OUTPUT_VALIDATION_FAILED`. + +For continuous refresh, use: + +```json +{"mode":"continuous","debounceSeconds":5,"reconcileIntervalSeconds":60} +``` + +The reconcile interval is required and must fit deployment limits. Debounce coalesces mutations; reconciliation also handles changes such as a moving time window. Manual mode omits both interval fields. Both modes run asynchronously within queue, source, generation, and concurrency limits. + +## Check coverage and manage definitions + +Inspect `sourceMemoryCount` and `summarizedMemoryCount`. Selection can omit whole records (`sourcesOmitted`) or clip included content (`sourceContentTruncated`). `truncated` combines both. Equal record counts do not prove all source text was included. Concurrent writes can change the source set while counts are measured. + +Request `includeSourceMemoryIds=true` for the selected source IDs. These identify contributing records, not every match. `computedAt`, `sourceUpdatedAt`, and `definitionVersion` help assess freshness. + +| Action | Python method | TypeScript method | REST | +|:-------|:--------------|:------------------|:-----| +| List definitions | `list_summary_views_async` | `listSummaryViews` | `GET /summary-views` | +| Read a definition | `get_summary_view_async` | `getSummaryView` | `GET /summary-views/{viewId}` | +| Update a definition | `update_summary_view_async` | `updateSummaryView` | `PATCH /summary-views/{viewId}` | +| Delete a definition | `delete_summary_view_async` | `deleteSummaryView` | `DELETE /summary-views/{viewId}` | + +REST paths are relative to `/v1/stores/{storeId}`. Lists return `items` and optional `nextPageToken`; pass it as `pageToken` with the same query. Definition states (`active`, `disabled`, `deleting`) are separate from result status. + +Name and description edits do not trigger generation. Material edits increment `definitionVersion`; a `groupBy` change also replaces the partition layout. `clearFilter: true` and `clearTimeWindow: true` remove those settings; do not also supply replacements. Owner, visibility, and source are immutable. Set `state: disabled` to disable a view; re-enabling requests refresh work. + +Deletion returns `202` while cleanup runs. Repeated deletion is idempotent; the definition becomes `deleting` before schedules and results are removed. Source memories remain. + +For quota failures, inspect the API error and deployment limits. For generation or source-access failures, keep the view ID and address the reported error instead of creating duplicates. [Ingestion webhooks](/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md) can reject generated content before publication. diff --git a/content/operate/iris/agent-memory/self-managed/authentication.md b/content/operate/iris/agent-memory/self-managed/authentication.md index ea3051b91a..b7820a3560 100644 --- a/content/operate/iris/agent-memory/self-managed/authentication.md +++ b/content/operate/iris/agent-memory/self-managed/authentication.md @@ -16,7 +16,7 @@ aliases: Self-managed Redis Agent Memory uses separate authentication models for the Control Plane and Data Plane. -The Control Plane uses an admin token for management endpoints. The Data Plane checks each agent +The Control Plane uses an admin token for management endpoints by default. It also accepts operator tokens when OpenID Connect (OIDC) authentication is configured. The Data Plane checks each agent key with the Identity Service, using the Data Plane service credential, and enforces store-level grants. @@ -24,7 +24,7 @@ grants. | Credential | Who presents it to whom | Secret / key | Auto-generated? | What it unlocks | | --- | --- | --- | --- | --- | -| Control Plane admin token | Operator → Control Plane (`:9100`) | `redis-agent-memory-controlplane-admin-token` / `token` | Yes (`controlplane.adminToken.autoGenerate`) | Store management: `/v1/stores`, `/v1/detectors` | +| Control Plane admin token | Operator → Control Plane (`:9100`) | `redis-agent-memory-controlplane-admin-token` / `token` | Yes (`controlplane.adminToken.autoGenerate`) | Store and extractor management through the `/v1` admin API and supported `/v2/memory` routes | | Control Plane internal token | Identity Service → Control Plane | `memory-internal-token` / `token` in the walkthrough | You provision it for both releases | Checking that the store in a grant exists. Set `controlplane.internalToken.existingSecret` on Memory and `productValidation.products.memory.credential.existingSecret` on Identity Service. | | Identity Service control token | Operator → Identity Service (`:9200`) | `ids-control-token` / `token` in the walkthrough | Supplied through `control.existingSecret` on Identity Service; that chart can also generate one | Creating, listing, updating, rotating and revoking agent keys | | Data Plane service credential | Data Plane → Identity Service | `memory-dp-credential` / `token` in the walkthrough | You provision it for both releases | Checking agent keys for `memory`. Set `identityService.credential.existingSecret` on Memory and register it in `runtime.serviceCredentials[]` on Identity Service. | @@ -37,7 +37,7 @@ Secret names and keys must agree between releases. See ## Control Plane admin token -Control Plane management endpoints require: +With the default configuration, Control Plane management endpoints require: ```http Authorization: Bearer @@ -87,6 +87,8 @@ Use auth-disabled mode for development and testing only: ```yaml profile: dev +ownership: + enabled: false auth: method: none ``` @@ -150,69 +152,162 @@ Operation mapping: ## Server-side ownership enforcement -Store grants let a caller use a store. Ownership enforcement adds a check within that store: a caller can access their own personal records and shared records. This option is available for on-prem deployments and is disabled by default. +Store grants authorize access to a store. Ownership enforcement adds a check within that store: ordinary callers can access their own personal records and shared records. -With enforcement disabled, `ownerId` is client-supplied information, not an access check. Your application must authenticate the user and restrict every request to that user's personal memories or shared memories they can access. For searches and session lists, derive the owner filter from the authenticated user. For direct-ID requests, check access before making the request. See [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). +Release 0.10.2 enables ownership enforcement by default in the on-prem server, worker, and chart. The Cloud runtime has a separate default and credential model. Your application remains responsible for end-user authorization when using Cloud store credentials; Cloud account users are not application end users. Customers cannot configure this on-prem setting or a customer identity provider through a Cloud account setting. -### Match stored owners to authenticated users +| Setting | Personal access | Shared access | +| --- | --- | --- | +| `ownership.enabled: false` | Your trusted backend must authorize every read and write, including direct-ID requests. Owner fields and filters do not authenticate the user. | Your backend checks which shared namespaces the user may access. | +| `ownership.enabled: true` | The server compares the stored owner with the authenticated user ID. Search and list restrictions apply in addition to client filters. | Callers with the required store permission can access shared records. Shared scope does not enforce team membership. | -Use one stable user ID across your application and its identity provider (IdP). It must meet the identifier format in the [API reference](/content/develop/ai/context-engine/agent-memory/api-reference.md). Enforcement compares stored owners with the authenticated User ID, including case. It does not take the current user's identity from an `ownerId` field in the request. +Keep application-specific checks in your backend in both modes. Use separate stores and grants if teams need separate server-enforced access. Worker credentials are for background services; do not give them to applications. -The source of that authenticated ID depends on the credential: +### Match stored owners to authenticated users -| Credential | Source of the authenticated User ID | -| --- | --- | -| Agent key with a user binding | The user ID associated with the key. The client cannot change it by sending a different owner ID. | -| Agent key without a user binding | No User ID. With enforcement enabled, it can access shared records but cannot access personal records or create a record for a named owner. | -| OpenID Connect (OIDC) token | The verified token claim selected by `auth.oidc.user_id_claim`, which defaults to `sub`. Claim mapping alone does not grant store access. | +Use the same stable user ID in stored records and the verified user token. Matching is exact, including case. IDs must meet the format in the [API reference](/content/develop/ai/context-engine/agent-memory/api-reference.md); an email address or an identity-provider subject containing punctuation may need a separate compatible claim. Changing a claim mapping does not rewrite stored owners. -There are two limits to the current identity setup: +The first session event's `actorId` becomes the session owner. Later events in an accessible session can use an assistant's actor ID without changing ownership. Directly created memories and personal namespace roots use the `ownerId` supplied by your application. Session extraction inherits the session owner. See [Identify users and conversations](/content/develop/ai/context-engine/agent-memory/developer-guide.md#identify-users-and-conversations) for client and server fields. -* The Identity Service's `POST /v1/api-keys` endpoint does not accept a user binding or copy the creator's User ID into the key. The key created in the [deployment walkthrough](/content/operate/iris/agent-memory/self-managed/deploy.md#7-mint-an-agent-key) is unbound. Configuring an IdP claim does not bind that key to a user. -* Direct OIDC authentication in the Redis Agent Memory Data Plane is a preview. It validates tokens but does not yet resolve the subject's store grants. A valid token alone therefore receives `403 Forbidden` for store operations. +With enforcement on: -Before enabling enforcement for personal memories, confirm that your deployment can supply an authenticated User ID. The standard key-minting walkthrough alone does not meet this requirement. +- A new personal owner, or an owner update, must match the authenticated user ID. A mismatch returns `403 Forbidden`, including when the record would enter a shared namespace. +- Another user's personal record is hidden from direct-ID reads and mutations with `404 Not Found`. Search and list filters cannot expand access. +- A personal namespace child inherits its root's owner. +- A caller without a user ID can access shared records within its grants, but cannot claim a named personal owner. Ownerless private records are hidden. -For example, if the authenticated User ID is `user-42`: +Ordinary agent keys created by `POST /v1/api-keys` do not contain a user binding. The API does not accept one or copy the key creator's identity. Use delegation for personal access. Configuring an OpenID Connect (OIDC) claim alone does not bind a key. -| Operation | Required owner value | -| --- | --- | -| Start a session | Set the first event's `actorId` to `user-42`. The server copies it to the session's `ownerId`; the event request has no separate owner field. | -| Add to an accessible session | You can use another `actorId`, such as `travel-agent`, for an assistant message. This does not change the session owner. | -| Create a long-term memory directly | Set `ownerId` to `user-42`. This applies even when placing the record in a shared namespace. | -| Create a personal namespace root | Set `ownerId` to `user-42`. Child namespaces inherit the root owner. | +An unbound key can pass store health checks and access allowed shared data while personal creation returns `403`. A successful health check does not establish a user identity. -Changing a long-term memory's owner also requires the new `ownerId` to match the authenticated User ID. An attempt to assign another user as owner returns `403 Forbidden`. +### Delegate a user with an agent key -### Enable ownership enforcement +Delegation lets an application use its agent key for store permissions and a user's access token for personal identity. This path is supported in on-prem release 0.10.2. It does not add the user's store grants to the key. -For an existing store, first confirm that its namespace placement index migration is complete. Compare the owners of existing personal sessions, memories, and namespace roots with the authenticated user IDs your deployment supplies. The setting does not rewrite stored owners. A mismatch, or a missing owner, makes those personal records inaccessible to ordinary callers after enforcement is enabled. +1. Configure your identity provider to issue signed access tokens with an issuer, a compatible user ID claim, and the intended audience. Obtain the user's token through your application's login process. +1. Configure the Data Plane to trust that issuer and its JSON Web Key Set (JWKS). Use the same tenant as the agent key and store. +1. Enable delegation and choose an audience distinct from direct OIDC sign-in. +1. Send both credentials on each request for that user. -In chart-rendered Helm values, set: +For chart-rendered configuration, merge this fragment into your existing Helm values. Replace the issuer, JWKS URL, and audience values with those configured by your identity provider: ```yaml +tenantId: default +auth: + oidc: + audience: api://memory-direct memory: ownership: enabled: true + auth: + oidc: + enabled: true + issuer: https:/// + jwks_uri: https:/// + user_id_claim: sub + allow_azp_fallback: false + delegation: + enabled: true + audience: + - api://memory-delegation ``` -In a bring-your-own Data Plane configuration file, use the same setting at the root: +The chart supplies the static tenant from `tenantId`. For a bring-your-own configuration file, place `ownership` and `auth` at the root, set `auth.oidc.static_tenant_id` to that same tenant, and set `auth.oidc.audience` to the direct-sign-in audience as a list. Do not also configure `tenant_id_claim` for a single-tenant deployment. -```yaml -ownership: - enabled: true +The server verifies the token's signature, issuer, audience, expiry, tenant, and user claim. JWKS verification does not decrypt a token or exchange it for another credential. An application-issued signed JSON Web Token (JWT) works only when the deployment trusts its issuer and verification keys. + +Delegation defaults to off and requires OIDC to be enabled. Its audience must not overlap the direct OIDC audience; keep `allow_azp_fallback` off with a separate delegation audience. Without a separate audience, the same user token can also be presented as a direct sign-in. + +Direct OIDC sign-in remains a preview in this release: it validates the token but does not resolve the subject's store grants. A valid token alone receives `403` for store operations. Use the agent key and delegated token together for personal access. + +Set `USER_TOKEN` to the access token for the current request's user, and `OWNER_ID` to that token's verified user ID. Use the connection values from the [quickstart](/content/develop/ai/context-engine/agent-memory/quickstart.md). These examples list that user's session metadata: + +{{< multitabs id="delegated-user" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python +import asyncio +import os + +import httpx +from redis_agent_memory import AgentMemory + + +async def main(): + async with httpx.AsyncClient( + headers={"X-Iris-User-Token": os.environ["USER_TOKEN"]}, + ) as transport: + async with AgentMemory( + server_url=os.environ["AGENT_MEMORY_URL"], + store_id=os.environ["STORE_ID"], + api_key=os.environ["API_KEY"], + async_client=transport, + ) as memory: + page = await memory.list_session_summaries_async( + filter_owner_id=os.environ["OWNER_ID"], limit=20, + ) + print(page.model_dump_json(by_alias=True)) + + +asyncio.run(main()) +``` + +-tab-sep- + +```typescript +import { AgentMemory } from "@redis-iris/agent-memory"; +import { HTTPClient } from "@redis-iris/agent-memory/lib/http"; + +async function main() { + const { AGENT_MEMORY_URL, STORE_ID, API_KEY, USER_TOKEN, OWNER_ID } = process.env; + if (!AGENT_MEMORY_URL || !STORE_ID || !API_KEY || !USER_TOKEN || !OWNER_ID) { + throw new Error("Set the connection values, USER_TOKEN, and OWNER_ID."); + } + const httpClient = new HTTPClient(); + httpClient.addHook("beforeRequest", (request) => { + request.headers.set("X-Iris-User-Token", USER_TOKEN); + return request; + }); + const memory = new AgentMemory({ + serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY, httpClient, + }); + console.dir(await memory.listSessionSummaries(20, undefined, OWNER_ID), { depth: null }); +} + +main().catch((error) => { console.error(error instanceof Error ? error.message : "Request failed"); process.exitCode = 1; }); ``` -The environment variable `MEM_OWNERSHIP_ENABLED` overrides the file setting. Its default is `false`. Apply the configuration to the Data Plane servers, then verify access with two different users: +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error --get \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: $USER_TOKEN" \ + --data-urlencode "filterOwnerId=$OWNER_ID" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/sessions" | jq +``` + +{{< /multitabs >}} + +Create a separate request-scoped client or transport for each user's token. Do not change a shared client's user header while it handles concurrent requests. + +A missing or invalid primary credential, an invalid or expired user token, or a missing user identity returns `401`. A token from another tenant, or a delegation header used with a credential other than an agent key, returns `403`. A valid delegated user still needs the key's store grant. + +### Enable or change ownership enforcement + +For a new deployment, configure the user-identity path before writing personal data. If your application does not use a trusted identity provider, explicitly disable enforcement and apply [backend authorization](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). -1. Confirm each user can retrieve their own personal records and search for them. -1. Confirm neither user can retrieve the other's personal records by ID or find them by changing an owner filter. -1. Confirm both users can access shared records when their store grants allow it. -1. Confirm an unbound agent key cannot read personal records or create a session with a named user as its first actor. +Before upgrading an existing deployment: -Search and list operations apply ownership restrictions in addition to client filters. An owner filter cannot expand access. Direct-ID requests for another user's personal record return `404 Not Found`. +1. Keep `memory.ownership.enabled: false` explicitly set while preparing the transition. +1. Complete the deployment's namespace-placement index migration. Existing stores need the scope and root-owner fields used by ownership queries. +1. Compare stored personal owners with the verified user IDs your application will send. Resolve mismatches through a controlled data migration; the setting does not rewrite owners. +1. Configure delegation, then set `memory.ownership.enabled: true` in chart-rendered values. For a bring-your-own file, use `ownership.enabled: true` at the root. +1. Apply the setting to both Data Plane servers and workers, then roll out those processes. `MEM_OWNERSHIP_ENABLED` overrides the file value; check both for conflicting settings. +1. Replace existing Summary Views when changing this setting. A view retains the source-access policy from its creation. +1. Test two users: each can access their own personal records, neither can retrieve or search for the other's records, and both can access shared records allowed by their store grants. +1. Test an agent key without a user token: it must not retrieve personal records or create a session for a named owner. -Shared scope grants access to all callers with the required store permission. It does not restrict access to a particular team. Keep team membership checks in your application, or separate teams into stores with different grants. Configured worker identities retain access across owners for background work; do not give worker credentials to applications. +Disabling enforcement restores store-wide access within the credential's grants. Keep backend authorization in place before disabling it. It does not remove stored owner fields, reassign records, or remove the need for store authentication. ## Worker callbacks diff --git a/content/operate/iris/agent-memory/self-managed/configuration.md b/content/operate/iris/agent-memory/self-managed/configuration.md index 67727a6ded..b74b736864 100644 --- a/content/operate/iris/agent-memory/self-managed/configuration.md +++ b/content/operate/iris/agent-memory/self-managed/configuration.md @@ -43,7 +43,7 @@ Data Plane settings go under `memory`, or at the top level of a BYO Data Plane c | `databases` | Required. Store Redis, as `databases."1".urls`. | | `embedding.models.default_embedding_model`, `embedding.models.dimensions` | Required. The embedding model and its vector size. The Control Plane's `embedding.dimensions` must match. | | `auth` | Data Plane authentication. Defaults to `agent_key`. | -| `ownership.enabled` | Restricts personal records to their authenticated owner while allowing shared records. Defaults to `false`. Requires a matching authenticated User ID for personal access; ordinary unbound agent keys do not provide one. See [Server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement) before enabling it. | +| `ownership.enabled` | Restricts personal records to their authenticated owner while allowing shared records. Defaults to `true` in on-prem release 0.10.2. Requires a matching authenticated user ID for personal access; use an agent key with a delegated user token, or explicitly disable enforcement and authorize requests in your backend. See [Server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement) before enabling it. | | `inference_providers` | Required when any `llm` block is set. Each entry holds a provider's `endpoint`. | | `server` | Data Plane bind address and port. | | `license.license_path` | Path where the license Secret is mounted. | @@ -292,3 +292,89 @@ subjectGrants: Also add `subject-grant-lookup` to the `memory-dp` runtime credential's `allowedOperations` alongside `api-key-introspect`. These are Identity Service values, not Memory's `identityService` values. `read_all_owners` is a subject-grant action; do not add it to agent-key grants. + +## Enable document ingestion + +Document ingestion is off when `document_ingestion` is omitted. In release 0.10.2, configure the server and worker together through chart-rendered values. This example sets explicit processing limits: + +```yaml +memory: + document_ingestion: + enabled: true + max_decoded_bytes: 1048576 + max_input_tokens: 8000 + max_chunks: 32 + max_output_records: 100 + max_queued_bytes_per_store: 10485760 + max_retries: 3 + max_execution_seconds: 900 + source_retention_seconds: 86400 + checkpoint_retention_seconds: 86400 + background_jobs: + redis: + enabled: true + jobs: + document-ingestion: + concurrency: 1 + timeout: 15m + max_retry: 3 + promote_session_memory: + strategies: + custom: + llm: + provider: openai + models: + default_chat_model: + credentials: + type: static + api_key: +``` + +Use a model supported by your configured `inference_providers.openai` connection. Supply the credential through the existing overlay Secret. The custom extraction model is required even if built-in session extraction already has a model. + +For a bring-your-own configuration, place these fields at the file root instead of under `memory`. Keep the worker's queue retries and timeout consistent with the ingestion limits. All numeric ingestion limits must be positive. Byte, estimated token, and chunk limits apply together. + +Roll out both the server and worker. Confirm store health, then [create an extractor and submit a document](/content/develop/ai/context-engine/agent-memory/memory-etl.md). Setting `enabled: false` rejects new ingestions; it does not undo memories already written. + +## Enable Summary Views + +Summary Views are off by default. Configure the server and worker with the same ownership setting, model provider, and explicit bounds. For chart-rendered configuration, merge these example values under `memory`: + +```yaml +memory: + summary_views: + enabled: true + llm: + provider: openai + models: + default_chat_model: + credentials: + type: static + api_key: + page_size: 10 + max_definitions: 100 + max_partitions: 100 + max_concurrent_refreshes: 2 + max_concurrent_refreshes_per_store: 1 + max_input_tokens: 8000 + max_source_records: 100 + max_source_tokens: 100000 + default_debounce: 1s + min_debounce: 1s + max_debounce: 1m + default_reconcile_interval: 1h + min_reconcile_interval: 1s + max_reconcile_interval: 24h + schedule_repair_interval: 1m + cleanup_batch_size: 100 + cleanup_timeout: 5s + generation: + max_output_tokens: 2000 + timeout: 30s +``` + +Use the deployment's configured model-provider connection and supply its credential through the overlay Secret. The selected provider must support the required text or structured generation; the OpenAI connection uses `chat_completions` for structured output. These numbers are example limits, not service-wide defaults. + +With a bring-your-own file, place `summary_views` at the file root. Keep the Redis job backend, authenticated worker callbacks, and scheduling worker enabled. Roll out server and worker together, then [create and refresh a view](/content/develop/ai/context-engine/agent-memory/summary-views.md). A private view needs an authenticated user; an ordinary agent key alone does not supply one. + +Each view retains its source-access policy. Replace existing definitions when changing ownership enforcement rather than expecting a flag change to rewrite them. diff --git a/content/operate/iris/agent-memory/self-managed/control-plane-api-reference/openapi-control-plane.json b/content/operate/iris/agent-memory/self-managed/control-plane-api-reference/openapi-control-plane.json index e48b196d40..71ee60f1fa 100644 --- a/content/operate/iris/agent-memory/self-managed/control-plane-api-reference/openapi-control-plane.json +++ b/content/operate/iris/agent-memory/self-managed/control-plane-api-reference/openapi-control-plane.json @@ -3,7 +3,7 @@ "info": { "title": "Redis Agent Memory Control Plane API", "version": "1.0.0", - "description": "Admin API for the Redis Agent Memory self-managed Control Plane.\n\nAuthentication: every endpoint requires a single static operator token, sent by default as\n`Authorization: Bearer `. The header and scheme are configurable with\n`auth.admin_token.header` and `auth.admin_token.scheme`; the security scheme shown is the\ndefault. This token is distinct from the agent keys used by the Data Plane. Operational\nendpoints (health, build-info) are served separately and are unauthenticated.\n\nThere is no per-store database, subscription, or endpoint binding: self-managed deployments use\na single shared store database. Embedding configuration is resolved server-side from deployment\nconfig rather than supplied here.\n" + "description": "Admin API for the Redis Agent Memory self-managed Control Plane.\n\nAuthentication: management endpoints require an operator credential. By default, use a static admin token, sent as\n`Authorization: Bearer `. The header and scheme are configurable with\n`auth.admin_token.header` and `auth.admin_token.scheme`; the security scheme shown is the\ndefault. Configured OIDC operator tokens are also accepted. The admin token is distinct from the agent keys used by the Data Plane. Operational\nendpoints (health, build-info) are served separately and are unauthenticated.\n\nThere is no per-store database, subscription, or endpoint binding: self-managed deployments use\na single shared store database. Embedding configuration is resolved server-side from deployment\nconfig rather than supplied here.\n" }, "paths": { "/v1/detectors": { @@ -228,6 +228,640 @@ } } }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + }, + "get": { + "operationId": "GetStore", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetStore 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetStoreResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + }, + "patch": { + "operationId": "UpdateStore", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateStoreRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateStore 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateStoreResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + } + }, + "/v1/stores/{storeId}/extractors": { + "get": { + "operationId": "ListExtractors", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ListExtractors 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListExtractorsResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + }, + "post": { + "operationId": "CreateExtractor", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateExtractorRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes." + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateExtractor 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateExtractorResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + } + }, + "/v1/stores/{storeId}/extractors/{extractorId}": { + "delete": { + "operationId": "RemoveExtractor", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes." + }, + "required": true + }, + { + "name": "extractorId", + "in": "path", + "description": "Store-scoped Extractor identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store-scoped Extractor identifier." + }, + "required": true + }, + { + "name": "expectedRevision", + "in": "query", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "RemoveExtractor 204 response" + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + }, + "get": { + "operationId": "GetExtractor", + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes." + }, + "required": true + }, + { + "name": "extractorId", + "in": "path", + "description": "Store-scoped Extractor identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store-scoped Extractor identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetExtractor 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetExtractorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "UnexpectedError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponseContent" + } + } + } + } + } + }, + "patch": { + "operationId": "UpdateExtractor", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateExtractorRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "storeId", + "in": "path", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes." + }, + "required": true + }, + { + "name": "extractorId", + "in": "path", + "description": "Store-scoped Extractor identifier.", + "schema": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store-scoped Extractor identifier." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateExtractor 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateExtractorResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "AuthenticationError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -239,9 +873,11 @@ } } } - }, + } + }, + "/v1/stores/{storeId}/ingestion-hooks": { "get": { - "operationId": "GetStore", + "operationId": "GetIngestionHooks", "parameters": [ { "name": "storeId", @@ -259,11 +895,11 @@ ], "responses": { "200": { - "description": "GetStore 200 response", + "description": "GetIngestionHooks 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetStoreResponseContent" + "$ref": "#/components/schemas/GetIngestionHooksResponseContent" } } } @@ -298,6 +934,16 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -310,16 +956,17 @@ } } }, - "patch": { - "operationId": "UpdateStore", + "put": { + "operationId": "PutIngestionHooks", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateStoreRequestContent" + "$ref": "#/components/schemas/PutIngestionHooksRequestContent" } } - } + }, + "required": true }, "parameters": [ { @@ -338,11 +985,11 @@ ], "responses": { "200": { - "description": "UpdateStore 200 response", + "description": "PutIngestionHooks 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateStoreResponseContent" + "$ref": "#/components/schemas/PutIngestionHooksResponseContent" } } } @@ -387,6 +1034,26 @@ } } }, + "409": { + "description": "ConflictError 409 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponseContent" + } + } + } + }, + "424": { + "description": "FailedDependencyError 424 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailedDependencyErrorResponseContent" + } + } + } + }, "500": { "description": "UnexpectedError 500 response", "content": { @@ -502,7 +1169,7 @@ }, "description": { "type": "string", - "description": "What the detector matches, and what it deliberately does not — the catalog is\nprecision-first, so an operator choosing between detectors needs the\nlimitation as much as the capability." + "description": "What the detector matches, and what it deliberately does not \u2014 the catalog is\nprecision-first, so an operator choosing between detectors needs the\nlimitation as much as the capability." } }, "required": [ @@ -529,6 +1196,157 @@ "enabled" ] }, + "ConflictErrorResponseContent": { + "type": "object", + "description": "The request conflicts with the current state of the resource.", + "properties": { + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem\n type. It SHOULD NOT change from occurrence to occurrence of the\n problem, except for purposes of localization (e.g., using\n proactive content negotiation; see [RFC7231], Section 3.4)." + }, + "status": { + "type": "integer", + "default": 409, + "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.", + "format": "int32" + }, + "detail": { + "type": "string", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "instance": { + "type": "string", + "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + }, + "type": { + "$ref": "#/components/schemas/ConflictErrorType" + } + }, + "required": [ + "status", + "title", + "type" + ] + }, + "ConflictErrorType": { + "type": "string", + "description": "Problem type URI for conflict errors.", + "enum": [ + "/errors/conflict" + ] + }, + "CreateExtractorRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 200, + "minLength": 1, + "description": "Customer-facing Extractor display name." + }, + "source": { + "$ref": "#/components/schemas/ExtractorSource" + }, + "strategy": { + "$ref": "#/components/schemas/ExtractorStrategy" + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Name of a custom memory type or field (1-64 chars). Must start with a letter,\nfollowed by letters, digits, underscores, or dashes." + }, + "prompt": { + "type": "string" + }, + "destinationNamespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes." + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "memoryType", + "name", + "prompt", + "source", + "strategy" + ] + }, + "CreateExtractorResponseContent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store-scoped Extractor identifier." + }, + "revision": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string", + "maxLength": 200, + "minLength": 1, + "description": "Customer-facing Extractor display name." + }, + "source": { + "$ref": "#/components/schemas/ExtractorSource" + }, + "strategy": { + "$ref": "#/components/schemas/ExtractorStrategy" + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Name of a custom memory type or field (1-64 chars). Must start with a letter,\nfollowed by letters, digits, underscores, or dashes." + }, + "prompt": { + "type": "string" + }, + "destinationNamespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes." + }, + "enabled": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "createdAt", + "enabled", + "id", + "memoryType", + "name", + "prompt", + "revision", + "source", + "strategy", + "updatedAt" + ] + }, "CreateStoreRequestContent": { "type": "object", "properties": { @@ -737,33 +1555,113 @@ "id" ] }, - "ExclusionAction": { + "ExclusionAction": { + "type": "string", + "enum": [ + "redact", + "drop" + ] + }, + "ExtractionCadenceConfig": { + "type": "object", + "description": "Cadence configuration for the extraction pipeline.", + "properties": { + "activeIntervalSeconds": { + "type": "integer", + "maximum": 600, + "minimum": 60, + "description": "Interval in seconds between extraction runs while a session is active (60-600).", + "format": "int32" + } + }, + "required": [ + "activeIntervalSeconds" + ] + }, + "ExtractionStrategy": { + "type": "string", + "enum": [ + "instruct" + ] + }, + "ExtractorOutput": { + "type": "object", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store-scoped Extractor identifier." + }, + "revision": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string", + "maxLength": 200, + "minLength": 1, + "description": "Customer-facing Extractor display name." + }, + "source": { + "$ref": "#/components/schemas/ExtractorSource" + }, + "strategy": { + "$ref": "#/components/schemas/ExtractorStrategy" + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Name of a custom memory type or field (1-64 chars). Must start with a letter,\nfollowed by letters, digits, underscores, or dashes." + }, + "prompt": { + "type": "string" + }, + "destinationNamespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes." + }, + "enabled": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "createdAt", + "enabled", + "id", + "memoryType", + "name", + "prompt", + "revision", + "source", + "strategy", + "updatedAt" + ] + }, + "ExtractorSource": { "type": "string", "enum": [ - "redact", - "drop" - ] - }, - "ExtractionCadenceConfig": { - "type": "object", - "description": "Cadence configuration for the extraction pipeline.", - "properties": { - "activeIntervalSeconds": { - "type": "integer", - "maximum": 600, - "minimum": 60, - "description": "Interval in seconds between extraction runs while a session is active (60-600).", - "format": "int32" - } - }, - "required": [ - "activeIntervalSeconds" + "documents" ] }, - "ExtractionStrategy": { + "ExtractorStrategy": { "type": "string", "enum": [ - "instruct" + "custom" ] }, "FailedDependencyErrorResponseContent": { @@ -868,6 +1766,96 @@ "/errors/insufficient-permissions" ] }, + "GetExtractorResponseContent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store-scoped Extractor identifier." + }, + "revision": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string", + "maxLength": 200, + "minLength": 1, + "description": "Customer-facing Extractor display name." + }, + "source": { + "$ref": "#/components/schemas/ExtractorSource" + }, + "strategy": { + "$ref": "#/components/schemas/ExtractorStrategy" + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Name of a custom memory type or field (1-64 chars). Must start with a letter,\nfollowed by letters, digits, underscores, or dashes." + }, + "prompt": { + "type": "string" + }, + "destinationNamespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes." + }, + "enabled": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "createdAt", + "enabled", + "id", + "memoryType", + "name", + "prompt", + "revision", + "source", + "strategy", + "updatedAt" + ] + }, + "GetIngestionHooksResponseContent": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "format": "int64" + }, + "enabled": { + "type": "boolean" + }, + "beforeInput": { + "$ref": "#/components/schemas/InputHookEndpointOutput" + }, + "beforeApply": { + "$ref": "#/components/schemas/OutputHookEndpointOutput" + } + }, + "required": [ + "enabled", + "revision" + ] + }, "GetStoreResponseContent": { "type": "object", "properties": { @@ -920,6 +1908,82 @@ "storeId" ] }, + "InputHookEndpointOutput": { + "type": "object", + "properties": { + "url": { + "type": "string", + "maxLength": 2048, + "minLength": 1 + }, + "paths": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InputHookPath" + }, + "maxItems": 10, + "minItems": 1, + "uniqueItems": true + }, + "keyId": { + "type": "string" + }, + "signingKeyConfigured": { + "type": "boolean" + } + }, + "required": [ + "keyId", + "paths", + "signingKeyConfigured", + "url" + ] + }, + "InputHookEndpointWrite": { + "type": "object", + "properties": { + "url": { + "type": "string", + "maxLength": 2048, + "minLength": 1 + }, + "paths": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InputHookPath" + }, + "maxItems": 10, + "minItems": 1, + "uniqueItems": true + }, + "signingKey": { + "type": "string", + "maxLength": 44, + "minLength": 44, + "description": "Omit to keep this phase's current key. Required for a new phase.", + "format": "password" + } + }, + "required": [ + "paths", + "url" + ] + }, + "InputHookPath": { + "type": "string", + "enum": [ + "session.append", + "memory.create", + "memory.update", + "memory.delete", + "memory.move", + "session.summary", + "session.property", + "session.delete", + "session.events.delete", + "document.submit" + ] + }, "ListBuiltInDetectorsResponseContent": { "type": "object", "properties": { @@ -940,6 +2004,20 @@ "detectors" ] }, + "ListExtractorsResponseContent": { + "type": "object", + "properties": { + "extractors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtractorOutput" + } + } + }, + "required": [ + "extractors" + ] + }, "ListStoresResponseContent": { "type": "object", "properties": { @@ -1068,9 +2146,125 @@ "/errors/resource-not-found" ] }, + "OutputHookEndpointOutput": { + "type": "object", + "properties": { + "url": { + "type": "string", + "maxLength": 2048, + "minLength": 1 + }, + "paths": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OutputHookPath" + }, + "maxItems": 4, + "minItems": 1, + "uniqueItems": true + }, + "keyId": { + "type": "string" + }, + "signingKeyConfigured": { + "type": "boolean" + } + }, + "required": [ + "keyId", + "paths", + "signingKeyConfigured", + "url" + ] + }, + "OutputHookEndpointWrite": { + "type": "object", + "properties": { + "url": { + "type": "string", + "maxLength": 2048, + "minLength": 1 + }, + "paths": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OutputHookPath" + }, + "maxItems": 4, + "minItems": 1, + "uniqueItems": true + }, + "signingKey": { + "type": "string", + "maxLength": 44, + "minLength": 44, + "description": "Exactly 32 bytes in canonical padded base64. Never returned by a read.", + "format": "password" + } + }, + "required": [ + "paths", + "url" + ] + }, + "OutputHookPath": { + "type": "string", + "enum": [ + "document.chunk", + "session.builtin", + "session.custom", + "summary_view.refresh" + ] + }, + "PutIngestionHooksRequestContent": { + "type": "object", + "properties": { + "expectedRevision": { + "type": "integer", + "minimum": 0, + "description": "Zero configures a store for the first time. Each successful write advances revision.", + "format": "int64" + }, + "enabled": { + "type": "boolean" + }, + "beforeInput": { + "$ref": "#/components/schemas/InputHookEndpointWrite" + }, + "beforeApply": { + "$ref": "#/components/schemas/OutputHookEndpointWrite" + } + }, + "required": [ + "enabled", + "expectedRevision" + ] + }, + "PutIngestionHooksResponseContent": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "format": "int64" + }, + "enabled": { + "type": "boolean" + }, + "beforeInput": { + "$ref": "#/components/schemas/InputHookEndpointOutput" + }, + "beforeApply": { + "$ref": "#/components/schemas/OutputHookEndpointOutput" + } + }, + "required": [ + "enabled", + "revision" + ] + }, "RegexSpec": { "type": "object", - "description": "A regular expression, evaluated by RE2 — linear-time, and therefore without\nlookaround. Rejected on write if it does not compile, or if it can match without\nconsuming text.", + "description": "A regular expression, evaluated by RE2 \u2014 linear-time, and therefore without\nlookaround. Rejected on write if it does not compile, or if it can match without\nconsuming text.", "properties": { "pattern": { "type": "string", @@ -1251,6 +2445,102 @@ "/errors/unexpected-error" ] }, + "UpdateExtractorRequestContent": { + "type": "object", + "properties": { + "expectedRevision": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string", + "maxLength": 200, + "minLength": 1, + "description": "Customer-facing Extractor display name." + }, + "prompt": { + "type": "string" + }, + "destinationNamespaceId": { + "type": "string", + "description": "Empty string clears the destination namespace. Omitted leaves it unchanged.\nTyped as String so a clear is not rejected by StoreId @length(min: 1)." + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "expectedRevision" + ] + }, + "UpdateExtractorResponseContent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store-scoped Extractor identifier." + }, + "revision": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string", + "maxLength": 200, + "minLength": 1, + "description": "Customer-facing Extractor display name." + }, + "source": { + "$ref": "#/components/schemas/ExtractorSource" + }, + "strategy": { + "$ref": "#/components/schemas/ExtractorStrategy" + }, + "memoryType": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "description": "Name of a custom memory type or field (1-64 chars). Must start with a letter,\nfollowed by letters, digits, underscores, or dashes." + }, + "prompt": { + "type": "string" + }, + "destinationNamespaceId": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "pattern": "^[a-zA-Z0-9-]+$", + "description": "Store identifier (1-64 chars, alphanumeric and dashes). Generated IDs are 32-char UUIDs without dashes." + }, + "enabled": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "createdAt", + "enabled", + "id", + "memoryType", + "name", + "prompt", + "revision", + "source", + "strategy", + "updatedAt" + ] + }, "UpdateStoreRequestContent": { "type": "object", "properties": { diff --git a/content/operate/iris/agent-memory/self-managed/deploy.md b/content/operate/iris/agent-memory/self-managed/deploy.md index cbbf9b3c71..63977ec4a4 100644 --- a/content/operate/iris/agent-memory/self-managed/deploy.md +++ b/content/operate/iris/agent-memory/self-managed/deploy.md @@ -22,7 +22,7 @@ These steps install Redis Agent Memory with chart-rendered configuration and age authentication, and end with one authenticated call to the Data Plane. Install Identity Service as release `ids`, then Memory as release `redis-agent-memory`. -These steps use Memory chart `0.8.0` and Identity Service chart `0.10.0` in one namespace. +These steps use Memory chart `0.10.2` and Identity Service chart `0.10.2` in one namespace. You create the shared credentials and Identity Service control token; Memory generates its Control Plane admin token. See [Credentials](/content/operate/iris/agent-memory/self-managed/authentication.md#credentials). @@ -33,6 +33,8 @@ placeholders with addresses reachable from the calling pods. Obtain their certif (CA) certificates as a PEM bundle named `service-ca.crt`; use your organization's trust bundle if the endpoints use public certificates. +Ownership enforcement is on by default. Before using an agent key with personal memories, [configure delegated user identity](/content/operate/iris/agent-memory/self-managed/authentication.md#delegate-a-user-with-an-agent-key). Applications that authorize users entirely in their backend must explicitly disable ownership enforcement in their Helm values. + Before you begin, review the [prerequisites](/content/operate/iris/agent-memory/self-managed/prerequisites.md). ### Isolated lab without TLS @@ -158,7 +160,7 @@ tls: helm repo add redis-ai https://helm.redis.io/ai helm repo update redis-ai helm install ids redis-ai/iris-identity-service \ - --version 0.10.0 \ + --version 0.10.2 \ --namespace \ -f values-ids.yaml ``` @@ -377,7 +379,7 @@ its row. The rows are checklist items CL-1 to CL-6. helm repo add redis-ai https://helm.redis.io/ai helm repo update redis-ai helm install redis-agent-memory redis-ai/redis-agent-memory \ - --version 0.8.0 \ + --version 0.10.2 \ --namespace \ -f ram-values.yaml kubectl -n get pods -w @@ -389,7 +391,7 @@ If you want Helm to wait, set an explicit timeout that matches the environment: ```bash helm install redis-agent-memory redis-ai/redis-agent-memory \ - --version 0.8.0 \ + --version 0.10.2 \ --namespace \ -f ram-values.yaml \ --wait \ diff --git a/local_examples/agent-memory/agent_memory_sdk.py b/local_examples/agent-memory/agent_memory_sdk.py index 8242857900..4d1c0fd650 100644 --- a/local_examples/agent-memory/agent_memory_sdk.py +++ b/local_examples/agent-memory/agent_memory_sdk.py @@ -4,13 +4,14 @@ import os from datetime import datetime, timezone +import httpx from redis_agent_memory import AgentMemory, models ENDPOINT = "" STORE_ID = "" SESSION_ID = "travel-planning-session" -USER_ID = "quickstart-user" +USER_ID = os.environ.get("OWNER_ID", "quickstart-user") def show(label, response): @@ -19,10 +20,13 @@ def show(label, response): def main(): - with AgentMemory( + with httpx.Client(headers={ + "X-Iris-User-Token": os.environ["USER_TOKEN"], + } if os.environ.get("USER_TOKEN") else {}) as transport, AgentMemory( ENDPOINT, store_id=STORE_ID, api_key=os.environ["API_KEY"], + client=transport, ) as agent_memory: health = agent_memory.store_health() show("Store health", health) @@ -36,13 +40,14 @@ def main(): import os from datetime import datetime, timezone +import httpx from redis_agent_memory import AgentMemory, models ENDPOINT = "" STORE_ID = "" SESSION_ID = "travel-planning-session" -USER_ID = "quickstart-user" +USER_ID = os.environ.get("OWNER_ID", "quickstart-user") def show(label, response): @@ -51,10 +56,13 @@ def show(label, response): def main(): - with AgentMemory( + with httpx.Client(headers={ + "X-Iris-User-Token": os.environ["USER_TOKEN"], + } if os.environ.get("USER_TOKEN") else {}) as transport, AgentMemory( ENDPOINT, store_id=STORE_ID, api_key=os.environ["API_KEY"], + client=transport, ) as agent_memory: health = agent_memory.store_health() show("Store health", health) @@ -87,13 +95,14 @@ def main(): import os from datetime import datetime, timezone +import httpx from redis_agent_memory import AgentMemory, models ENDPOINT = "" STORE_ID = "" SESSION_ID = "travel-planning-session" -USER_ID = "quickstart-user" +USER_ID = os.environ.get("OWNER_ID", "quickstart-user") def show(label, response): @@ -102,10 +111,13 @@ def show(label, response): def main(): - with AgentMemory( + with httpx.Client(headers={ + "X-Iris-User-Token": os.environ["USER_TOKEN"], + } if os.environ.get("USER_TOKEN") else {}) as transport, AgentMemory( ENDPOINT, store_id=STORE_ID, api_key=os.environ["API_KEY"], + client=transport, ) as agent_memory: health = agent_memory.store_health() show("Store health", health) diff --git a/local_examples/agent-memory/agent_memory_sdk.ts b/local_examples/agent-memory/agent_memory_sdk.ts index ea6d12c582..4caf88464f 100644 --- a/local_examples/agent-memory/agent_memory_sdk.ts +++ b/local_examples/agent-memory/agent_memory_sdk.ts @@ -2,6 +2,7 @@ // STEP_START connect_health_check import { AgentMemory } from "@redis-iris/agent-memory"; +import { HTTPClient } from "@redis-iris/agent-memory/lib/http"; const apiKey = process.env.API_KEY; if (!apiKey) { @@ -11,12 +12,22 @@ if (!apiKey) { const serverURL = ""; const storeId = ""; const sessionId = "travel-planning-session"; -const userId = "quickstart-user"; +const userId = process.env.OWNER_ID ?? "quickstart-user"; + +const httpClient = new HTTPClient(); +const userToken = process.env.USER_TOKEN; +if (userToken) { + httpClient.addHook("beforeRequest", (request) => { + request.headers.set("X-Iris-User-Token", userToken); + return request; + }); +} const agentMemory = new AgentMemory({ serverURL, storeId, apiKey, + httpClient, }); async function run() { @@ -26,13 +37,14 @@ async function run() { } run().catch((error) => { - console.error(error); + console.error(error instanceof Error ? error.message : "Request failed"); process.exitCode = 1; }); // STEP_END // STEP_START add_session_event import { AgentMemory } from "@redis-iris/agent-memory"; +import { HTTPClient } from "@redis-iris/agent-memory/lib/http"; const apiKey = process.env.API_KEY; if (!apiKey) { @@ -42,12 +54,22 @@ if (!apiKey) { const serverURL = ""; const storeId = ""; const sessionId = "travel-planning-session"; -const userId = "quickstart-user"; +const userId = process.env.OWNER_ID ?? "quickstart-user"; + +const httpClient = new HTTPClient(); +const userToken = process.env.USER_TOKEN; +if (userToken) { + httpClient.addHook("beforeRequest", (request) => { + request.headers.set("X-Iris-User-Token", userToken); + return request; + }); +} const agentMemory = new AgentMemory({ serverURL, storeId, apiKey, + httpClient, }); async function run() { @@ -73,13 +95,14 @@ async function run() { } run().catch((error) => { - console.error(error); + console.error(error instanceof Error ? error.message : "Request failed"); process.exitCode = 1; }); // STEP_END // STEP_START search_long_term_memory_basic import { AgentMemory } from "@redis-iris/agent-memory"; +import { HTTPClient } from "@redis-iris/agent-memory/lib/http"; const apiKey = process.env.API_KEY; if (!apiKey) { @@ -89,12 +112,22 @@ if (!apiKey) { const serverURL = ""; const storeId = ""; const sessionId = "travel-planning-session"; -const userId = "quickstart-user"; +const userId = process.env.OWNER_ID ?? "quickstart-user"; + +const httpClient = new HTTPClient(); +const userToken = process.env.USER_TOKEN; +if (userToken) { + httpClient.addHook("beforeRequest", (request) => { + request.headers.set("X-Iris-User-Token", userToken); + return request; + }); +} const agentMemory = new AgentMemory({ serverURL, storeId, apiKey, + httpClient, }); async function run() { @@ -116,7 +149,7 @@ async function run() { } run().catch((error) => { - console.error(error); + console.error(error instanceof Error ? error.message : "Request failed"); process.exitCode = 1; }); // STEP_END From d14a0b00a1a7d50e341de4573c65374a3e58152f Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Fri, 23 Oct 2026 11:18:47 -0700 Subject: [PATCH 22/26] Separate operator setup and clarify memory examples --- .../ai/context-engine/agent-memory/_index.md | 26 ++- .../api-reference/openapi-agent-memory.json | 6 +- .../agent-memory/developer-guide.md | 120 ++-------- .../agent-memory/ingestion-webhooks.md | 132 ++++------- .../agent-memory/list-sessions.md | 36 ++- .../agent-memory/long-term-memory.md | 114 ++++++++-- .../context-engine/agent-memory/memory-etl.md | 101 ++------- .../agent-memory/namespace-routing.md | 59 ++--- .../context-engine/agent-memory/namespaces.md | 205 +++++++++++------- .../context-engine/agent-memory/overview.md | 2 +- .../context-engine/agent-memory/quickstart.md | 48 +++- .../context-engine/agent-memory/sessions.md | 68 +++--- .../agent-memory/summary-views.md | 26 ++- .../agent-memory-continue-from-quickstart.md | 7 + .../iris/agent-memory/create-service.md | 2 +- .../iris/agent-memory/self-managed/_index.md | 2 +- .../agent-memory/self-managed/api-examples.md | 204 ++++++++++++++++- .../self-managed/authentication.md | 16 +- .../self-managed/configuration.md | 73 ++++++- .../openapi-control-plane.json | 2 +- .../iris/agent-memory/self-managed/deploy.md | 2 - .../agent-memory/agent_memory_sdk.py | 18 +- .../agent-memory/agent_memory_sdk.ts | 33 --- 23 files changed, 765 insertions(+), 537 deletions(-) create mode 100644 content/embeds/agent-memory-continue-from-quickstart.md diff --git a/content/develop/ai/context-engine/agent-memory/_index.md b/content/develop/ai/context-engine/agent-memory/_index.md index 58c3a766a9..45b797f5e0 100644 --- a/content/develop/ai/context-engine/agent-memory/_index.md +++ b/content/develop/ai/context-engine/agent-memory/_index.md @@ -24,7 +24,7 @@ When enabled, automatic summarization compacts session memory by summarizing old Access Redis Agent Memory through the Python and TypeScript SDKs or its REST API. It works with any agent framework or LLM provider.
- {{< image-card image="images/ai-brain.svg" alt="Overview icon" title="Overview — Understand the memory model" url="/develop/ai/context-engine/agent-memory/overview" >}} + {{< image-card image="images/ai-model.svg" alt="Overview icon" title="Overview — What's the same and what's different if you already know Redis" url="/develop/ai/context-engine/agent-memory/overview" >}} {{< image-card image="images/ai-cube.svg" alt="Quickstart icon" title="Quickstart — Make your first requests" url="/develop/ai/context-engine/agent-memory/quickstart" >}} {{< image-card image="images/ai-lib.svg" alt="Developer guide icon" title="Developer guide — Add memory to your application" url="/develop/ai/context-engine/agent-memory/developer-guide" >}} {{< image-card image="images/ai-LLM-memory.svg" alt="Sessions icon" title="Sessions — Store conversation events" url="/develop/ai/context-engine/agent-memory/sessions" >}} @@ -44,9 +44,29 @@ Access Redis Agent Memory through the Python and TypeScript SDKs or its REST API ## Two-tier memory model -Session memory keeps the current conversation available to the agent. Long-term memory preserves useful information across conversations. See [Overview]({{< relref "/develop/ai/context-engine/agent-memory/overview" >}}) for an overview of both and the optional namespaces that organize them. +Redis Agent Memory provides two memory tiers: -To watch both tiers change during a conversation, try the [interactive demo](/content/develop/ai/context-engine/agent-memory/interactive-demo.md). +
+
+

Session memory

+

Stores the ordered events and metadata for a conversation.

+
    +
  • Configurable retention: Set a time-to-live (TTL) to control how long session events are retained.
  • +
  • Automatic summarization: Condense older events after a configured threshold while retaining recent events in full, reducing the conversation history sent to the model's context window.
  • +
+
+
+

Long-term memory

+

Stores durable information that can be retrieved across sessions using semantic search and filters.

+
    +
  • Automatic extraction: Process session events asynchronously and store important information with vector embeddings and metadata.
  • +
  • Custom memory types: Define domain-specific memories with structured fields and extraction instructions.
  • +
  • Sensitive-data exclusions: Guide automatic extraction away from specified sensitive information.
  • +
  • Direct memory creation: Create memories through the API or import knowledge from external sources.
  • +
  • Configurable retention: Set a separate TTL for long-term memories.
  • +
+
+
### Example: Travel planning agent diff --git a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json index c3095f3194..8ae68ff124 100644 --- a/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json +++ b/content/develop/ai/context-engine/agent-memory/api-reference/openapi-agent-memory.json @@ -5559,13 +5559,13 @@ { "name": "memoryType", "in": "query", - "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, `message`, and `session_summary_view`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes.\n", + "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, and `message`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes.\n", "schema": { "type": "string", "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, `message`, and `session_summary_view`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes." + "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, and `message`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes." } }, { @@ -9195,7 +9195,7 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, `message`, and `session_summary_view`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes." + "description": "Identifier of a long-term memory type.\n\nBuilt-in types include `semantic`, `episodic`, and `message`.\nStores may also register custom memory types; this open string accepts any\nregistered type name.\n\nConstraints: 1-64 chars, must start with a letter, followed by letters, digits,\nunderscores, or dashes." } } }, diff --git a/content/develop/ai/context-engine/agent-memory/developer-guide.md b/content/develop/ai/context-engine/agent-memory/developer-guide.md index 8a775e8022..c89a3d5da9 100644 --- a/content/develop/ai/context-engine/agent-memory/developer-guide.md +++ b/content/develop/ai/context-engine/agent-memory/developer-guide.md @@ -28,76 +28,7 @@ Every client needs the service endpoint, Store ID, and application programming i ### Manage a service with the Control Plane SDK -The same package supplies two independently configured clients: - -| Client | Purpose | Connection and credential | -|:-------|:--------|:--------------------------| -| `AgentMemoryService` | Manage stores and extractors. Supply the store ID on each call. | Control Plane URL and operator `token`. | -| `AgentMemory` | Read and write memory within the store selected by the constructor. | Data Plane URL, Store ID, and application credential. | - -Use the self-managed Control Plane admin token or supported operator token with `AgentMemoryService`. Its SDK uses `/v2/memory` routes; the self-managed `/v1` admin API remains available. The SDK does not derive a Data Plane URL or credential from a Control Plane response. - -Set `AGENT_MEMORY_CONTROL_PLANE_URL` and `AGENT_MEMORY_SERVICE_TOKEN` separately from `AGENT_MEMORY_URL`, `STORE_ID`, and `API_KEY`. These examples read the store and check Data Plane health: - -{{< multitabs id="memory-service-sdk" tab1="Python" tab2="TypeScript" >}} - -```python -import asyncio -import os - -from redis_agent_memory import AgentMemory, AgentMemoryService - - -async def main(): - async with AgentMemoryService( - server_url=os.environ["AGENT_MEMORY_CONTROL_PLANE_URL"], - token=os.environ["AGENT_MEMORY_SERVICE_TOKEN"], - ) as service: - store = await service.get_store_async(store_id=os.environ["STORE_ID"]) - print("Store:", store.name) - async with AgentMemory( - os.environ["AGENT_MEMORY_URL"], store_id=os.environ["STORE_ID"], - api_key=os.environ["API_KEY"], - ) as memory: - print((await memory.store_health_async()).status) - - -asyncio.run(main()) -``` - --tab-sep- - -```typescript -import { AgentMemory, AgentMemoryService } from "@redis-iris/agent-memory"; - -async function main() { - const { AGENT_MEMORY_CONTROL_PLANE_URL, AGENT_MEMORY_SERVICE_TOKEN, - AGENT_MEMORY_URL, STORE_ID, API_KEY } = process.env; - if (!AGENT_MEMORY_CONTROL_PLANE_URL || !AGENT_MEMORY_SERVICE_TOKEN || - !AGENT_MEMORY_URL || !STORE_ID || !API_KEY) { - throw new Error("Set both planes' connection values."); - } - const service = new AgentMemoryService({ - serverURL: AGENT_MEMORY_CONTROL_PLANE_URL, token: AGENT_MEMORY_SERVICE_TOKEN, - }); - console.log("Store:", (await service.getStore(STORE_ID)).name); - const memory = new AgentMemory({ serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY }); - console.log((await memory.storeHealth()).status); -} - -main().catch((error) => { - console.error(error instanceof Error ? error.message : "Request failed"); - process.exitCode = 1; -}); -``` - -{{< /multitabs >}} - -If omitted, the Control Plane token is read from `AGENT_MEMORY_SERVICE_TOKEN`; it does not fall back to the Data Plane key. Python service models, errors, and retry helpers use `redis_agent_memory.controlplane`; Data Plane imports remain separate. Use each client's corresponding error classes. - -Store and extractor management, detectors, and memory-type reference data have Cloud and self-managed API implementations. The generated client also includes Cloud-only account database, end user license agreement (EULA), network, store-model, metrics, flush, and empty-store operations; those return `404` on-prem. The current Cloud Control Plane does not accept a programmatic credential for this SDK. Use the Cloud console for Cloud service setup rather than sending a Data Plane key or assuming a console session is a service token. - -See [MemoryETL](/content/develop/ai/context-engine/agent-memory/memory-etl.md#create-an-extractor) for extractor administration and the [self-managed Control Plane reference](/content/operate/iris/agent-memory/self-managed/control-plane-api-reference.md) for the admin REST API. Webhook-policy configuration uses the admin REST API; it is not a method on the current service SDK. +For store and extractor administration, see [Manage a service with the Control Plane SDK](/content/operate/iris/agent-memory/self-managed/api-examples.md#manage-a-service-with-the-control-plane-sdk). Keep operator credentials separate from application credentials. ## Identify users and conversations @@ -198,24 +129,23 @@ See [View and edit service configuration]({{< relref "/operate/iris/agent-memory A failed request is different from a successful search with no matches. Handle the returned error before you use the result: -| Failure | Application response | -|:--------|:---------------------| -| Authentication error | Check the configured API key. Retrying with the same invalid key will not help. | -| Invalid request | Read the error details and correct the field or filter before retrying. | -| Search timeout | If the assistant can answer from the current conversation, continue without recalled memories. Otherwise, tell the user that memory is unavailable and ask them to retry. | -| Event-write timeout | Retrieve the session and check whether the event was stored before sending it again. Compare its actor, timestamp, and content. Repeating a write can create a duplicate. | - ### Handle typed errors and partial results -| Status or result | Action | -|:-----------------|:-------| -| `401` | Refresh or correct the credential. An expired delegated user token needs the application's login/token lifecycle. | -| `403` or a hidden personal-record `404` | Check store grants and authenticated ownership. Changing an owner filter does not grant access. | -| `409` | Correct an archived namespace destination or a stale extractor/hook-policy revision before retrying. | -| `423` | The resource is suspended. Restore its availability before retrying. | -| `424` | Inspect the typed failed-dependency error and its problem type. | -| `429` or retryable `5xx` | Use bounded backoff and honor retry timing. Retrying a write also needs its operation's idempotency or recovery contract. | -| Successful bulk response with `errors` | Inspect every item. Other records can already be created, deleted, or moved. | +| What you see | What it means | What to do | +|:-------------|:--------------|:-----------| +| Empty `items` | No memories match yet. | Continue with session context. | +| Invalid request | A field or filter is invalid. | Correct it using the error details. | +| `401` | The credential is invalid or expired. | Correct the key or refresh the user's token. | +| `403` or a hidden personal-record `404` | Store access or ownership prevents the request. | Check grants and authenticated identity. An owner filter cannot grant access. | +| `409` | A resource conflicts, such as an archived destination or stale revision. | Correct the destination or read the current revision before retrying. | +| `423` | The resource is suspended. | Restore its availability. | +| `424` | A dependency failed. | Inspect the typed error and problem type. | +| `429` or retryable `5xx` | The request is throttled or temporarily unavailable. | Use bounded backoff and honor retry timing. | +| Search timeout | Recall did not complete. | Use session context if it is enough to answer. Otherwise, report memory unavailability. | +| Event-write timeout | The write may already have succeeded. | Retrieve the session before retrying; repeated writes can add duplicate events. | +| Successful bulk response with `errors` | Some records failed; others may already be changed. | Inspect every item before deciding what to retry. | + +Before retrying a write, follow its idempotency or recovery contract. The operation's response contract determines its `424` exception class. For example, search uses `MemoryFailedDependencyErrorResponseContent`, while store health uses `FailedDependencyErrorResponseContent`. Both can report unavailable resources or database memory exhaustion; the memory response also covers model credentials and quota failures. Inspect the problem type as well as the class. Python exceptions are in `redis_agent_memory.errors`, with problem fields under `error.data`. TypeScript exceptions are in `@redis-iris/agent-memory/models/errors`. @@ -252,23 +182,11 @@ async def main(): asyncio.run(main()) ``` -The ingestion-hook `503` retry error and `422` stopped error also have distinct types and recovery rules. See [webhook failures](/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md#handle-failures-and-inspect-status). Use store-scoped `store_health`/`storeHealth` for current servers; a retained SDK `health()` method does not establish that a server still serves `/health`. - -### Preserve TypeScript argument positions when upgrading - -TypeScript listing methods use positional arguments. Their new parameters do not all occupy the same position: - -| Method | Relevant positions | -|:-------|:-------------------| -| `listNamespaces` | Request options remain sixth; `sortBy` and `sortOrder` are seventh and eighth. | -| `listSessions` | `includeAll` is fourth, request options fifth, and namespace filtering sixth. | -| `listSessionSummaries` | Namespace filtering is fourth, then `includeAll`, `sortBy`, `sortOrder`, and request options. | - -Pass `undefined` for skipped positions. Python uses named arguments. See [namespace sorting](/content/develop/ai/context-engine/agent-memory/namespaces.md#sort-namespaces) and [session listing](/content/develop/ai/context-engine/agent-memory/list-sessions.md) for complete examples. +The ingestion-hook `503` retry error and `422` stopped error also have distinct types and recovery rules. See [webhook failures](/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md#handle-failures-and-inspect-status). To check the store, use `store_health` (Python) or `storeHealth` (TypeScript). -### Create long-term memories +## Create long-term memories -Create memories directly when your application already has information to store, such as a preference submitted through a form. Set the owner and a stable record ID, and check the bulk response for per-record errors. Direct creation does not apply automatic extraction or sensitive-data exclusions. +Create memories directly when your application already has information to store, such as a preference submitted through a form. Set the owner and a stable record ID. Creating an eligible record with the same ID again replaces it; ownership checks and protection for document-managed records still apply. Check the bulk response for per-record errors. Direct creation does not apply automatic extraction or sensitive-data exclusions. See [Long-term memory]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory" >}}) for creation methods, search fields, retention, and extraction controls. diff --git a/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md b/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md index 56e076c284..6540044503 100644 --- a/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md +++ b/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md @@ -11,7 +11,7 @@ title: Review content with ingestion webhooks weight: 15 --- -Ingestion webhooks let your application accept, reject, or transform content before Agent Memory saves it. A callback returns a decision; successful HTTP delivery alone does not approve a write. +Ingestion webhooks let your application accept, reject, or transform content before Redis Agent Memory saves it. A callback returns a decision; successful HTTP delivery alone does not approve a write. The supported setup uses a self-managed deployment with the Redis background-job backend. Hooks are off by default. This guide does not assume Cloud account controls or a Cloud webhook rollout. @@ -19,102 +19,35 @@ The supported setup uses a self-managed deployment with the Redis background-job Select each path your policy needs. Selecting creation does not also select updates or deletion. -| Phase | Paths | Review | -|:------|:------|:-------| -| `before_input` | `session.append`, `memory.create`, `document.submit` | Session input, a manual memory batch, or a document before persistence and extraction dispatch. Accept, reject, or transform. | -| `before_apply` | `session.builtin`, `session.custom`, `document.chunk` | Generated creates, updates, and deletes before the current batch is applied. Whole-batch or per-operation decisions. | -| `before_input` | `memory.update`, `memory.delete`, `memory.move` | An authorized memory mutation. Accept or reject only. | -| `before_input` | `session.summary`, `session.property`, `session.delete`, `session.events.delete` | Session content changes and explicit deletion, including applicable worker writes. Accept or reject only. | -| `before_apply` | `summary_view.refresh` | A generated Summary View value before publication. Accept or reject only. | +### Before input is saved (`before_input`) -Configuration, reads, searches, automatic expiration, and store teardown are outside content hooks. Empty extraction batches and empty Summary View results have no generated content to review. Authorization runs before content is sent to a receiver; an acceptance cannot grant access or bypass validation. - -## Prepare the deployment - -Start with the [self-managed deployment](/content/operate/iris/agent-memory/self-managed/deploy.md). These are process settings; a client cannot enable them through a request header. - -An operator must configure both Data Plane servers and workers: - -1. Enable `memory.webhooks.enabled` in chart-rendered configuration, or `webhooks.enabled` at the root of a bring-your-own file. -1. Enable the Redis job backend and [authenticated worker callbacks](/content/operate/iris/agent-memory/self-managed/authentication.md#worker-callbacks). -1. Configure the same signing-secret backend and prefix in the Control Plane, Data Plane, and worker. Supported backends are Vault and AWS Secrets Manager. The Control Plane writes keys; Data Plane processes read them. -1. Serve the receiver over HTTPS with a certificate trusted by the Data Plane. -1. For a private receiver address, allow its range in `memory.webhooks.private_cidrs` on both servers and workers. - -For example, allow one private receiver address in Helm values: - -```yaml -memory: - webhooks: - enabled: true - private_cidrs: - - /32 -``` +| Path | What triggers it | Decisions | +|:-----|:-----------------|:----------| +| `session.append` | An application adds a session event. | Accept, reject, or transform. | +| `memory.create` | An application creates a memory batch. | Accept, reject, transform, or review each operation. | +| `document.submit` | An application submits a document for extraction. | Accept, reject, or transform. | +| `memory.update`, `memory.delete`, `memory.move` | An authorized caller changes, deletes, or moves a memory. | Accept or reject. | +| `session.summary`, `session.property` | An application or worker writes a session summary or property. | Accept or reject. | +| `session.delete`, `session.events.delete` | An application explicitly deletes a session or event. | Accept or reject. | -Delivery checks all resolved addresses at connection time. Redirects and environment HTTP proxies are not used. An allowlist cannot permit loopback, link-local, metadata-service, multicast, or other protected addresses. A localhost URL is not a production callback endpoint. +### Before generated changes are applied (`before_apply`) -For Vault, use matching application configuration in the Data Plane and Control Plane: +| Path | What triggers it | Decisions | +|:-----|:-----------------|:----------| +| `session.builtin` | Built-in session extraction produces memory changes. | Whole-batch or per-operation decisions. | +| `session.custom` | Custom session extraction produces memory changes. | Whole-batch or per-operation decisions. | +| `document.chunk` | Document extraction produces memory changes from a chunk. | Whole-batch or per-operation decisions. | +| `summary_view.refresh` | A Summary View generates a value for publication. | Accept or reject. | -```yaml -secrets: - provider: vault -vault: - address: https://:8200 - mount: secret - base_path: agent-memory -``` - -In chart-rendered values, put this under `memory` and `controlplane.configData`, respectively. Supply `vault.token` through a mounted configuration Secret. The Control Plane needs permission to write and delete signing-key versions; servers and workers need read permission. Keep real Vault tokens and callback keys out of source control. +Configuration, reads, searches, automatic expiration, and store teardown are outside content hooks. Empty extraction batches and empty Summary View results have no generated content to review. Authorization runs before content is sent to a receiver; an acceptance cannot grant access or bypass validation. -Before enabling output hooks on an existing deployment, upgrade workers first, then servers, and drain jobs queued by the older release. Keep hooks disabled until both sides support them. Setting the deployment flag to false stops delivery and fails selected work closed; it does not approve pending operations. +## Prepare the deployment -For Active-Active backing databases, route every server and worker for a store through the same authoritative writer connection. Do not mix member addresses or use region-dependent aliases for that connection. Before changing writers, drain old writers, wait for replication, and restart the processes to clear old connections. A store with hook history fails closed if its persisted writer connection is missing or invalid; regional queue ownership does not make independent writers safe. +Ask your operator to [enable ingestion webhooks](/content/operate/iris/agent-memory/self-managed/configuration.md#enable-ingestion-webhooks) and configure the signing-secret backend. ## Configure a store policy -Use the self-managed Control Plane REST API and an operator credential. The Data Plane SDKs expose status reads; `AgentMemoryService` does not currently expose hook-policy management. Set `AGENT_MEMORY_CONTROL_PLANE_URL`, `AGENT_MEMORY_SERVICE_TOKEN`, `STORE_ID`, and `HOOK_URL`. For the receiver example, use `https://:8443/hook`, or the HTTPS ingress URL that forwards to it. - -A policy PUT replaces the whole policy. Include every phase you want to retain; omitting a phase removes it. Read the current revision before writing: - -```sh -curl --fail-with-body --silent --show-error \ - --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ - "$AGENT_MEMORY_CONTROL_PLANE_URL/v1/stores/$STORE_ID/ingestion-hooks" > hook-config.json -``` - -A new store starts at revision 0. Generate separate 32-byte signing keys for the two phases, then configure them with delivery still disabled: - -```sh -export HOOK_INPUT_KEY="$(openssl rand -base64 32)" -export HOOK_OUTPUT_KEY="$(openssl rand -base64 32)" - -jq --arg url "$HOOK_URL" \ - '{expectedRevision:.revision,enabled:false,beforeInput:{url:$url,paths:["session.append","memory.create","document.submit"],signingKey:env.HOOK_INPUT_KEY},beforeApply:{url:$url,paths:["session.builtin","session.custom","document.chunk"],signingKey:env.HOOK_OUTPUT_KEY}}' \ - hook-config.json | curl --fail-with-body --silent --show-error --request PUT \ - --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ - --header 'Content-Type: application/json' --data @- \ - "$AGENT_MEMORY_CONTROL_PLANE_URL/v1/stores/$STORE_ID/ingestion-hooks" > hook-config-new.json -``` - -The response returns each phase's `keyId` and `signingKeyConfigured`, never its key. Load those IDs and original keys into the receiver. A receiver that accepts the JSON map used in the next section can get it from: - -```sh -export HOOK_KEYS_JSON="$(jq -c '{(.beforeInput.keyId):env.HOOK_INPUT_KEY,(.beforeApply.keyId):env.HOOK_OUTPUT_KEY}' hook-config-new.json)" -``` - -After the receiver is ready, enable the same policy using the returned revision and omitting signing keys to retain them: - -```sh -jq '{expectedRevision:.revision,enabled:true,beforeInput:{url:.beforeInput.url,paths:.beforeInput.paths},beforeApply:{url:.beforeApply.url,paths:.beforeApply.paths}}' \ - hook-config-new.json | curl --fail-with-body --silent --show-error --request PUT \ - --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ - --header 'Content-Type: application/json' --data @- \ - "$AGENT_MEMORY_CONTROL_PLANE_URL/v1/stores/$STORE_ID/ingestion-hooks" | jq -``` - -A stale `expectedRevision` returns `409`. To rotate a key, supply a new `signingKey` and load its returned ID into the receiver. Retain the previous receiver key for 24 hours: pending work keeps its original endpoint and key version. Policy changes affect new work; they do not rewrite saved decisions or a pending run's policy. - -Keep the previous endpoint reachable while its pending work completes; delivery does not follow a redirect to the replacement URL. +Your operator must [configure a store policy](/content/operate/iris/agent-memory/self-managed/api-examples.md#configure-a-webhook-policy), then provide the receiver with its signing keys and key IDs through a secret store. The receiver example reads them from `HOOK_KEYS_JSON`. Enable delivery after the receiver is ready. ## Verify callbacks and return decisions @@ -128,7 +61,14 @@ v1= Read the timestamp, event ID, key ID, and signature from `X-Redis-Webhook-Timestamp`, `X-Redis-Webhook-Event-Id`, `X-Redis-Webhook-Key-Id`, and `X-Redis-Webhook-Signature`. Check freshness and use a constant-time signature comparison. Retries keep the event ID and raw body but receive a fresh signed timestamp. -This runnable receiver uses FastAPI and the Python standard library. It rejects the demonstration marker `[reject]` and accepts other signed content. Replace that marker policy with your application's policy. Save as `receiver.py`: +This runnable receiver uses FastAPI and the Python standard library. It: + +1. Checks the four signature headers and the timestamp. +1. Verifies the HMAC signature over the raw body. +1. Returns the saved decision if the event and body match a previous request. +1. Rejects content containing `[reject]` and accepts other signed content. + +Replace the demonstration marker policy with your application's policy. Save as `receiver.py`: ```python import base64 @@ -194,8 +134,8 @@ async def hook(request: Request): if payload.get("phase") not in {"before_input", "before_apply"}: raise HTTPException(400, "Invalid phase") - # ponytail: one process and at most 1000 decisions; use a durable shared - # decision store before adding replicas or policies with external effects. + # Demo only: one process and at most 1,000 saved decisions. + # Use a durable shared store before adding replicas or external effects. now = time.time() for identity, saved in list(decisions.items()): if saved[2] <= now: @@ -244,7 +184,13 @@ A document-input transform replaces its decoded text: {"version":"1","decision":"transform","replacement":{"content":"Approved document text."}} ``` -Session-input transforms can change `actorId`, `role`, `content`, `createdAt`, and `metadata`, while preserving session identity, owner, and placement. Manual-create transforms preserve record count, order, and IDs. They can change editable text, type, session, owner, namespace placement, topics, and attributes. Do not supply both `namespace` and `namespaceRef`. +| Path | What a transform can change | +|:-----|:----------------------------| +| `document.submit` | The decoded document text in `replacement.content`. | +| `session.append` | `actorId`, `role`, `content`, `createdAt`, and `metadata`. Session identity, owner, and placement stay fixed. | +| `memory.create` | Editable text, type, session, owner, namespace placement, topics, and attributes. Record count, order, and IDs stay fixed. | + +Do not supply both `namespace` and `namespaceRef`. Transformed records must still pass authorization and validation. For `memory.create`, `session.builtin`, `session.custom`, and `document.chunk`, `decision: review` can decide each operation separately: @@ -304,7 +250,7 @@ Session summary and property hooks also apply to worker writes. A rejected summa | `429` | Hook capacity is exhausted. Apply backoff and inspect pending work. | | Bulk success with item errors | Check every record's result; HTTP success does not mean all records were applied. | -Decision `state` and `applyState` are separate. Inspect `applied`, `filtered`, `conflicts`, attempts, safe errors, and retry times. A later failure or an incomplete completion record does not prove earlier writes were rolled back. There is no manual redrive endpoint. +Decision `state` and `applyState` are separate. Inspect `applied`, `filtered`, `conflicts`, attempts, safe errors, and retry times. A later failure or an incomplete completion record does not prove earlier writes were rolled back. You cannot manually retry a stopped operation through a retry endpoint. Use the Data Plane connection values from the [quickstart](/content/develop/ai/context-engine/agent-memory/quickstart.md). Set `HOOK_EVENT_ID` to inspect one known event, or omit it to list events. Add `USER_TOKEN` for delegated access: diff --git a/content/develop/ai/context-engine/agent-memory/list-sessions.md b/content/develop/ai/context-engine/agent-memory/list-sessions.md index 13aaef1034..fc11e27177 100644 --- a/content/develop/ai/context-engine/agent-memory/list-sessions.md +++ b/content/develop/ai/context-engine/agent-memory/list-sessions.md @@ -11,7 +11,7 @@ title: List and sort sessions weight: 10 --- -Use session listing to let a user reopen an earlier conversation. The session rows contain IDs and available owner, timestamp, and namespace metadata. They do not contain conversation events or summary text. Retrieve the selected session with `GetSessionMemory` to build the agent's conversation context. +Use session listing to let a user reopen an earlier conversation. The session rows contain IDs and available owner, timestamp, and namespace metadata. They do not contain conversation events or summary text. Retrieve the selected session with `get_session_memory` (Python), `getSessionMemory` (TypeScript), or `GET /v1/stores/{storeId}/session-memory/{sessionId}` to build the agent's conversation context. ## Choose a listing operation @@ -20,7 +20,7 @@ Use session listing to let a user reopen an earlier conversation. The session ro | Session IDs | `GET /v1/stores/{storeId}/session-memory` | `list_sessions` | `listSessions` | | Session rows with sorting | `GET /v1/stores/{storeId}/sessions` | `list_session_summaries` | `listSessionSummaries` | -The second method's name refers to metadata rows, not generated conversation summaries. The ID-only operation remains available. Both operations return `items`, `total`, and an optional `nextPageToken`. +The second method's name refers to metadata rows, not generated conversation summaries. Both operations return `items`, `total`, and an optional `nextPageToken`. For a user's personal conversations, set `filterOwnerId` to the user's authenticated ID. The session owner is the first event's `actorId`. Add `namespaceRef` to require an exact namespace ID as well. It does not include descendants, deprecated namespace labels, or sessions without a namespace resource. Owner and namespace matching is case-sensitive. @@ -30,7 +30,7 @@ A request without filters must set `includeAll=true`. Do not combine `includeAll The default is `sortBy=updatedAt` and `sortOrder=desc`, so recent activity appears first. `sortBy` accepts `sessionId`, `createdAt`, or `updatedAt`. When omitted, `sortOrder` is `asc` for `sessionId` and `desc` for timestamps. `limit` accepts 1–1000 and defaults to 100. -Install `redis-agent-memory` for Python or `@redis-iris/agent-memory` for TypeScript. Use a service that supports the endpoint. Set `AGENT_MEMORY_URL`, `STORE_ID`, `API_KEY`, and `OWNER_ID` in your shell. See the [developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md#connect-to-a-redis-agent-memory-service) for connection details. +Install `redis-agent-memory` for Python or `@redis-iris/agent-memory` for TypeScript. Set `AGENT_MEMORY_URL`, `STORE_ID`, `API_KEY`, and `OWNER_ID` in your shell. See the [developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md#connect-to-a-redis-agent-memory-service) for connection details. These examples list all pages for one owner, with the most recently updated sessions first: @@ -40,16 +40,14 @@ These examples list all pages for one owner, with the most recently updated sess import asyncio import os -import httpx from redis_agent_memory import AgentMemory async def main(): - headers = {"X-Iris-User-Token": os.environ["USER_TOKEN"]} if os.environ.get("USER_TOKEN") else {} - async with httpx.AsyncClient(headers=headers) as transport, AgentMemory( + async with AgentMemory( os.environ["AGENT_MEMORY_URL"], store_id=os.environ["STORE_ID"], - api_key=os.environ["API_KEY"], async_client=transport, + api_key=os.environ["API_KEY"], ) as agent_memory: page_token = None while True: @@ -72,28 +70,25 @@ asyncio.run(main()) ```typescript import { AgentMemory } from "@redis-iris/agent-memory"; -import { HTTPClient } from "@redis-iris/agent-memory/lib/http"; async function main() { const { AGENT_MEMORY_URL, STORE_ID, API_KEY, OWNER_ID } = process.env; if (!AGENT_MEMORY_URL || !STORE_ID || !API_KEY || !OWNER_ID) { throw new Error("Set AGENT_MEMORY_URL, STORE_ID, API_KEY, and OWNER_ID."); } - const httpClient = new HTTPClient(); - const userToken = process.env.USER_TOKEN; - if (userToken) { - httpClient.addHook("beforeRequest", (request) => { - request.headers.set("X-Iris-User-Token", userToken); - return request; - }); - } const agentMemory = new AgentMemory({ - serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY, httpClient, + serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY, }); let pageToken: string | undefined; do { const page = await agentMemory.listSessionSummaries( - 20, pageToken, OWNER_ID, undefined, undefined, "updatedAt", "desc", + 20, // limit + pageToken, // pageToken + OWNER_ID, // filterOwnerId + undefined, // namespaceRef + undefined, // includeAll + "updatedAt", // sortBy + "desc", // sortOrder ); console.dir(page.items, { depth: null }); pageToken = page.nextPageToken; @@ -110,7 +105,6 @@ page_token='' while :; do page=$(curl --fail-with-body --silent --show-error --get \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --data-urlencode "filterOwnerId=$OWNER_ID" \ --data-urlencode 'sortBy=updatedAt' --data-urlencode 'sortOrder=desc' \ --data-urlencode 'limit=20' --data-urlencode "pageToken=$page_token" \ @@ -125,6 +119,4 @@ done Pass each returned `nextPageToken` as `pageToken`. Keep filters and sort options unchanged; the token is bound to them. Stop when the token is absent. The token is opaque, so URL-encode it in REST requests. Changing a filter or sort requires a new request without the old token. -Legacy sessions can omit `ownerId`, `createdAt`, or `updatedAt`. Handle those missing values when rendering a conversation list. A row's timestamps describe session metadata; they do not promise that background extraction or summarization has completed. - -TypeScript uses positional arguments. `listSessionSummaries` takes namespace filtering fourth, followed by `includeAll`, `sortBy`, and `sortOrder`. The older `listSessions` method keeps its previous argument order: `includeAll` is fourth, request options fifth, and the new namespace filter sixth. Do not copy one method's argument positions to the other. +Sessions stored without owner or timestamp metadata can omit `ownerId`, `createdAt`, or `updatedAt`. Handle those missing values when rendering a conversation list. A row's timestamps describe session metadata; they do not promise that background extraction or summarization has completed. diff --git a/content/develop/ai/context-engine/agent-memory/long-term-memory.md b/content/develop/ai/context-engine/agent-memory/long-term-memory.md index ed8d344274..0bad82eac0 100644 --- a/content/develop/ai/context-engine/agent-memory/long-term-memory.md +++ b/content/develop/ai/context-engine/agent-memory/long-term-memory.md @@ -15,6 +15,17 @@ aliases: Long-term memory stores information that remains useful beyond one conversation. Use automatic extraction or direct creation to store memories, then search for information relevant to an agent turn. +## Create long-term memories + +Redis Agent Memory provides two ways to create memories: + +* **Automatic extraction:** Redis Agent Memory processes session events asynchronously and creates durable memories from relevant information. Configure the extraction cadence to control how often session events are processed. +* **Direct creation:** Your application creates one or more long-term memories through the application programming interface (API) or a software development kit (SDK). Use direct creation when importing existing information or when your application determines exactly what to store. + +Configure the long-term-memory time to live (TTL) separately from the session-memory TTL. + +See [memory configuration]({{< relref "/operate/iris/agent-memory/create-service#memory-configuration" >}}) to configure the extraction cadence and long-term-memory TTL in Redis Cloud. + ## Memory fields When you create a memory directly, you must supply `id`, `text`, and `ownerId`. The server sets `createdAt` and `updatedAt`. During automatic extraction, the server creates the memory ID and content and copies the session owner into `ownerId`. Use that same stable user ID when querying personal memories; see [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). @@ -34,17 +45,6 @@ A long-term memory record includes: | `createdAt` | Time the memory was created. | | `updatedAt` | Time the memory was last updated. | -## Create long-term memories - -Redis Agent Memory provides two ways to create memories: - -* **Automatic extraction:** Redis Agent Memory processes session events asynchronously and creates durable memories from relevant information. Configure the extraction cadence to control how often session events are processed. -* **Direct creation:** Your application creates one or more long-term memories through the application programming interface (API) or a software development kit (SDK). Use direct creation when importing existing information or when your application determines exactly what to store. - -Configure the long-term-memory time to live (TTL) separately from the session-memory TTL. - -See [memory configuration]({{< relref "/operate/iris/agent-memory/create-service#memory-configuration" >}}) to configure the extraction cadence and long-term-memory TTL in Redis Cloud. - ## Search long-term memory Search long-term memory semantically and narrow results with filters. Scope results with filters for owners, sessions, namespaces, topics, and memory types. @@ -57,6 +57,29 @@ For domain-specific fields and extraction instructions, see [Custom memory types For controls that apply during automatic extraction, see [Exclude sensitive data from automatic extraction]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#exclude-sensitive-data-from-automatic-extraction" >}}). +### Search by meaning + +Use the same connection values and owner ID as the [quickstart](/content/develop/ai/context-engine/agent-memory/quickstart.md). This example finds memories about the user’s food preferences: + +{{< clients-example set="agent_memory_sdk" step="search_long_term_memory_basic" footer="hide" dft_tab_name="curl" runnable="false" try_it="false" >}} +curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data @- \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/search" <}} + ### Filter and sort without semantic search Omit `text` to retrieve records by filters without a semantic query. For example, retrieve the user's most recently updated memories: @@ -89,7 +112,6 @@ Omit `text` to retrieve records by filters without a semantic query. For example curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data "$(jq -n --arg owner "$OWNER_ID" \ '{filter: {ownerId: {eq: $owner}}, sortBy: "updatedAt", sortOrder: "desc", limit: 10}')" \ @@ -104,12 +126,28 @@ Use the client and identifiers from the [quickstart](/content/develop/ai/context Top-level filters use `filterOp: "all"` (AND) by default; `"any"` uses OR. To restrict a query to a user, keep the owner condition mandatory. Do not use `"any"` with separate owner and namespace conditions when both must match. These retrieval filters do not replace application access checks. -Set `filter.unnamespaced: true` to match records with neither a legacy namespace label nor a namespace resource. It cannot be combined with `namespace` or `namespaceRef`, and remains mandatory even with `filterOp: "any"`. +Set `filter.unnamespaced: true` to match records with neither the older `namespace` string label nor a namespace resource. It cannot be combined with `namespace` or `namespaceRef`, and remains mandatory even with `filterOp: "any"`. [MemoryETL](/content/develop/ai/context-engine/agent-memory/memory-etl.md) outputs are included in search by default. Set top-level `excludeDocumentManaged: true` to omit them. Returned `documentManaged` records cannot be overwritten through generic memory creation or PATCH requests; use the document-ingestion workflow to manage their content. For custom fields, use `filter.attributes`, keyed by the field name, with the operators described in [`AttributeFilterClause`](/content/develop/ai/context-engine/agent-memory/api-reference.md#operation/SearchLongTermMemory). Combine it with `memoryType` to select the registered custom type. +For example, this search request combines the owner, type, and dietary requirement with `filterOp: "all"`. A `list` clause requires every listed value to be present: + +```json +{ + "filterOp": "all", + "filter": { + "ownerId": {"eq": ""}, + "memoryType": {"eq": "trip_preference"}, + "attributes": {"dietary_requirements": {"list": ["vegetarian"]}} + }, + "limit": 10 +} +``` + +Use exactly one typed clause per attribute: `string`, `number`, `boolean`, or `list`, matching the registered field type. + ## Custom memory types Custom memory types capture structured information specific to your business domain. @@ -141,15 +179,49 @@ When this type is enabled, Redis Agent Memory can extract a structured `trip_pre ### Extract custom memories -Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first. Reuse its client, connection values, and user and session identifiers. Add Python snippets inside the `with` block and TypeScript snippets inside `run`. Run curl commands in the same shell. Run each write once; comment out completed writes and their output statements before rerunning an SDK file. +{{< embed-md "agent-memory-continue-from-quickstart.md" >}} -Custom memory types let an application extract structured information for its business domain. Configure and enable the `trip_preference` type with the fields in [Define a memory type]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory#define-a-memory-type" >}}). Use this extraction prompt: +In the Redis Cloud console, open your service’s **Configuration** tab, select **Edit**, and under **Memory types & extraction** select **Add type**. Configure and enable `trip_preference` with the fields in [Define a memory type]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory#define-a-memory-type" >}}). Use this extraction prompt: ```text Extract trip requirements only when the user states a destination or travel plan. Preserve explicit dietary requirements and food preferences. ``` -After enabling the type, add the travel message from the quickstart to a new session. Wait at least one extraction interval before searching. +After enabling the type, add a travel message to a new session. Run this write once, then wait at least one extraction interval before searching. + +{{< multitabs id="custom-memory-event" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + agent_memory.add_session_event( + session_id="trip-preference-session", actor_id=USER_ID, + role=models.MessageRole.USER, + content=[models.Text(text="I am visiting Tokyo and Kyoto next month. I am vegetarian.")], + created_at=datetime.now(timezone.utc), + ) +``` + +-tab-sep- + +```typescript + await agentMemory.addSessionEvent({ + sessionId: "trip-preference-session", actorId: userId, role: "USER", + content: [{ text: "I am visiting Tokyo and Kyoto next month. I am vegetarian." }], + createdAt: new Date(), + }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data "$(jq -n --arg owner "$OWNER_ID" --arg now "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ + '{sessionId:"trip-preference-session",actorId:$owner,role:"USER",content:[{text:"I am visiting Tokyo and Kyoto next month. I am vegetarian."}],createdAt:$now}')" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/events" | jq +``` + +{{< /multitabs >}} Search for the structured memory: @@ -190,7 +262,6 @@ Search for the structured memory: curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data @- \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/search" <}} -Inspect the bulk response for per-record errors. Run the custom-memory search again to retrieve the record. Direct creation does not wait for background extraction and does not apply the extraction prompt or sensitive-data exclusions. Run this write once; the memory ID identifies the record within the store. +Inspect the bulk response for per-record errors. Run the custom-memory search again to retrieve the record. Direct creation does not wait for background extraction and does not apply the extraction prompt or sensitive-data exclusions. Creating an eligible record with the same `id` again replaces that record. Ownership checks and protection for document-managed records still apply. See [custom memory types]({{< relref "/operate/iris/agent-memory/create-service#custom-memory-types" >}}) for configuration requirements and limits. ## Test semantic exclusions -Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first. Reuse its client, connection values, and user and session identifiers. Add Python snippets inside the `with` block and TypeScript snippets inside `run`. Run curl commands in the same shell. Run each write once; comment out completed writes and their output statements before rerunning an SDK file. +{{< embed-md "agent-memory-continue-from-quickstart.md" >}} -In the service configuration, enable **Semantic exclusions** and set this prompt: +In the Redis Cloud console, open your service’s **Configuration** tab and select **Edit**. Under **Sensitive-data exclusions**, enable **Semantic exclusions** and set this prompt: ```text Do not keep passwords, access tokens, recovery codes, payment card information, or booking confirmation codes in long-term memory. @@ -409,7 +479,6 @@ export EVENT_CREATED_AT="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data @- \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/events" <}} - -```python -import asyncio -import os - -from redis_agent_memory import AgentMemoryService - - -async def main(): - async with AgentMemoryService( - server_url=os.environ["AGENT_MEMORY_CONTROL_PLANE_URL"], - token=os.environ["AGENT_MEMORY_SERVICE_TOKEN"], - ) as service: - extractor = await service.create_extractor_async( - store_id=os.environ["STORE_ID"], - name="Travel guide facts", memory_type="document_fact", - source="documents", strategy="custom", - prompt="Extract useful travel facts. Keep each fact in one memory.", - ) - print(extractor.model_dump_json(by_alias=True)) - - -asyncio.run(main()) -``` - --tab-sep- - -```typescript -import { AgentMemoryService } from "@redis-iris/agent-memory"; - -async function main() { - const { AGENT_MEMORY_CONTROL_PLANE_URL, AGENT_MEMORY_SERVICE_TOKEN, STORE_ID } = process.env; - if (!AGENT_MEMORY_CONTROL_PLANE_URL || !AGENT_MEMORY_SERVICE_TOKEN || !STORE_ID) { - throw new Error("Set the Control Plane connection values and STORE_ID."); - } - const service = new AgentMemoryService({ - serverURL: AGENT_MEMORY_CONTROL_PLANE_URL, token: AGENT_MEMORY_SERVICE_TOKEN, - }); - console.dir(await service.createExtractor(STORE_ID, { - name: "Travel guide facts", memoryType: "document_fact", - source: "documents", strategy: "custom", - prompt: "Extract useful travel facts. Keep each fact in one memory.", - }), { depth: null }); -} - -main().catch((error) => { console.error(error instanceof Error ? error.message : "Request failed"); process.exitCode = 1; }); -``` - --tab-sep- - -```sh -curl --fail-with-body --silent --show-error --request POST \ - --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ - --header 'Content-Type: application/json' \ - --data '{"name":"Travel guide facts","memoryType":"document_fact","source":"documents","strategy":"custom","prompt":"Extract useful travel facts. Keep each fact in one memory."}' \ - "$AGENT_MEMORY_CONTROL_PLANE_URL/v2/memory/stores/$STORE_ID/extractors" | jq -``` - -{{< /multitabs >}} - -Set `destinationNamespaceId` when creating an extractor to place its output in one namespace in the same store. Use a stable namespace ID, not a display name. Personal destinations must be compatible with the document owner; shared placement does not make the original document accessible to every reader of the extracted memories. - -List or read extractors before editing them. Updates and removal require the current `expectedRevision`; a stale revision returns `409`. An update can change the name, prompt, enabled flag, or destination. An empty destination clears it; omission keeps it. Accepted ingestions retain their extractor snapshot, so a later edit does not change an existing run. +Ask your operator to [register a memory type and create a document extractor](/content/operate/iris/agent-memory/self-managed/api-examples.md#create-an-extractor). Set `EXTRACTOR_ID` to the returned extractor ID. Applications use their Data Plane credential to submit documents. ## Submit and track a document @@ -122,7 +43,19 @@ Kyoto has vegetarian restaurants near its central railway station. Only `text/plain` and `text/markdown` are accepted. Encode the UTF-8 bytes as base64 **once** before passing `content` to REST or an SDK. The SDK expects the encoded string, not a file object or raw text. -Each submission requires `documentId`, `revision`, `content`, `mediaType`, `ownerId`, `extractorId`, and `idempotencyKey`. The document ID identifies a logical document. Its revision is immutable and case-sensitive; use a new revision when its content changes. Neither ID is the server-generated ingestion ID. +Each submission requires these fields: + +| Field | Purpose | Example | +|:------|:--------|:--------| +| `documentId` | Your ID for the logical document. | `travel-guide` | +| `revision` | An immutable, case-sensitive version. Use a new value when the content changes. | `1` | +| `content` | The file's UTF-8 bytes, base64-encoded once. | The examples encode `travel-guide.md`. | +| `mediaType` | The document's content type. | `text/plain` or `text/markdown` | +| `ownerId` | The authenticated user the memories belong to. | Your verified `OWNER_ID`. | +| `extractorId` | The extractor's returned `id`. | `$EXTRACTOR_ID` | +| `idempotencyKey` | A key to reuse when retrying the same submission. | `travel-guide-revision-1` | + +The server returns a separate ingestion ID to track the run. The examples submit the file, poll for a terminal processing state, and list output memory IDs. Save the ingestion ID even if polling times out: diff --git a/content/develop/ai/context-engine/agent-memory/namespace-routing.md b/content/develop/ai/context-engine/agent-memory/namespace-routing.md index c5b10f4d5d..734f67516f 100644 --- a/content/develop/ai/context-engine/agent-memory/namespace-routing.md +++ b/content/develop/ai/context-engine/agent-memory/namespace-routing.md @@ -13,7 +13,7 @@ weight: 14 Namespace routing selects a shared namespace for an eligible memory. Admission rules check whether a memory can enter a destination. Use routing to organize shared memories by content, and admission rules to reject content that does not belong in a namespace. -The policy applies to one store. It targets existing shared namespace resources, using their stable `namespaceId` values. It does not create namespaces or change access permissions. Your service must support namespace routing and have it enabled. +The policy applies to one store. It targets existing shared namespace resources, using their stable `namespaceId` values. It does not create namespaces or change access permissions. For self-managed deployments, follow [Enable namespace routing](/content/operate/iris/agent-memory/self-managed/configuration.md#enable-namespace-routing), then read and configure the store policy. ## Understand placement and admission @@ -27,15 +27,13 @@ The policy applies to one store. It targets existing shared namespace resources, An automatic move keeps the current placement and returns a per-item error if routing is disabled, no destination qualifies, or the memory is personal or has no namespace. Inspect both `moved` and `errors` in a move response. Bulk creation also reports failures per record in `errors`. An explicit move to an archived namespace returns `409 Conflict`. -An enabled `accept` rule selects a candidate destination. Accept rules run in list order. Every enabled `reject` rule for that destination applies, including rules later in the list. A matching reject vetoes the candidate. Automatic routing then tries another candidate; explicit placement fails rather than choosing another namespace. - -Strategies within a rule run in list order; the first match fires the rule. An accept rule with no strategies always matches. A reject rule with no strategies never matches. Use `matcher_spec` with a `regex` matcher for text patterns, or `llm_prompt` with `llmPrompt` for a model-evaluated instruction. Each strategy must contain only the block for its declared `kind`. - ## Configure a policy -Create a shared namespace with `CreateNamespace`, and save its returned ID as `SHARED_NAMESPACE_ID`. Use `scope: "SHARED"` and omit `ownerId`. Install the software development kit (SDK) for your language, then set `AGENT_MEMORY_URL`, `STORE_ID`, and `API_KEY` as described in the [developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md#connect-to-a-redis-agent-memory-service). +1. [Create a shared namespace](/content/develop/ai/context-engine/agent-memory/namespaces.md#create-a-shared-namespace) and save its ID as `SHARED_NAMESPACE_ID`. +1. Install the software development kit (SDK) for your language. +1. Set `AGENT_MEMORY_URL`, `STORE_ID`, and `API_KEY` as described in the [developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md#connect-to-a-redis-agent-memory-service). -This example accepts text containing `travel` into that namespace. The regular expression is case-insensitive. **PUT replaces the entire policy**, so read the current policy first and retain any rules you still need. +This example accepts text containing `travel` into that namespace. The regular expression is case-insensitive. **PUT replaces the entire policy.** This example installs only the `travel` rule. On a store with existing rules, include those you want to keep in the `rules` array before sending the PUT. Reading the current policy does not merge it into the example automatically. {{< multitabs id="namespace-routing-policy" tab1="Python" tab2="TypeScript" tab3="curl" >}} @@ -43,16 +41,14 @@ This example accepts text containing `travel` into that namespace. The regular e import asyncio import os -import httpx from redis_agent_memory import AgentMemory async def main(): - headers = {"X-Iris-User-Token": os.environ["USER_TOKEN"]} if os.environ.get("USER_TOKEN") else {} - async with httpx.AsyncClient(headers=headers) as transport, AgentMemory( + async with AgentMemory( os.environ["AGENT_MEMORY_URL"], store_id=os.environ["STORE_ID"], - api_key=os.environ["API_KEY"], async_client=transport, + api_key=os.environ["API_KEY"], ) as agent_memory: current = await agent_memory.get_namespace_routing_async() print(current.model_dump_json(by_alias=True, indent=2)) @@ -79,23 +75,14 @@ asyncio.run(main()) ```typescript import { AgentMemory } from "@redis-iris/agent-memory"; -import { HTTPClient } from "@redis-iris/agent-memory/lib/http"; async function main() { const { AGENT_MEMORY_URL, STORE_ID, API_KEY, SHARED_NAMESPACE_ID } = process.env; if (!AGENT_MEMORY_URL || !STORE_ID || !API_KEY || !SHARED_NAMESPACE_ID) { throw new Error("Set AGENT_MEMORY_URL, STORE_ID, API_KEY, and SHARED_NAMESPACE_ID."); } - const httpClient = new HTTPClient(); - const userToken = process.env.USER_TOKEN; - if (userToken) { - httpClient.addHook("beforeRequest", (request) => { - request.headers.set("X-Iris-User-Token", userToken); - return request; - }); - } const agentMemory = new AgentMemory({ - serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY, httpClient, + serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY, }); console.dir(await agentMemory.getNamespaceRouting(), { depth: null }); const result = await agentMemory.putNamespaceRouting({ @@ -122,13 +109,11 @@ main().catch((error) => { console.error(error instanceof Error ? error.message : ```sh curl --fail-with-body --silent --show-error \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespace-routing" | jq curl --fail-with-body --silent --show-error \ --request PUT \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data "$(jq -n --arg ns "$SHARED_NAMESPACE_ID" \ '{policy: {enabled: true, rules: [{id: "travel", enabled: true, @@ -140,11 +125,36 @@ curl --fail-with-body --silent --show-error \ {{< /multitabs >}} +## How rules are evaluated + +In this example, `travel` is an accept rule with one strategy, a regex matcher. + +An enabled `accept` rule selects a candidate destination. Accept rules run in list order. Every enabled `reject` rule for that destination applies, including rules later in the list. A matching reject vetoes the candidate. Automatic routing then tries another candidate; explicit placement fails rather than choosing another namespace. + +Strategies within a rule run in list order; the first match fires the rule. An accept rule with no strategies always matches. A reject rule with no strategies never matches. Use `matcher_spec` with a `regex` matcher for text patterns, or `llm_prompt` with `llmPrompt` for a model-evaluated instruction. Each strategy must contain only the block for its declared `kind`. + A store without a saved policy returns an empty, disabled policy. Regular expressions use RE2 syntax. Lookaround, backreferences, invalid expressions, and patterns that can match empty text are not supported. A policy supports up to 32 rules and 8 strategies per rule. See [`PutNamespaceRouting`](/content/develop/ai/context-engine/agent-memory/api-reference.md#operation/PutNamespaceRouting) for validation and error responses. ## Move memories with the policy -To evaluate the policy for an existing shared memory, omit the destination from `MoveLongTermMemories`. Use IDs returned by a previous create or search request: +To evaluate the policy for an existing shared memory, omit the destination from the move request. First create a test memory in another shared namespace. Run this setup once, after setting `OWNER_ID` to your application user: + +```sh +SOURCE_NAMESPACE_ID=$(curl --fail-with-body --silent --show-error --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data '{"name":"routing-source","scope":"SHARED"}' \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespaces" | jq -er '.namespace.namespaceId') || exit 1 +export MEMORY_ID='routing-travel-example' +curl --fail-with-body --silent --show-error --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data "$(jq -n --arg id "$MEMORY_ID" --arg owner "$OWNER_ID" --arg ns "$SOURCE_NAMESPACE_ID" \ + '{memories:[{id:$id,ownerId:$owner,text:"Travel policy allows train tickets.",namespaceRef:{namespaceId:$ns}}]}')" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory" | jq +``` + +Continue only if the namespace request succeeds and the memory response has no per-record errors. Run the move in the same shell, or pass the exported `MEMORY_ID` to your SDK process: {{< multitabs id="namespace-routing-move" tab1="Python" tab2="TypeScript" tab3="curl" >}} @@ -170,7 +180,6 @@ To evaluate the policy for an existing shared memory, omit the destination from curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data "$(jq -n --arg id "$MEMORY_ID" '{memoryIds: [$id]}')" \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/move" | jq diff --git a/content/develop/ai/context-engine/agent-memory/namespaces.md b/content/develop/ai/context-engine/agent-memory/namespaces.md index 3fe316b2ba..52ee5bacb7 100644 --- a/content/develop/ai/context-engine/agent-memory/namespaces.md +++ b/content/develop/ai/context-engine/agent-memory/namespaces.md @@ -15,85 +15,11 @@ Namespaces are optional. Use them to group memories for a project, team, or user Create the namespace before writing to it. The response contains a server-generated `namespaceId`. Save this ID and use it in `namespaceRef` on session events and long-term memory writes. You will also use this ID to search for memories in that namespace. Names and paths can change when you rename a namespace, but its ID stays the same. -## Create a namespace hierarchy - -Choose a root scope when you create a namespace: - -| Scope | Creation fields | Example use | -|:------|:----------------|:------------| -| `PERSONAL` | `name`, `scope`, and `ownerId` | A user's travel plans. | -| `SHARED` | `name` and `scope`, without `ownerId` | Information used across a team. | - -Create a child with `name` and `parentId`. You can also create a hierarchy with `path`, such as `travel/japan`, and the root scope. Supply `ownerId` for a personal path. Missing path segments are created as needed. Do not combine `path` with `name` or `parentId`. - -Names and paths are case-sensitive. The path does not encode scope. Use the returned ID to distinguish namespaces that have the same path under different owners or scopes. - -For runnable examples, see [Create a personal namespace]({{< relref "/develop/ai/context-engine/agent-memory/namespaces#create-a-personal-namespace" >}}) or [Create a shared namespace]({{< relref "/develop/ai/context-engine/agent-memory/namespaces#create-a-shared-namespace" >}}). - -## Place and retrieve memories - -Pass `namespaceRef: {"namespaceId": ""}` when you start a session. Memories extracted from the session use that namespace. Use a new session ID when adding a namespace to the quickstart example. - -For direct long-term memory creation, set `namespaceRef` on each record. To move existing records, use `MoveLongTermMemories` with their IDs and the destination `namespaceRef`. Check both `moved` and `errors` in the response before treating a batch as complete. - -Search with `filter.namespaceRef.eq` for one namespace or `filter.namespaceRef.in` for several IDs. These filters match exact namespace IDs; they do not expand a parent into its descendants. List the children and include their IDs when you need to search several levels of a hierarchy. Keep the `ownerId` filter when recalling one user's memories. - -Without server-side ownership enforcement, your application must check that the current user can access each namespace before querying it. With enforcement enabled, personal namespace access follows the root owner's authenticated User ID. Shared namespaces are accessible to callers with the required store permission; enforcement does not add team membership checks. If a shared namespace is only for one team, enforce that rule in your application or use separate stores and grants. See [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). - -## Manage namespaces - -| Operation | Behavior | -|:----------|:---------| -| List | List roots, or pass `parentId` to list direct children. Sort by `id`, `name`, `createdAt`, or `updatedAt` with `sortBy`; `sortOrder` accepts `asc` or `desc`. Defaults are `id` and `asc`. Follow `nextPageToken` with the same filters and sort options. | -| Get | Retrieve the current name, path, scope, and state by ID. | -| Rename | Update `name`; continue using the same ID for placement and retrieval. | -| Archive | Set `state` to `ARCHIVED` to stop new children and placements. The update application programming interface (API) does not offer an unarchive transition. | -| Delete | Delete an empty leaf namespace. A namespace with children or memory placements returns `409 Conflict`. | - -Creating a namespace at an occupied location returns `409 Conflict`, including when the existing namespace is archived. Save and reuse the returned ID instead of creating the same namespace on each agent turn. For request fields and error responses, see the [namespace API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}#operation/CreateNamespace). - -## Sort namespaces - -Use the client from the [quickstart](/content/develop/ai/context-engine/agent-memory/quickstart.md). This example lists shared roots by name: - -{{< multitabs id="namespace-sort" tab1="Python" tab2="TypeScript" tab3="curl" >}} - -```python - namespaces = agent_memory.list_namespaces( - scope="SHARED", limit=20, sort_by="name", sort_order="asc", - ) - show("Shared namespaces", namespaces) -``` - --tab-sep- - -```typescript - const namespaces = await agentMemory.listNamespaces( - undefined, "SHARED", undefined, 20, undefined, undefined, "name", "asc", - ); - console.dir(namespaces, { depth: null }); -``` - --tab-sep- - -```sh -curl --fail-with-body --silent --show-error --get \ - --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ - --data-urlencode 'scope=SHARED' --data-urlencode 'limit=20' \ - --data-urlencode 'sortBy=name' --data-urlencode 'sortOrder=asc' \ - "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespaces" | jq -``` - -{{< /multitabs >}} - -Add the Python snippet inside the `with` block and the TypeScript snippet inside `run`. In TypeScript, request options remain the sixth argument; `sortBy` and `sortOrder` are seventh and eighth. Name sorting uses the display name with Redis text collation, rather than the hierarchy path. Equal sort values use namespace ID ascending as a tie-breaker. - ## Create a personal namespace -Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first. Reuse its client, connection values, and user and session identifiers. Add Python snippets inside the `with` block and TypeScript snippets inside `run`. Run curl commands in the same shell. Run each write once; comment out completed writes and their output statements before rerunning an SDK file. +{{< embed-md "agent-memory-continue-from-quickstart.md" >}} -Namespaces are optional. This example uses a personal namespace to group the trip memories. +This example uses a personal namespace to group the trip memories. {{< multitabs id="namespace" tab1="Python" tab2="TypeScript" tab3="curl" >}} @@ -114,7 +40,16 @@ Add this code inside the `with` block. It creates a personal namespace for the u Run the file once, then run the printed `export` command in your shell before running the file again. Later runs reuse that ID. Creating the same namespace again returns `409 Conflict`. If you lose the ID, use `list_namespaces` with `scope="PERSONAL"` and `owner_id=USER_ID` to find it. -Use a fresh `SESSION_ID` if you already ran this quickstart without a namespace. For new session events, add `namespace_ref=namespace_ref` in Python, `namespaceRef` in TypeScript, or `"namespaceRef": {"namespaceId": "$NAMESPACE_ID"}` in curl. Extracted memories use the session namespace. +Start a new session with this namespace. Extracted memories inherit it: + +```python + agent_memory.add_session_event( + session_id="travel-planning-with-namespace", actor_id=USER_ID, + role=models.MessageRole.USER, + content=[models.Text(text="I am visiting Tokyo and Kyoto next month.")], + created_at=datetime.now(timezone.utc), namespace_ref=namespace_ref, + ) +``` -tab-sep- @@ -136,7 +71,15 @@ Add this code inside `run`. It creates a personal namespace for the user's trave Run the file once, then run the printed `export` command in your shell before running the file again. Later runs reuse that ID. Creating the same namespace again returns `409 Conflict`. If you lose the ID, use `listNamespaces` with `scope: "PERSONAL"` and `ownerId: userId` to find it. -Use a fresh `sessionId` if you already ran this quickstart without a namespace. For new session events, add `namespace_ref=namespace_ref` in Python, `namespaceRef` in TypeScript, or `"namespaceRef": {"namespaceId": "$NAMESPACE_ID"}` in curl. Extracted memories use the session namespace. +Start a new session with this namespace. Extracted memories inherit it: + +```typescript + await agentMemory.addSessionEvent({ + sessionId: "travel-planning-with-namespace", actorId: userId, role: "USER", + content: [{ text: "I am visiting Tokyo and Kyoto next month." }], + createdAt: new Date(), namespaceRef, + }); +``` -tab-sep- @@ -146,7 +89,6 @@ Create a personal namespace for the user's travel memories. Run this request onc NAMESPACE_ID=$(curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data "$(jq -n --arg owner "$OWNER_ID" \ '{name: "travel", scope: "PERSONAL", ownerId: $owner}')" \ @@ -157,7 +99,16 @@ printf '%s\n' "$NAMESPACE_ID" Continue only after the request succeeds and prints a namespace ID. Save that ID for later runs. Creating the same namespace again returns `409 Conflict`; use the existing namespace ID instead. If you lose the ID, list personal roots with `GET /v1/stores/{storeId}/namespaces?scope=PERSONAL&ownerId=`. -Use a fresh `SESSION_ID` if you already ran this quickstart without a namespace. For new session events, add `namespace_ref=namespace_ref` in Python, `namespaceRef` in TypeScript, or `"namespaceRef": {"namespaceId": "$NAMESPACE_ID"}` in curl. Extracted memories use the session namespace. +Start a new session with this namespace: + +```sh +curl --fail-with-body --silent --show-error --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data "$(jq -n --arg owner "$OWNER_ID" --arg ns "$NAMESPACE_ID" --arg now "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ + '{sessionId:"travel-planning-with-namespace",actorId:$owner,role:"USER",content:[{text:"I am visiting Tokyo and Kyoto next month."}],createdAt:$now,namespaceRef:{namespaceId:$ns}}')" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/events" | jq +``` {{< /multitabs >}} @@ -216,7 +167,6 @@ Run this request once and save the returned ID: SHARED_NAMESPACE_ID=$(curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data '{"name":"team-travel","scope":"SHARED"}' \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespaces" | jq -er '.namespace.namespaceId') @@ -264,7 +214,6 @@ After writing memories to the shared namespace, search with its namespace ID. Th curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data "$(jq -n --arg ns "$SHARED_NAMESPACE_ID" \ '{text: "What travel requirements has the team discussed?", filter: {namespaceRef: {eq: $ns}}, limit: 5}')" \ @@ -275,6 +224,98 @@ curl --fail-with-body --silent --show-error \ The namespace filter selects that exact namespace. To search its children too, list them and include their IDs with `filter.namespaceRef.in`. +## Place and retrieve memories + +Pass `namespaceRef: {"namespaceId": ""}` when you start a session. The first event fixes the session namespace. Later events cannot add or change it. Memories extracted from the session use that namespace, so use a new session ID when adding a namespace to the quickstart example. + +For direct long-term memory creation, set `namespaceRef` on each record. To move existing records, use `move_long_term_memories` (Python), `moveLongTermMemories` (TypeScript), or `POST /v1/stores/{storeId}/long-term-memory/move` with their IDs and the destination `namespaceRef`. Check both `moved` and `errors` in the response before treating a batch as complete. + +Search with `filter.namespaceRef.eq` for one namespace or `filter.namespaceRef.in` for several IDs. These filters match exact namespace IDs; they do not expand a parent into its descendants. List the children and include their IDs when you need to search several levels of a hierarchy. Keep the `ownerId` filter when recalling one user's memories. + +Without server-side ownership enforcement, your application must check that the current user can access each namespace before querying it. With enforcement enabled, personal namespace access follows the root owner's authenticated User ID. Shared namespaces are accessible to callers with the required store permission; enforcement does not add team membership checks. If a shared namespace is only for one team, enforce that rule in your application or use separate stores and grants. See [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). + +## Create a namespace hierarchy + +Choose a root scope when you create a namespace: + +| Scope | Creation fields | Example use | +|:------|:----------------|:------------| +| `PERSONAL` | `name`, `scope`, and `ownerId` | A user's travel plans. | +| `SHARED` | `name` and `scope`, without `ownerId` | Information used across a team. | + +Create a child with `name` and `parentId`. You can also create a hierarchy with `path`, such as `travel/japan`, and the root scope. Supply `ownerId` for a personal path. Missing path segments are created as needed. Do not combine `path` with `name` or `parentId`. + +To create a child, pass this request body to `create_namespace` (Python), `createNamespace` (TypeScript), or `POST /v1/stores/{storeId}/namespaces`: + +```json +{"name": "japan", "parentId": ""} +``` + +To create a personal path and any missing segments: + +```json +{"path": "travel/japan", "scope": "PERSONAL", "ownerId": ""} +``` + +Names and paths are case-sensitive. The path does not encode scope. Use the returned ID to distinguish namespaces that have the same path under different owners or scopes. + +For runnable examples, see [Create a personal namespace]({{< relref "/develop/ai/context-engine/agent-memory/namespaces#create-a-personal-namespace" >}}) or [Create a shared namespace]({{< relref "/develop/ai/context-engine/agent-memory/namespaces#create-a-shared-namespace" >}}). + +## Manage namespaces + +| Operation | Behavior | +|:----------|:---------| +| List | List roots, or pass `parentId` to list direct children. Sort by `id`, `name`, `createdAt`, or `updatedAt` with `sortBy`; `sortOrder` accepts `asc` or `desc`. Defaults are `id` and `asc`. Follow `nextPageToken` with the same filters and sort options. | +| Get | Retrieve the current name, path, scope, and state by ID. | +| Rename | Update `name`; continue using the same ID for placement and retrieval. | +| Archive | Set `state` to `ARCHIVED` to stop new children and placements. You cannot unarchive a namespace. | +| Delete | Delete an empty leaf namespace. A namespace with children or memory placements returns `409 Conflict`. | + +Creating a namespace at an occupied location returns `409 Conflict`, including when the existing namespace is archived. Save and reuse the returned ID instead of creating the same namespace on each agent turn. For request fields and error responses, see the [namespace API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}#operation/CreateNamespace). + +## Sort namespaces + +Use the client from the [quickstart](/content/develop/ai/context-engine/agent-memory/quickstart.md). This example lists shared roots by name: + +{{< multitabs id="namespace-sort" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python + namespaces = agent_memory.list_namespaces( + scope="SHARED", limit=20, sort_by="name", sort_order="asc", + ) + show("Shared namespaces", namespaces) +``` + +-tab-sep- + +```typescript + const namespaces = await agentMemory.listNamespaces( + undefined, // parentId + "SHARED", // scope + undefined, // ownerId + 20, // limit + undefined, // pageToken + undefined, // request options + "name", // sortBy + "asc", // sortOrder + ); + console.dir(namespaces, { depth: null }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error --get \ + --header "Authorization: Bearer $API_KEY" \ + --data-urlencode 'scope=SHARED' --data-urlencode 'limit=20' \ + --data-urlencode 'sortBy=name' --data-urlencode 'sortOrder=asc' \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/namespaces" | jq +``` + +{{< /multitabs >}} + +Add the Python snippet inside the `with` block and the TypeScript snippet inside `run`. Name sorting uses the display name with Redis text collation, rather than the hierarchy path. Equal sort values use namespace ID ascending as a tie-breaker. + ## Route and admit shared memories An explicit namespace destination must pass its admission rules. Direct creation without a namespace stays unassigned. To route an existing shared memory, omit `namespaceRef` from a move request. Personal and unassigned memories are not candidates for automatic routing. See [Namespace routing](/content/develop/ai/context-engine/agent-memory/namespace-routing.md) for policy setup, examples, and per-item errors. diff --git a/content/develop/ai/context-engine/agent-memory/overview.md b/content/develop/ai/context-engine/agent-memory/overview.md index 1885e33da2..79f52780c4 100644 --- a/content/develop/ai/context-engine/agent-memory/overview.md +++ b/content/develop/ai/context-engine/agent-memory/overview.md @@ -48,7 +48,7 @@ Your application gives Agent Memory the conversation for extracting long-term me - **Session event**: One message in a conversation, added with `sessionId`, `actorId`, `role`, `content`, and `createdAt`. - **actorId**: Identifies who or what sent the event, a user, the agent, or a tool. - **ownerId**: Identifies who a long-term memory belongs to, for search and filtering later. Independent of who wrote the session event it came from. -- **memoryType**: The long-term memory tier a fact is stored under, one of the built-in types (`semantic`, `episodic`, `message`, `session_summary_view`) or a custom type you define. +- **memoryType**: The category of a long-term memory, such as `semantic`, `episodic`, or a custom type you define. ### How it works diff --git a/content/develop/ai/context-engine/agent-memory/quickstart.md b/content/develop/ai/context-engine/agent-memory/quickstart.md index da9d42da3f..e2f3c7d133 100644 --- a/content/develop/ai/context-engine/agent-memory/quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/quickstart.md @@ -18,7 +18,7 @@ aliases: - /operate/rc/context-engine/agent-memory/use-agent-memory/ --- -Store a travel-planning message, retrieve the conversation, and recall a preference extracted into long-term memory. Choose Python, TypeScript, or curl for each example. +Build the memory for a travel assistant: save a conversation, retrieve it, and recall the user’s food preferences in a later conversation. ## Before you begin @@ -76,9 +76,11 @@ export OWNER_ID='quickstart-user' {{< /multitabs >}} +The SDK examples read `API_KEY` from your environment; replace `` and `` in each file. The curl examples read all three values from your environment. + The endpoint must include `https://`. Keep the API key out of source control, application logs, and other unsecured locations. -For a self-managed service with ownership enforcement enabled, [configure delegation](/content/operate/iris/agent-memory/self-managed/authentication.md#delegate-a-user-with-an-agent-key), then also export `USER_TOKEN` and set `OWNER_ID` to that token's verified user ID. The examples use these optional values. Leave `USER_TOKEN` unset for Cloud store credentials. Without `OWNER_ID`, the SDK examples use `quickstart-user` for this exercise. +For a self-managed service with ownership enforcement, use the authenticated client setup in [Delegate a user with an agent key](/content/operate/iris/agent-memory/self-managed/authentication.md#delegate-a-user-with-an-agent-key) and set `OWNER_ID` to the verified user ID before using these examples. ## Install the SDK @@ -112,20 +114,47 @@ Use `curl` and `jq` directly. No SDK installation is required. ## Create the client and check the service health -Create `quickstart.py` or `quickstart.ts` with the complete example for your language. Replace `` and `` with your service values. The endpoint must include `https://`. +1. Create `quickstart.py` or `quickstart.ts` with the complete example for your language. +1. Replace `` and `` with your service values. The endpoint must include `https://`. +1. Run `python quickstart.py` or `npx tsx quickstart.ts`. For curl, run the command in your configured shell. +1. Check that the overall `status` and each entry under `features` are healthy. {{< clients-example set="agent_memory_sdk" step="connect_health_check" footer="hide" dft_tab_name="curl" runnable="false" try_it="false" >}} curl --fail-with-body --silent --show-error \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/health" | jq {{< /clients-example >}} -The store health response checks the configured Store ID and reports the status of its memory features. Inspect the overall `status` and individual `features` before continuing. +The store health response checks the configured Store ID and reports the status of its memory features. Inspect the overall `status` and individual `features` before continuing. A healthy response looks like: + +```json +{ + "status": "healthy", + "features": { + "background_jobs": { + "status": "healthy" + }, + "long_term_memory": { + "status": "healthy" + }, + "session_memory": { + "status": "healthy" + }, + "source_link": { + "status": "healthy" + }, + "store_db": { + "status": "healthy" + } + } +} +``` Run Python examples with `python quickstart.py` or TypeScript examples with `npx tsx quickstart.ts`. Each SDK example includes its client setup. Replace the file with the complete example for the next step, and use the same connection values and identifiers. Run curl commands in the shell where you exported the connection values. -## 1. Build conversation context with session memory + + +## Save and retrieve a conversation Session memory is the conversation transcript. Each message is an event, stored with the conversation's session ID and the sender's actor ID. The first request saves a user's travel plans. The second retrieves the conversation so you can check that the message was stored. @@ -137,7 +166,6 @@ export EVENT_CREATED_AT="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data @- \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/events" <}} @@ -169,7 +196,9 @@ curl --fail-with-body --silent --show-error \ Run the event write once. Repeating it adds the same message again. The next example searches without repeating the write. -## 2. Recall automatically extracted information + + +## Recall an extracted preference Redis Agent Memory processes session events in the background and creates long-term memories for information that may be useful in later conversations. You configured the extraction cadence to one minute when you created the service. @@ -179,7 +208,6 @@ Wait at least one minute. Replace your SDK file with the complete search example curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ --header 'Content-Type: application/json' \ --data @- \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/long-term-memory/search" <}}) for examples. -1. Set the message's `role`, `content`, and `createdAt` timestamp. -1. Send the request using the [quickstart's session-event example]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#1-build-conversation-context-with-session-memory" >}}). Repeat it for each new message, using the same session ID throughout the conversation. - Redis Agent Memory adds the event ID and storage timestamp. The event fields are: | Field | Who supplies it | Purpose | @@ -39,7 +34,23 @@ Redis Agent Memory adds the event ID and storage timestamp. The event fields are | `systemTimestamp` | Server-generated. | Time Redis Agent Memory stored the event. | | `metadata` | Optional from the client. | Application-specific information associated with the event. | -The session's `ownerId` is returned when you retrieve the session; it is not a session-event input. With [server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement), the first event's `actorId` must match the authenticated User ID. +1. Set the first event's `actorId` to the signed-in user's account ID from your application. Use the same ID each time this user returns. The server uses it as the session owner. Later events can identify other actors, such as the assistant. +1. Supply a `sessionId`, or omit it to let the server create one. Save the returned `event.sessionId` for later requests. See [Identify users and conversations]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#identify-users-and-conversations" >}}) for examples. +1. Set the message's `role`, `content`, and `createdAt` timestamp. +1. Send the request. For curl, reuse the connection values from the [quickstart](/content/develop/ai/context-engine/agent-memory/quickstart.md#save-the-connection-values): + +```sh +curl --fail-with-body --silent --show-error --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' \ + --data "$(jq -n --arg session "$SESSION_ID" --arg owner "$OWNER_ID" --arg now "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ + '{sessionId:$session,actorId:$owner,role:"USER",content:[{text:"I am vegetarian."}],createdAt:$now}')" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/events" | jq +``` + +Send each new message once, using the same session ID throughout the conversation. + +The session's `ownerId` comes from the first event's `actorId` and is returned when you retrieve the session. With [server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement), the first event's `actorId` must match the authenticated User ID. ## Retrieve conversation context @@ -48,7 +59,6 @@ When the next message arrives, retrieve the session by its ID before calling the ```sh curl --fail-with-body --silent --show-error \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/$SESSION_ID" | jq ``` @@ -58,9 +68,9 @@ Without server-side ownership enforcement, your application must check that the ## List sessions -To retrieve metadata rows ordered by recent activity, use `GET /v1/stores/{storeId}/sessions`, `list_session_summaries` in Python, or `listSessionSummaries` in TypeScript. These rows do not contain events or generated summary text. See [List and sort sessions](/content/develop/ai/context-engine/agent-memory/list-sessions.md) for SDK examples, pagination, and legacy field handling. +To let a user reopen an earlier conversation, list their sessions and retrieve the selected session by ID. Use `GET /v1/stores/{storeId}/sessions`, `list_session_summaries` in Python, or `listSessionSummaries` in TypeScript. These rows do not contain events or generated summary text. See [List and sort sessions](/content/develop/ai/context-engine/agent-memory/list-sessions.md) for SDK examples and pagination. -To let a user reopen an earlier conversation, first find its session ID. `GET /v1/stores/{storeId}/session-memory` lists IDs by owner or namespace; it does not return the conversation events. Reuse the curl connection values from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#save-the-connection-values" >}}). +For session IDs only, use `GET /v1/stores/{storeId}/session-memory` lists IDs by owner or namespace; it does not return the conversation events. Reuse the curl connection values from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#save-the-connection-values" >}}). For a user's personal conversations, set `filterOwnerId` to that user's authenticated ID. A namespace-only query is suitable for shared conversations only after your application checks access to that namespace. Reserve `includeAll=true` for callers your application authorizes to list the whole store. See [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). @@ -69,18 +79,16 @@ To list sessions in one namespace, set `NAMESPACE_ID` to the stable ID returned ```sh curl --fail-with-body --silent --show-error \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory?namespaceRef=$NAMESPACE_ID" | jq ``` -`namespaceRef` matches the namespace ID exactly, including case. It excludes descendant namespaces, sessions with only a deprecated `namespace` label, and sessions without a namespace resource. +`namespaceRef` matches the namespace ID exactly, including case. It excludes descendant namespaces, sessions with only the older `namespace` string label, and sessions without a namespace resource. Add `filterOwnerId` to return sessions that match both the namespace and the owner: ```sh curl --fail-with-body --silent --show-error \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory?namespaceRef=$NAMESPACE_ID&filterOwnerId=$OWNER_ID" | jq ``` @@ -89,7 +97,6 @@ You can also use `filterOwnerId` alone. To list all sessions, omit both filters ```sh curl --fail-with-body --silent --show-error \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory?includeAll=true" | jq ``` @@ -124,11 +131,11 @@ To group a session and its extracted memories, use an optional [namespace]({{< r ## Try automatic summarization -Complete the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}) first. Reuse its client, connection values, and user and session identifiers. Add Python snippets inside the `with` block and TypeScript snippets inside `run`. Run curl commands in the same shell. Run each write once; comment out completed writes and their output statements before rerunning an SDK file. +{{< embed-md "agent-memory-continue-from-quickstart.md" >}} Automatic summarization condenses older events and retains the most recent events in full. The retrieved session then contains a `summary` object and the recent `events` array, so the application can provide useful history without filling the model's context window with every original message. -In the service configuration, enable automatic summarization. Set **Summarize after** to `6` and **Keep most recent** to `2` for this example. When the session reaches six events, Redis Agent Memory summarizes the older events and retains the two most recent events in full. +Follow [Configure automatic summarization](#configure-automatic-summarization). Set **Summarize after** to `6` and **Keep most recent** to `2` for this example. When the session reaches six events, Redis Agent Memory summarizes the older events and retains the two most recent events in full. ### Add conversation turns @@ -182,18 +189,26 @@ Add these events to the session from the quickstart to continue the conversation -tab-sep- -Add enough user and assistant events to reach the configured threshold. Use the session-event request from the quickstart and change `role`, `actorId`, `content`, and `createdAt` for each event. Add these six turns once, using `travel-agent` as the assistant actor and `$OWNER_ID` as the user actor: +Run this command once to append the six turns: -| Role | Content | -|:---|:---| -| `ASSISTANT` | What dates are you traveling? | -| `USER` | I arrive on October 10 and leave on October 18. | -| `ASSISTANT` | Would you like formal or casual restaurants? | -| `USER` | Mostly casual places near public transit. | -| `ASSISTANT` | Do you have a preferred budget? | -| `USER` | About 40 euros per person. | - -Summarization runs in the background after the session reaches the threshold. +```sh +jq -cn --arg user "$OWNER_ID" '[ + {role:"ASSISTANT",actorId:"travel-agent",text:"What dates are you traveling?"}, + {role:"USER",actorId:$user,text:"I arrive on October 10 and leave on October 18."}, + {role:"ASSISTANT",actorId:"travel-agent",text:"Would you like formal or casual restaurants?"}, + {role:"USER",actorId:$user,text:"Mostly casual places near public transit."}, + {role:"ASSISTANT",actorId:"travel-agent",text:"Do you have a preferred budget?"}, + {role:"USER",actorId:$user,text:"About 40 euros per person."} +] | .[]' | while IFS= read -r turn; do + printf '%s' "$turn" | jq --arg session "$SESSION_ID" \ + --arg now "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ + '{sessionId:$session,actorId:.actorId,role:.role,content:[{text:.text}],createdAt:$now}' | \ + curl --fail-with-body --silent --show-error --request POST \ + --header "Authorization: Bearer $API_KEY" \ + --header 'Content-Type: application/json' --data @- \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/events" || exit 1 +done +``` {{< /multitabs >}} @@ -226,7 +241,6 @@ After a short wait, retrieve the session again: ```sh curl --fail-with-body --silent --show-error \ --header "Authorization: Bearer $API_KEY" \ - --header "X-Iris-User-Token: ${USER_TOKEN:-}" \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/$SESSION_ID" | jq ``` diff --git a/content/develop/ai/context-engine/agent-memory/summary-views.md b/content/develop/ai/context-engine/agent-memory/summary-views.md index 3d9f29fa17..7c4bc4bdd1 100644 --- a/content/develop/ai/context-engine/agent-memory/summary-views.md +++ b/content/develop/ai/context-engine/agent-memory/summary-views.md @@ -38,7 +38,7 @@ Shared does not mean a particular team. Enforce team membership in your backend ## Create, refresh, and read a view -These examples create one private profile for `OWNER_ID`, request a full refresh, and poll its result. First store ordinary long-term memories with the [quickstart](/content/develop/ai/context-engine/agent-memory/quickstart.md). A successful refresh with no matching sources returns `empty`. +These examples create one private profile for `OWNER_ID`, request a full refresh, and poll its result. First [create ordinary long-term memories](/content/develop/ai/context-engine/agent-memory/long-term-memory.md#create-a-custom-memory-directly) in this self-managed store, using the delegated client and matching `OWNER_ID`. A successful refresh with no matching sources returns `empty`. Save the view ID for later requests. Creation and refresh are asynchronous; their responses do not contain generated output. @@ -112,7 +112,13 @@ async function main() { console.dir(await memory.refreshSummaryView(view.id, { target: "full" }, options)); for (let attempt = 0; attempt < 60; attempt++) { const result = await memory.getSummaryViewResult( - view.id, undefined, undefined, undefined, undefined, true, options, + view.id, // viewId + undefined, // ownerId + undefined, // sessionId + undefined, // namespaceRef + undefined, // memoryType + true, // includeSourceMemoryIds + options, // request options ); if (result.status === "failed") throw new Error(`Refresh failed: ${result.errorCode}`); if (result.status === "empty") { @@ -181,8 +187,8 @@ Text output uses `value.text`; structured output uses `value.data`. Python outpu | Status | Application behavior | |:-------|:---------------------| -| `cold` | No result is materialized for this scope. A grouped scope may be outside the active partition set. | -| `pending` | Work is queued, or unsafe prior output is withheld while repair is pending. | +| `cold` | No result is stored for this group. It may not have been generated yet, or it may have been evicted. | +| `pending` | Work is queued. Previous output may be withheld because its sources changed or became inaccessible. | | `ready` | A value is available. Check `stale` and coverage fields. | | `empty` | No sources match. There is no generated value. | | `refreshing` | New work is running. A last good value can remain. | @@ -240,7 +246,7 @@ The reconcile interval is required and must fit deployment limits. Debounce coal ## Check coverage and manage definitions -Inspect `sourceMemoryCount` and `summarizedMemoryCount`. Selection can omit whole records (`sourcesOmitted`) or clip included content (`sourceContentTruncated`). `truncated` combines both. Equal record counts do not prove all source text was included. Concurrent writes can change the source set while counts are measured. +Inspect `sourceMemoryCount` and `summarizedMemoryCount`. Selection can omit whole records (`sourcesOmitted`) or clip included content (`sourceContentTruncated`). `truncated` combines both. Check `truncated` even when every record was included: some text may have been cut. Concurrent writes can change the source set while counts are measured. Request `includeSourceMemoryIds=true` for the selected source IDs. These identify contributing records, not every match. `computedAt`, `sourceUpdatedAt`, and `definitionVersion` help assess freshness. @@ -253,7 +259,15 @@ Request `includeSourceMemoryIds=true` for the selected source IDs. These identif REST paths are relative to `/v1/stores/{storeId}`. Lists return `items` and optional `nextPageToken`; pass it as `pageToken` with the same query. Definition states (`active`, `disabled`, `deleting`) are separate from result status. -Name and description edits do not trigger generation. Material edits increment `definitionVersion`; a `groupBy` change also replaces the partition layout. `clearFilter: true` and `clearTimeWindow: true` remove those settings; do not also supply replacements. Owner, visibility, and source are immutable. Set `state: disabled` to disable a view; re-enabling requests refresh work. +| Change | Effect | +|:-------|:-------| +| Name or description | Does not trigger generation. | +| Filter, output, or refresh policy | Increments `definitionVersion`. | +| `groupBy` | Increments `definitionVersion` and replaces the partition layout. | +| Owner, visibility, or source | Cannot be changed. Create a new definition. | +| `state: disabled` | Disables the view. Re-enabling requests refresh work. | + +`clearFilter: true` and `clearTimeWindow: true` remove those settings; do not also supply replacements. Deletion returns `202` while cleanup runs. Repeated deletion is idempotent; the definition becomes `deleting` before schedules and results are removed. Source memories remain. diff --git a/content/embeds/agent-memory-continue-from-quickstart.md b/content/embeds/agent-memory-continue-from-quickstart.md new file mode 100644 index 0000000000..6ea7fc3939 --- /dev/null +++ b/content/embeds/agent-memory-continue-from-quickstart.md @@ -0,0 +1,7 @@ +These examples continue from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart" >}}). Reuse its client, connection values, and user ID. + +- **Python:** Add each snippet inside the `with` block in `main`. +- **TypeScript:** Add each snippet inside `run`. +- **curl:** Use the shell where you exported the connection values. + +Each run executes every write still in the file. Comment out completed writes and their output statements before rerunning it. Repeating a session-event write can add the same message again. diff --git a/content/operate/iris/agent-memory/create-service.md b/content/operate/iris/agent-memory/create-service.md index 097ea1070e..713352a351 100644 --- a/content/operate/iris/agent-memory/create-service.md +++ b/content/operate/iris/agent-memory/create-service.md @@ -131,7 +131,7 @@ To add a custom memory type, select **Add type** and configure the following set | Setting name |Description| |:----------------------|:----------| -| **Name** | A unique name for the custom memory type. Must start with a letter and contain only letters, numbers, hyphens, or underscores (1–64 characters). The name must be unique within the service and cannot match a built-in memory type (`semantic`, `episodic`, `message`, or `session_summary_view`). | +| **Name** | A unique name for the custom memory type. Must start with a letter and contain only letters, numbers, hyphens, or underscores (1–64 characters). The name must be unique within the service and cannot match a built-in memory type. | | **Description** | A short description of what the memory type represents (1–200 characters). | | **Fields** | The structured fields that make up the memory type. See [Fields](#fields) below. | diff --git a/content/operate/iris/agent-memory/self-managed/_index.md b/content/operate/iris/agent-memory/self-managed/_index.md index f29b360132..744158c82d 100644 --- a/content/operate/iris/agent-memory/self-managed/_index.md +++ b/content/operate/iris/agent-memory/self-managed/_index.md @@ -91,4 +91,4 @@ provides searchable context across sessions. Long-term memory `memoryType` is an open identifier. When omitted on create, Redis Agent Memory stores the record as `semantic`; built-in names include `semantic`, -`episodic`, `message`, and `session_summary_view`. +`episodic`, and `message`. diff --git a/content/operate/iris/agent-memory/self-managed/api-examples.md b/content/operate/iris/agent-memory/self-managed/api-examples.md index 43a32a8461..b3ddc8616b 100644 --- a/content/operate/iris/agent-memory/self-managed/api-examples.md +++ b/content/operate/iris/agent-memory/self-managed/api-examples.md @@ -5,7 +5,7 @@ categories: - docs - operate - iris -description: Use curl examples with the Redis Agent Memory self-managed Control Plane and Data Plane APIs. +description: Manage a self-managed Redis Agent Memory service and make application requests with SDKs or curl. linkTitle: Self-managed API examples weight: 70 hideListLinks: true @@ -141,6 +141,208 @@ A store may define at most 32 custom detectors. Update an existing store's polic For what each mechanism does, how a match is handled, and which memory paths exclusions apply to, see [exclude sensitive data from automatic extraction]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#exclude-sensitive-data-from-automatic-extraction" >}}). +## Manage a service with the Control Plane SDK + +The same package supplies two independently configured clients: + +| Client | Purpose | Connection and credential | +|:-------|:--------|:--------------------------| +| `AgentMemoryService` | Manage stores and extractors. Supply the store ID on each call. | Control Plane URL and operator `token`. | +| `AgentMemory` | Read and write memory within the store selected by the constructor. | Data Plane URL, Store ID, and application credential. | + +Use the self-managed Control Plane admin token or supported operator token with `AgentMemoryService`. Its SDK uses `/v2/memory` routes; the self-managed `/v1` admin API remains available. The SDK does not derive a Data Plane URL or credential from a Control Plane response. + +Set `AGENT_MEMORY_CONTROL_PLANE_URL` and `AGENT_MEMORY_SERVICE_TOKEN` separately from `AGENT_MEMORY_URL`, `STORE_ID`, and `API_KEY`. These examples read the store and check Data Plane health: + +{{< multitabs id="memory-service-sdk" tab1="Python" tab2="TypeScript" >}} + +```python +import asyncio +import os + +from redis_agent_memory import AgentMemory, AgentMemoryService + + +async def main(): + async with AgentMemoryService( + server_url=os.environ["AGENT_MEMORY_CONTROL_PLANE_URL"], + token=os.environ["AGENT_MEMORY_SERVICE_TOKEN"], + ) as service: + store = await service.get_store_async(store_id=os.environ["STORE_ID"]) + print("Store:", store.name) + async with AgentMemory( + os.environ["AGENT_MEMORY_URL"], store_id=os.environ["STORE_ID"], + api_key=os.environ["API_KEY"], + ) as memory: + print((await memory.store_health_async()).status) + + +asyncio.run(main()) +``` + +-tab-sep- + +```typescript +import { AgentMemory, AgentMemoryService } from "@redis-iris/agent-memory"; + +async function main() { + const { AGENT_MEMORY_CONTROL_PLANE_URL, AGENT_MEMORY_SERVICE_TOKEN, + AGENT_MEMORY_URL, STORE_ID, API_KEY } = process.env; + if (!AGENT_MEMORY_CONTROL_PLANE_URL || !AGENT_MEMORY_SERVICE_TOKEN || + !AGENT_MEMORY_URL || !STORE_ID || !API_KEY) { + throw new Error("Set both planes' connection values."); + } + const service = new AgentMemoryService({ + serverURL: AGENT_MEMORY_CONTROL_PLANE_URL, token: AGENT_MEMORY_SERVICE_TOKEN, + }); + console.log("Store:", (await service.getStore(STORE_ID)).name); + const memory = new AgentMemory({ serverURL: AGENT_MEMORY_URL, storeId: STORE_ID, apiKey: API_KEY }); + console.log((await memory.storeHealth()).status); +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : "Request failed"); + process.exitCode = 1; +}); +``` + +{{< /multitabs >}} + +If omitted, the Control Plane token is read from `AGENT_MEMORY_SERVICE_TOKEN`; it does not fall back to the Data Plane key. Python service models, errors, and retry helpers use `redis_agent_memory.controlplane`; Data Plane imports remain separate. Use each client's corresponding error classes. + +Store and extractor management, detectors, and memory-type reference data have Cloud and self-managed API implementations. The generated client also includes Cloud-only account database, end user license agreement (EULA), network, store-model, metrics, flush, and empty-store operations; those return `404` on-prem. The current Cloud Control Plane does not accept a programmatic credential for this SDK. Use the Cloud console for Cloud service setup rather than sending a Data Plane key or assuming a console session is a service token. + +See [Create an extractor](#create-an-extractor) for extractor administration and the [self-managed Control Plane reference](/content/operate/iris/agent-memory/self-managed/control-plane-api-reference.md) for the admin REST API. Webhook-policy configuration uses the admin REST API; it is not a method on the current service SDK. + +## Create an extractor + +Enable [document ingestion](/content/operate/iris/agent-memory/self-managed/configuration.md#enable-document-ingestion). Set `AGENT_MEMORY_CONTROL_PLANE_URL`, `AGENT_MEMORY_SERVICE_TOKEN`, and `STORE_ID` as described in [Manage a service with the Control Plane SDK](#manage-a-service-with-the-control-plane-sdk). + +First register a custom memory type on the store. For a new `document_fact` type, run this once: + +```sh +curl --fail-with-body --silent --show-error --request PATCH \ + --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ + --header 'Content-Type: application/json' \ + --data '{"addCustomMemoryTypes":[{"name":"document_fact","description":"Useful facts extracted from documents"}]}' \ + "$AGENT_MEMORY_CONTROL_PLANE_URL/v2/memory/stores/$STORE_ID" | jq +``` + +Use an existing registered type instead if it already has the fields you need. Existing types cannot be redefined by adding them again. + +Create an extractor with `source: documents` and `strategy: custom`. Save its returned `id` as `EXTRACTOR_ID`: + +{{< multitabs id="document-extractor" tab1="Python" tab2="TypeScript" tab3="curl" >}} + +```python +import asyncio +import os + +from redis_agent_memory import AgentMemoryService + + +async def main(): + async with AgentMemoryService( + server_url=os.environ["AGENT_MEMORY_CONTROL_PLANE_URL"], + token=os.environ["AGENT_MEMORY_SERVICE_TOKEN"], + ) as service: + extractor = await service.create_extractor_async( + store_id=os.environ["STORE_ID"], + name="Travel guide facts", memory_type="document_fact", + source="documents", strategy="custom", + prompt="Extract useful travel facts. Keep each fact in one memory.", + ) + print(extractor.model_dump_json(by_alias=True)) + + +asyncio.run(main()) +``` + +-tab-sep- + +```typescript +import { AgentMemoryService } from "@redis-iris/agent-memory"; + +async function main() { + const { AGENT_MEMORY_CONTROL_PLANE_URL, AGENT_MEMORY_SERVICE_TOKEN, STORE_ID } = process.env; + if (!AGENT_MEMORY_CONTROL_PLANE_URL || !AGENT_MEMORY_SERVICE_TOKEN || !STORE_ID) { + throw new Error("Set the Control Plane connection values and STORE_ID."); + } + const service = new AgentMemoryService({ + serverURL: AGENT_MEMORY_CONTROL_PLANE_URL, token: AGENT_MEMORY_SERVICE_TOKEN, + }); + console.dir(await service.createExtractor(STORE_ID, { + name: "Travel guide facts", memoryType: "document_fact", + source: "documents", strategy: "custom", + prompt: "Extract useful travel facts. Keep each fact in one memory.", + }), { depth: null }); +} + +main().catch((error) => { console.error(error instanceof Error ? error.message : "Request failed"); process.exitCode = 1; }); +``` + +-tab-sep- + +```sh +curl --fail-with-body --silent --show-error --request POST \ + --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ + --header 'Content-Type: application/json' \ + --data '{"name":"Travel guide facts","memoryType":"document_fact","source":"documents","strategy":"custom","prompt":"Extract useful travel facts. Keep each fact in one memory."}' \ + "$AGENT_MEMORY_CONTROL_PLANE_URL/v2/memory/stores/$STORE_ID/extractors" | jq +``` + +{{< /multitabs >}} + +Set `destinationNamespaceId` when creating an extractor to place its output in one namespace in the same store. Use a stable namespace ID, not a display name. Personal destinations must be compatible with the document owner; shared placement does not make the original document accessible to every reader of the extracted memories. + +List or read extractors before editing them. Updates and removal require the current `expectedRevision`; a stale revision returns `409`. An update can change the name, prompt, enabled flag, or destination. An empty destination clears it; omission keeps it. Accepted ingestions retain their extractor snapshot, so a later edit does not change an existing run. + +After setup, applications can [submit and track documents](/content/develop/ai/context-engine/agent-memory/memory-etl.md#submit-and-track-a-document) with their Data Plane credentials. + +## Configure a webhook policy + +First [enable ingestion webhooks](/content/operate/iris/agent-memory/self-managed/configuration.md#enable-ingestion-webhooks). Use the self-managed Control Plane REST API and an operator credential. The Data Plane SDKs expose status reads; `AgentMemoryService` does not currently expose hook-policy management. Set `AGENT_MEMORY_CONTROL_PLANE_URL`, `AGENT_MEMORY_SERVICE_TOKEN`, `STORE_ID`, and `HOOK_URL`. For the receiver example, use `https://:8443/hook`, or the HTTPS ingress URL that forwards to it. + +A policy PUT replaces the whole policy. Include every phase you want to retain; omitting a phase removes it. Read the current revision before writing: + +```sh +curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ + "$AGENT_MEMORY_CONTROL_PLANE_URL/v1/stores/$STORE_ID/ingestion-hooks" > hook-config.json +``` + +A new store starts at revision 0. Generate separate 32-byte signing keys for the two phases, then configure them with delivery still disabled: + +```sh +export HOOK_INPUT_KEY="$(openssl rand -base64 32)" +export HOOK_OUTPUT_KEY="$(openssl rand -base64 32)" + +jq --arg url "$HOOK_URL" \ + '{expectedRevision:.revision,enabled:false,beforeInput:{url:$url,paths:["session.append","memory.create","document.submit"],signingKey:env.HOOK_INPUT_KEY},beforeApply:{url:$url,paths:["session.builtin","session.custom","document.chunk"],signingKey:env.HOOK_OUTPUT_KEY}}' \ + hook-config.json | curl --fail-with-body --silent --show-error --request PUT \ + --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ + --header 'Content-Type: application/json' --data @- \ + "$AGENT_MEMORY_CONTROL_PLANE_URL/v1/stores/$STORE_ID/ingestion-hooks" > hook-config-new.json +``` + +The response returns each phase's `keyId` and `signingKeyConfigured`, never its key. Load those IDs and original keys into the receiver. The [receiver example](/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md#verify-callbacks-and-return-decisions) can get it from: + +```sh +export HOOK_KEYS_JSON="$(jq -c '{(.beforeInput.keyId):env.HOOK_INPUT_KEY,(.beforeApply.keyId):env.HOOK_OUTPUT_KEY}' hook-config-new.json)" +``` + +After the receiver is ready, enable the same policy using the returned revision and omitting signing keys to retain them: + +```sh +jq '{expectedRevision:.revision,enabled:true,beforeInput:{url:.beforeInput.url,paths:.beforeInput.paths},beforeApply:{url:.beforeApply.url,paths:.beforeApply.paths}}' \ + hook-config-new.json | curl --fail-with-body --silent --show-error --request PUT \ + --header "Authorization: Bearer $AGENT_MEMORY_SERVICE_TOKEN" \ + --header 'Content-Type: application/json' --data @- \ + "$AGENT_MEMORY_CONTROL_PLANE_URL/v1/stores/$STORE_ID/ingestion-hooks" | jq +``` + +A stale `expectedRevision` returns `409`. To rotate a key, supply a new `signingKey` and load its returned ID into the receiver. Retain the previous receiver key for 24 hours: pending work keeps its original endpoint and key version. Keep the previous endpoint reachable while that work completes; delivery does not follow redirects. Policy changes affect new work; they do not rewrite saved decisions or a pending run's policy. + ## Identity Service API examples Manage agent keys with the Identity Service, using the Identity Service control token. diff --git a/content/operate/iris/agent-memory/self-managed/authentication.md b/content/operate/iris/agent-memory/self-managed/authentication.md index b7820a3560..e7818179a2 100644 --- a/content/operate/iris/agent-memory/self-managed/authentication.md +++ b/content/operate/iris/agent-memory/self-managed/authentication.md @@ -299,14 +299,26 @@ For a new deployment, configure the user-identity path before writing personal d Before upgrading an existing deployment: 1. Keep `memory.ownership.enabled: false` explicitly set while preparing the transition. -1. Complete the deployment's namespace-placement index migration. Existing stores need the scope and root-owner fields used by ownership queries. -1. Compare stored personal owners with the verified user IDs your application will send. Resolve mismatches through a controlled data migration; the setting does not rewrite owners. +1. Compare stored personal owners with the verified user IDs your application will send. Resolve owner mismatches before enabling enforcement; the setting does not rewrite owners. 1. Configure delegation, then set `memory.ownership.enabled: true` in chart-rendered values. For a bring-your-own file, use `ownership.enabled: true` at the root. 1. Apply the setting to both Data Plane servers and workers, then roll out those processes. `MEM_OWNERSHIP_ENABLED` overrides the file value; check both for conflicting settings. 1. Replace existing Summary Views when changing this setting. A view retains the source-access policy from its creation. 1. Test two users: each can access their own personal records, neither can retrieve or search for the other's records, and both can access shared records allowed by their store grants. 1. Test an agent key without a user token: it must not retrieve personal records or create a session for a named owner. +For the two-user read check, use an ordinary agent key with store read access, without operator privileges. Set `USER_TOKEN` to the first user's valid token. Set `OWN_SESSION_ID`, `OTHER_SESSION_ID`, and `SHARED_SESSION_ID` to existing sessions owned by that user, owned by the other user, and placed in a shared namespace, respectively: + +```sh +for session_id in "$OWN_SESSION_ID" "$OTHER_SESSION_ID" "$SHARED_SESSION_ID"; do + curl --silent --show-error --output /dev/null --write-out '%{http_code}\n' \ + --header "Authorization: Bearer $API_KEY" \ + --header "X-Iris-User-Token: $USER_TOKEN" \ + "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/session-memory/$session_id" +done +``` + +Expect `200`, `404`, and `200`, in that order. Repeat with the second user's token and swap `OWN_SESSION_ID` with `OTHER_SESSION_ID`. A `401` indicates a token configuration problem; it does not prove ownership enforcement works. + Disabling enforcement restores store-wide access within the credential's grants. Keep backend authorization in place before disabling it. It does not remove stored owner fields, reassign records, or remove the need for store authentication. ## Worker callbacks diff --git a/content/operate/iris/agent-memory/self-managed/configuration.md b/content/operate/iris/agent-memory/self-managed/configuration.md index b74b736864..2fdade950a 100644 --- a/content/operate/iris/agent-memory/self-managed/configuration.md +++ b/content/operate/iris/agent-memory/self-managed/configuration.md @@ -373,8 +373,79 @@ memory: timeout: 30s ``` -Use the deployment's configured model-provider connection and supply its credential through the overlay Secret. The selected provider must support the required text or structured generation; the OpenAI connection uses `chat_completions` for structured output. These numbers are example limits, not service-wide defaults. +Use the deployment's configured model-provider connection and supply its credential through the overlay Secret. The selected provider must support the required text or structured generation; the OpenAI connection uses `chat_completions` for structured output. These numbers are examples. Required bounds have no defaults when Summary Views are enabled; set them explicitly and validate them under your expected load. + +| Bounds | How to choose them | +|:-------|:-------------------| +| `max_input_tokens`, `generation.max_output_tokens` | Leave room for the prompt and output within the selected model’s context limit. | +| `max_source_records`, `max_source_tokens`, `max_partitions`, `max_definitions` | Bound the amount of data each store can summarize and retain. Test representative source sizes and grouping values. | +| `max_concurrent_refreshes`, `max_concurrent_refreshes_per_store` | Budget model-provider requests and Redis load. Both caps apply per worker replica; multiply by the replica count when sizing capacity. | +| Debounce and reconciliation intervals | Balance result freshness against repeated generation cost. | +| `generation.timeout`, `cleanup_timeout` | Allow measured generation and cleanup time while bounding stalled work. | +| `page_size`, `cleanup_batch_size`, `schedule_repair_interval` | Test scan and cleanup throughput against Redis capacity. | With a bring-your-own file, place `summary_views` at the file root. Keep the Redis job backend, authenticated worker callbacks, and scheduling worker enabled. Roll out server and worker together, then [create and refresh a view](/content/develop/ai/context-engine/agent-memory/summary-views.md). A private view needs an authenticated user; an ordinary agent key alone does not supply one. Each view retains its source-access policy. Replace existing definitions when changing ownership enforcement rather than expecting a flag change to rewrite them. + +## Enable ingestion webhooks + +Start with the [self-managed deployment](/content/operate/iris/agent-memory/self-managed/deploy.md). These are process settings; a client cannot enable them through a request header. + +An operator must configure both Data Plane servers and workers: + +1. Enable `memory.webhooks.enabled` in chart-rendered configuration, or `webhooks.enabled` at the root of a bring-your-own file. +1. Enable the Redis job backend and [authenticated worker callbacks](/content/operate/iris/agent-memory/self-managed/authentication.md#worker-callbacks). +1. Configure the same signing-secret backend and prefix in the Control Plane, Data Plane, and worker. Supported backends are Vault and AWS Secrets Manager. The Control Plane writes keys; Data Plane processes read them. +1. Serve the receiver over HTTPS with a certificate trusted by the Data Plane. +1. For a private receiver address, allow its range in `memory.webhooks.private_cidrs` on both servers and workers. + +For example, allow one private receiver address in Helm values: + +```yaml +memory: + webhooks: + enabled: true + private_cidrs: + - /32 +``` + +Delivery checks all resolved addresses at connection time. Redirects and environment HTTP proxies are not used. An allowlist cannot permit loopback, link-local, metadata-service, multicast, or other protected addresses. A localhost URL is not a production callback endpoint. + +For Vault, use matching application configuration in the Data Plane and Control Plane: + +```yaml +secrets: + provider: vault +vault: + address: https://:8200 + mount: secret + base_path: agent-memory +``` + +In chart-rendered values, put this under `memory` and `controlplane.configData`, respectively. Supply `vault.token` through a mounted configuration Secret. The Control Plane needs permission to write and delete signing-key versions; servers and workers need read permission. Keep real Vault tokens and callback keys out of source control. + +Before enabling output hooks on an existing deployment, upgrade workers first, then servers, and drain jobs queued by the older release. Keep hooks disabled until both sides support them. Setting the deployment flag to false stops delivery and fails selected work closed; it does not approve pending operations. + +For Active-Active backing databases, route every server and worker for a store through the same authoritative writer connection. Do not mix member addresses or use region-dependent aliases for that connection. Before changing writers, drain old writers, wait for replication, and restart the processes to clear old connections. A store with hook history fails closed if its persisted writer connection is missing or invalid; regional queue ownership does not make independent writers safe. + +Then [configure a store policy](/content/operate/iris/agent-memory/self-managed/api-examples.md#configure-a-webhook-policy) and deploy a [callback receiver](/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md#verify-callbacks-and-return-decisions). + +## Enable namespace routing + +Use the same switches on the server and worker. For chart-rendered configuration, add: + +```yaml +memory: + namespace_features: + api: true + routing: + policy_api: true + automatic: true + admission: true + strategies: [matcher_spec] +``` + +For a bring-your-own file, put `namespace_features` at the file root. This example enables the `matcher_spec` strategy used by the regex policy in the routing guide. + +After rollout, use the read-policy request in [Configure a policy](/content/develop/ai/context-engine/agent-memory/namespace-routing.md#configure-a-policy). A successful response confirms that the policy API is available. Read the returned `policy.enabled` value; saving a policy with `enabled: true` enables it for that store. API availability alone does not prove that automatic routing or admission is enabled, so check the process settings as well. diff --git a/content/operate/iris/agent-memory/self-managed/control-plane-api-reference/openapi-control-plane.json b/content/operate/iris/agent-memory/self-managed/control-plane-api-reference/openapi-control-plane.json index 71ee60f1fa..88849785f9 100644 --- a/content/operate/iris/agent-memory/self-managed/control-plane-api-reference/openapi-control-plane.json +++ b/content/operate/iris/agent-memory/self-managed/control-plane-api-reference/openapi-control-plane.json @@ -1513,7 +1513,7 @@ "maxLength": 64, "minLength": 1, "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$", - "description": "Unique type name within the store. Must not collide with built-in types\n(semantic, episodic, message, session_summary_view)." + "description": "Unique type name within the store. Must not collide with built-in memory types." }, "description": { "type": "string", diff --git a/content/operate/iris/agent-memory/self-managed/deploy.md b/content/operate/iris/agent-memory/self-managed/deploy.md index 63977ec4a4..d930636688 100644 --- a/content/operate/iris/agent-memory/self-managed/deploy.md +++ b/content/operate/iris/agent-memory/self-managed/deploy.md @@ -306,8 +306,6 @@ memory: - write session_summarisation: enabled: false - session_summary_view: - enabled: false image: tag: "0.8.0" controlplane: diff --git a/local_examples/agent-memory/agent_memory_sdk.py b/local_examples/agent-memory/agent_memory_sdk.py index 4d1c0fd650..26130f14b7 100644 --- a/local_examples/agent-memory/agent_memory_sdk.py +++ b/local_examples/agent-memory/agent_memory_sdk.py @@ -4,7 +4,6 @@ import os from datetime import datetime, timezone -import httpx from redis_agent_memory import AgentMemory, models @@ -20,13 +19,10 @@ def show(label, response): def main(): - with httpx.Client(headers={ - "X-Iris-User-Token": os.environ["USER_TOKEN"], - } if os.environ.get("USER_TOKEN") else {}) as transport, AgentMemory( + with AgentMemory( ENDPOINT, store_id=STORE_ID, api_key=os.environ["API_KEY"], - client=transport, ) as agent_memory: health = agent_memory.store_health() show("Store health", health) @@ -40,7 +36,6 @@ def main(): import os from datetime import datetime, timezone -import httpx from redis_agent_memory import AgentMemory, models @@ -56,13 +51,10 @@ def show(label, response): def main(): - with httpx.Client(headers={ - "X-Iris-User-Token": os.environ["USER_TOKEN"], - } if os.environ.get("USER_TOKEN") else {}) as transport, AgentMemory( + with AgentMemory( ENDPOINT, store_id=STORE_ID, api_key=os.environ["API_KEY"], - client=transport, ) as agent_memory: health = agent_memory.store_health() show("Store health", health) @@ -95,7 +87,6 @@ def main(): import os from datetime import datetime, timezone -import httpx from redis_agent_memory import AgentMemory, models @@ -111,13 +102,10 @@ def show(label, response): def main(): - with httpx.Client(headers={ - "X-Iris-User-Token": os.environ["USER_TOKEN"], - } if os.environ.get("USER_TOKEN") else {}) as transport, AgentMemory( + with AgentMemory( ENDPOINT, store_id=STORE_ID, api_key=os.environ["API_KEY"], - client=transport, ) as agent_memory: health = agent_memory.store_health() show("Store health", health) diff --git a/local_examples/agent-memory/agent_memory_sdk.ts b/local_examples/agent-memory/agent_memory_sdk.ts index 4caf88464f..fd734a40d0 100644 --- a/local_examples/agent-memory/agent_memory_sdk.ts +++ b/local_examples/agent-memory/agent_memory_sdk.ts @@ -2,7 +2,6 @@ // STEP_START connect_health_check import { AgentMemory } from "@redis-iris/agent-memory"; -import { HTTPClient } from "@redis-iris/agent-memory/lib/http"; const apiKey = process.env.API_KEY; if (!apiKey) { @@ -14,20 +13,10 @@ const storeId = ""; const sessionId = "travel-planning-session"; const userId = process.env.OWNER_ID ?? "quickstart-user"; -const httpClient = new HTTPClient(); -const userToken = process.env.USER_TOKEN; -if (userToken) { - httpClient.addHook("beforeRequest", (request) => { - request.headers.set("X-Iris-User-Token", userToken); - return request; - }); -} - const agentMemory = new AgentMemory({ serverURL, storeId, apiKey, - httpClient, }); async function run() { @@ -44,7 +33,6 @@ run().catch((error) => { // STEP_START add_session_event import { AgentMemory } from "@redis-iris/agent-memory"; -import { HTTPClient } from "@redis-iris/agent-memory/lib/http"; const apiKey = process.env.API_KEY; if (!apiKey) { @@ -56,20 +44,10 @@ const storeId = ""; const sessionId = "travel-planning-session"; const userId = process.env.OWNER_ID ?? "quickstart-user"; -const httpClient = new HTTPClient(); -const userToken = process.env.USER_TOKEN; -if (userToken) { - httpClient.addHook("beforeRequest", (request) => { - request.headers.set("X-Iris-User-Token", userToken); - return request; - }); -} - const agentMemory = new AgentMemory({ serverURL, storeId, apiKey, - httpClient, }); async function run() { @@ -102,7 +80,6 @@ run().catch((error) => { // STEP_START search_long_term_memory_basic import { AgentMemory } from "@redis-iris/agent-memory"; -import { HTTPClient } from "@redis-iris/agent-memory/lib/http"; const apiKey = process.env.API_KEY; if (!apiKey) { @@ -114,20 +91,10 @@ const storeId = ""; const sessionId = "travel-planning-session"; const userId = process.env.OWNER_ID ?? "quickstart-user"; -const httpClient = new HTTPClient(); -const userToken = process.env.USER_TOKEN; -if (userToken) { - httpClient.addHook("beforeRequest", (request) => { - request.headers.set("X-Iris-User-Token", userToken); - return request; - }); -} - const agentMemory = new AgentMemory({ serverURL, storeId, apiKey, - httpClient, }); async function run() { From d8f2a6578903f89d9fd3d01d38edeaa12a9c1256 Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Fri, 23 Oct 2026 13:24:14 -0700 Subject: [PATCH 23/26] Keep ownership enforcement guidance scoped to on-prem --- .../context-engine/agent-memory/developer-guide.md | 13 ++++--------- .../ai/context-engine/agent-memory/memory-etl.md | 2 +- .../agent-memory/namespace-routing.md | 2 +- .../ai/context-engine/agent-memory/namespaces.md | 2 +- .../ai/context-engine/agent-memory/sessions.md | 4 ++-- .../ai/context-engine/agent-memory/summary-views.md | 10 +++++----- .../agent-memory/self-managed/authentication.md | 2 +- .../iris/agent-memory/self-managed/configuration.md | 12 ++++++------ 8 files changed, 21 insertions(+), 26 deletions(-) diff --git a/content/develop/ai/context-engine/agent-memory/developer-guide.md b/content/develop/ai/context-engine/agent-memory/developer-guide.md index c89a3d5da9..9960af21fb 100644 --- a/content/develop/ai/context-engine/agent-memory/developer-guide.md +++ b/content/develop/ai/context-engine/agent-memory/developer-guide.md @@ -51,24 +51,19 @@ Some IDs come from your application; others come from Redis Agent Memory: For example, the quickstart uses `quickstart-user` as the user's ID and `travel-planning-session` for one conversation. Replace the user ID with your authenticated account ID in an application. IDs must follow the format in the [API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}). -On-prem deployments enforce ownership by default in release 0.10.2. With enforcement enabled, the first session event's `actorId` and any `ownerId` you supply must match the authenticated user ID, including case. Later events in an accessible session can use a different actor. An agent key supplies store permissions; a [delegated user token](/content/operate/iris/agent-memory/self-managed/authentication.md#delegate-a-user-with-an-agent-key) supplies the user identity for personal access. See [Server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement) before upgrading an existing deployment. +For on-prem deployments, see [Server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement) for identity requirements and configuration. ### Authorize every memory request Your application must decide which memories the user can access. Put these checks in trusted backend code before sending a request to Redis Agent Memory. Store access alone does not restrict a caller to one user's memories. Cloud store credentials also require these application checks; a Redis Cloud account user is not your application's end user. -| Ownership enforcement | Responsibility | -|:----------------------|:---------------| -| Off | Your backend enforces personal ownership and permitted shared access on every request. Owner fields and filters are metadata, not verified user identity. | -| On | The server also restricts personal records to the authenticated user and applies that restriction to searches, lists, and direct-ID requests. Your backend still enforces application rules, including team access to shared content. | - * For personal searches, set the `ownerId` filter from the authenticated user's ID. For session listing, use `filterOwnerId`. A request from `user-42` must not be able to select `user-99` as its owner. * For shared memories, check which shared namespaces that user can access, then restrict the query to those namespace IDs. Do not add a personal owner filter to that shared query: it would hide shared memories written by other users. * For requests that retrieve or change a record by ID, check that the record belongs to the user or to a shared namespace they can access. These endpoints do not all accept an owner filter; a session or memory ID is not proof of permission. Use the authenticated user on every request to choose the owner filter or the permitted shared namespaces. Search filters limit results; they do not authenticate the user. For examples, see [Namespaces](/content/develop/ai/context-engine/agent-memory/namespaces.md). -Keep mandatory owner restrictions separate from optional search conditions. Combining an owner and another filter with `filterOp: "any"` can return either owner's records when enforcement is off. `includeAll` on a list request is also a selection option, not permission to enumerate a store. +Keep mandatory owner restrictions separate from optional search conditions. Combining an owner and another filter with `filterOp: "any"` matches either condition; it does not require the owner to match. `includeAll` on a list request is also a selection option, not permission to enumerate a store. ## Build context for each agent turn @@ -136,7 +131,7 @@ A failed request is different from a successful search with no matches. Handle t | Empty `items` | No memories match yet. | Continue with session context. | | Invalid request | A field or filter is invalid. | Correct it using the error details. | | `401` | The credential is invalid or expired. | Correct the key or refresh the user's token. | -| `403` or a hidden personal-record `404` | Store access or ownership prevents the request. | Check grants and authenticated identity. An owner filter cannot grant access. | +| `403` or a hidden personal-record `404` | Store grants, or on-prem ownership enforcement, prevent the request. | Check grants and authenticated identity. An owner filter cannot grant access. | | `409` | A resource conflicts, such as an archived destination or stale revision. | Correct the destination or read the current revision before retrying. | | `423` | The resource is suspended. | Restore its availability. | | `424` | A dependency failed. | Inspect the typed error and problem type. | @@ -186,7 +181,7 @@ The ingestion-hook `503` retry error and `422` stopped error also have distinct ## Create long-term memories -Create memories directly when your application already has information to store, such as a preference submitted through a form. Set the owner and a stable record ID. Creating an eligible record with the same ID again replaces it; ownership checks and protection for document-managed records still apply. Check the bulk response for per-record errors. Direct creation does not apply automatic extraction or sensitive-data exclusions. +Create memories directly when your application already has information to store, such as a preference submitted through a form. Set the owner and a stable record ID. Creating an eligible record with the same ID again replaces it; access checks and protection for document-managed records still apply. Check the bulk response for per-record errors. Direct creation does not apply automatic extraction or sensitive-data exclusions. See [Long-term memory]({{< relref "/develop/ai/context-engine/agent-memory/long-term-memory" >}}) for creation methods, search fields, retention, and extraction controls. diff --git a/content/develop/ai/context-engine/agent-memory/memory-etl.md b/content/develop/ai/context-engine/agent-memory/memory-etl.md index e650363812..dbabf68dac 100644 --- a/content/develop/ai/context-engine/agent-memory/memory-etl.md +++ b/content/develop/ai/context-engine/agent-memory/memory-etl.md @@ -19,7 +19,7 @@ Use [session extraction](/content/develop/ai/context-engine/agent-memory/session ## Prepare the service -These examples use a self-managed service with document ingestion enabled. An operator must configure the Data Plane and worker, their model provider, and [worker authentication](/content/operate/iris/agent-memory/self-managed/authentication.md#worker-callbacks). Installing an SDK does not enable processing. See [Enable document ingestion](/content/operate/iris/agent-memory/self-managed/configuration.md#enable-document-ingestion). +MemoryETL is available in Redis Cloud and enabled in self-managed deployments. These examples use a self-managed service. For its model, processing limits, and worker settings, see [Configure document ingestion](/content/operate/iris/agent-memory/self-managed/configuration.md#enable-document-ingestion). Install the SDK using the [quickstart](/content/develop/ai/context-engine/agent-memory/quickstart.md#install-the-sdk). Set: diff --git a/content/develop/ai/context-engine/agent-memory/namespace-routing.md b/content/develop/ai/context-engine/agent-memory/namespace-routing.md index 734f67516f..599554dea2 100644 --- a/content/develop/ai/context-engine/agent-memory/namespace-routing.md +++ b/content/develop/ai/context-engine/agent-memory/namespace-routing.md @@ -13,7 +13,7 @@ weight: 14 Namespace routing selects a shared namespace for an eligible memory. Admission rules check whether a memory can enter a destination. Use routing to organize shared memories by content, and admission rules to reject content that does not belong in a namespace. -The policy applies to one store. It targets existing shared namespace resources, using their stable `namespaceId` values. It does not create namespaces or change access permissions. For self-managed deployments, follow [Enable namespace routing](/content/operate/iris/agent-memory/self-managed/configuration.md#enable-namespace-routing), then read and configure the store policy. +The policy applies to one store. It targets existing shared namespace resources, using their stable `namespaceId` values. It does not create namespaces or change access permissions. Namespace routing is available in Redis Cloud and enabled in self-managed deployments. Configure a policy to use it for a store. For self-managed process settings, see [Configure namespace routing](/content/operate/iris/agent-memory/self-managed/configuration.md#enable-namespace-routing). ## Understand placement and admission diff --git a/content/develop/ai/context-engine/agent-memory/namespaces.md b/content/develop/ai/context-engine/agent-memory/namespaces.md index 52ee5bacb7..a3e246388e 100644 --- a/content/develop/ai/context-engine/agent-memory/namespaces.md +++ b/content/develop/ai/context-engine/agent-memory/namespaces.md @@ -232,7 +232,7 @@ For direct long-term memory creation, set `namespaceRef` on each record. To move Search with `filter.namespaceRef.eq` for one namespace or `filter.namespaceRef.in` for several IDs. These filters match exact namespace IDs; they do not expand a parent into its descendants. List the children and include their IDs when you need to search several levels of a hierarchy. Keep the `ownerId` filter when recalling one user's memories. -Without server-side ownership enforcement, your application must check that the current user can access each namespace before querying it. With enforcement enabled, personal namespace access follows the root owner's authenticated User ID. Shared namespaces are accessible to callers with the required store permission; enforcement does not add team membership checks. If a shared namespace is only for one team, enforce that rule in your application or use separate stores and grants. See [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). +Your application must check that the current user can access each namespace before querying it. In on-prem deployments with ownership enforcement enabled, personal namespace access follows the root owner's authenticated user ID. Shared namespaces are accessible to callers with the required store permission. If a shared namespace is only for one team, enforce that rule in your application or use separate stores and grants. See [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). ## Create a namespace hierarchy diff --git a/content/develop/ai/context-engine/agent-memory/sessions.md b/content/develop/ai/context-engine/agent-memory/sessions.md index 78ef7f7f09..5baed23aa1 100644 --- a/content/develop/ai/context-engine/agent-memory/sessions.md +++ b/content/develop/ai/context-engine/agent-memory/sessions.md @@ -50,7 +50,7 @@ curl --fail-with-body --silent --show-error --request POST \ Send each new message once, using the same session ID throughout the conversation. -The session's `ownerId` comes from the first event's `actorId` and is returned when you retrieve the session. With [server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement), the first event's `actorId` must match the authenticated User ID. +The session's `ownerId` comes from the first event's `actorId` and is returned when you retrieve the session. In on-prem deployments with [server-side ownership enforcement](/content/operate/iris/agent-memory/self-managed/authentication.md#server-side-ownership-enforcement), the first event's `actorId` must match the authenticated user ID. ## Retrieve conversation context @@ -64,7 +64,7 @@ curl --fail-with-body --silent --show-error \ The `events` array contains the retained conversation messages. If older events have been summarized, the response also includes `summary.text`. Pass both to the model with the current user message. See [Build context for each agent turn]({{< relref "/develop/ai/context-engine/agent-memory/developer-guide#build-context-for-each-agent-turn" >}}) for an example. -Without server-side ownership enforcement, your application must check that the current user can access this session before retrieving it. The request selects a session by ID; it does not include an owner filter. +Your application must check that the current user can access this session before retrieving it. The request selects a session by ID; it does not include an owner filter. ## List sessions diff --git a/content/develop/ai/context-engine/agent-memory/summary-views.md b/content/develop/ai/context-engine/agent-memory/summary-views.md index 7c4bc4bdd1..e003419ae7 100644 --- a/content/develop/ai/context-engine/agent-memory/summary-views.md +++ b/content/develop/ai/context-engine/agent-memory/summary-views.md @@ -19,22 +19,22 @@ Summary Views differ from [session summarization](/content/develop/ai/context-en ## Prepare the service and identity -An operator must enable Summary Views and configure their model, worker, queues, and limits. An SDK installation does not enable processing. See [Enable Summary Views](/content/operate/iris/agent-memory/self-managed/configuration.md#enable-summary-views). +Summary Views are available in Redis Cloud and enabled in self-managed deployments. For self-managed model and capacity settings, see [Configure Summary Views](/content/operate/iris/agent-memory/self-managed/configuration.md#enable-summary-views). The examples use a self-managed service with ownership enforcement and [delegated user identity](/content/operate/iris/agent-memory/self-managed/authentication.md#delegate-a-user-with-an-agent-key). Set `AGENT_MEMORY_URL`, `STORE_ID`, `API_KEY`, `USER_TOKEN`, and `OWNER_ID` as described there. `OWNER_ID` must match the verified user claim. Install the SDK through the [quickstart](/content/develop/ai/context-engine/agent-memory/quickstart.md#install-the-sdk). -The server assigns the definition's immutable `ownerId`. Creation requires an authenticated user ID even with enforcement off. A store credential without user identity cannot create a view by supplying an owner field. These examples do not assume customer identity-provider setup is available in Redis Cloud. +The server assigns the definition's immutable `ownerId`. In on-prem deployments, creation requires an authenticated user ID even with enforcement off. A store credential without user identity cannot create a view by supplying an owner field. ### Choose private or shared visibility -With ownership enforcement enabled: +In on-prem deployments with ownership enforcement enabled: | Visibility | Who can read | Sources | |:-----------|:-------------|:--------| | `PRIVATE` (default) | The definition owner. | The owner's personal and store-authorized shared memories, narrowed by the definition's filters. | | `SHARED` | Callers with store read access. The owner retains lifecycle control. | Shared memories only. Personal records cannot be published through a shared view. | -Shared does not mean a particular team. Enforce team membership in your backend or use separate stores and grants. With enforcement off, store-level access is broader: `PRIVATE` does not replace backend authorization. A view records its source-access policy at creation. Changing enforcement requires replacing existing views and configuring server and worker alike. +Shared does not mean a particular team. Enforce team membership in your backend or use separate stores and grants. In on-prem deployments with enforcement off, store-level access is broader: `PRIVATE` does not replace backend authorization. A view records its source-access policy at creation. Changing on-prem enforcement requires replacing existing views and configuring server and worker alike. ## Create, refresh, and read a view @@ -200,7 +200,7 @@ Result reads validate contributing source records. Deleted, changed, or newly in ## Filter and group sources -Use `filter` for owners, sessions, stable namespace IDs, topics, memory types, creation times, and typed attributes. `filterOp` defaults to `all`; `any` combines optional filters with OR. Ownership, exact partition scope, and time-window restrictions apply separately. Legacy `namespace` labels are unsupported for Summary Views; use `namespaceRef`. +Use `filter` for owners, sessions, stable namespace IDs, topics, memory types, creation times, and typed attributes. `filterOp` defaults to `all`; `any` combines optional filters with OR. Exact partition scope and time-window restrictions apply separately, as do ownership checks in on-prem deployments. Legacy `namespace` labels are unsupported for Summary Views; use `namespaceRef`. `groupBy` accepts up to four distinct fields: `ownerId`, `sessionId`, `namespaceRef`, and `memoryType`. A result read or `target: partition` refresh must supply exactly those fields. Missing or extra scope fields are invalid. `groupBy: []` creates a singleton; omit all scope fields when reading it. diff --git a/content/operate/iris/agent-memory/self-managed/authentication.md b/content/operate/iris/agent-memory/self-managed/authentication.md index e7818179a2..0cf6a6a62b 100644 --- a/content/operate/iris/agent-memory/self-managed/authentication.md +++ b/content/operate/iris/agent-memory/self-managed/authentication.md @@ -154,7 +154,7 @@ Operation mapping: Store grants authorize access to a store. Ownership enforcement adds a check within that store: ordinary callers can access their own personal records and shared records. -Release 0.10.2 enables ownership enforcement by default in the on-prem server, worker, and chart. The Cloud runtime has a separate default and credential model. Your application remains responsible for end-user authorization when using Cloud store credentials; Cloud account users are not application end users. Customers cannot configure this on-prem setting or a customer identity provider through a Cloud account setting. +This section applies to on-prem deployments. Release 0.10.2 enables ownership enforcement by default in the on-prem server, worker, and chart. | Setting | Personal access | Shared access | | --- | --- | --- | diff --git a/content/operate/iris/agent-memory/self-managed/configuration.md b/content/operate/iris/agent-memory/self-managed/configuration.md index 2fdade950a..53526026b0 100644 --- a/content/operate/iris/agent-memory/self-managed/configuration.md +++ b/content/operate/iris/agent-memory/self-managed/configuration.md @@ -293,9 +293,9 @@ Also add `subject-grant-lookup` to the `memory-dp` runtime credential's `allowed alongside `api-key-introspect`. These are Identity Service values, not Memory's `identityService` values. `read_all_owners` is a subject-grant action; do not add it to agent-key grants. -## Enable document ingestion +## Configure document ingestion {#enable-document-ingestion} -Document ingestion is off when `document_ingestion` is omitted. In release 0.10.2, configure the server and worker together through chart-rendered values. This example sets explicit processing limits: +Document ingestion is enabled in self-managed deployments. Configure the server and worker together through chart-rendered values. This example sets explicit processing limits: ```yaml memory: @@ -336,9 +336,9 @@ For a bring-your-own configuration, place these fields at the file root instead Roll out both the server and worker. Confirm store health, then [create an extractor and submit a document](/content/develop/ai/context-engine/agent-memory/memory-etl.md). Setting `enabled: false` rejects new ingestions; it does not undo memories already written. -## Enable Summary Views +## Configure Summary Views {#enable-summary-views} -Summary Views are off by default. Configure the server and worker with the same ownership setting, model provider, and explicit bounds. For chart-rendered configuration, merge these example values under `memory`: +Summary Views are enabled in self-managed deployments. Configure the server and worker with the same ownership setting, model provider, and explicit bounds. For chart-rendered configuration, merge these example values under `memory`: ```yaml memory: @@ -431,9 +431,9 @@ For Active-Active backing databases, route every server and worker for a store t Then [configure a store policy](/content/operate/iris/agent-memory/self-managed/api-examples.md#configure-a-webhook-policy) and deploy a [callback receiver](/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md#verify-callbacks-and-return-decisions). -## Enable namespace routing +## Configure namespace routing {#enable-namespace-routing} -Use the same switches on the server and worker. For chart-rendered configuration, add: +Namespace routing is enabled in self-managed deployments. Keep these switches consistent on the server and worker. For chart-rendered configuration, use: ```yaml memory: From f6c7ac8fdf5a003034ae1ad6a4bc0917b773ceaa Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Fri, 23 Oct 2026 13:36:15 -0700 Subject: [PATCH 24/26] Clarify the Agent Memory overview --- .../develop/ai/context-engine/agent-memory/overview.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/develop/ai/context-engine/agent-memory/overview.md b/content/develop/ai/context-engine/agent-memory/overview.md index 79f52780c4..4ea0609e1a 100644 --- a/content/develop/ai/context-engine/agent-memory/overview.md +++ b/content/develop/ai/context-engine/agent-memory/overview.md @@ -15,15 +15,15 @@ aliases: ## Background summarization and extraction -Agent Memory has two writers. Your application writes session events as they happen. After you enable summarization and extraction, Agent Memory reads those events in the background and does two things without an application write: +Your application writes session events as they happen. In the background, Redis Agent Memory reads those events and: -- **Summarizes** older events into a compact summary once the session passes a configured threshold, so a long conversation doesn't blow the model's context window. +- **Summarizes** older events into a compact summary once the session passes a configured threshold, so a long conversation doesn't overflow the model's context window. - **Extracts** long-term memories, facts and preferences worth keeping, and writes them as separate, searchable records with vector embeddings. ![An application writes session events synchronously to session memory. Background extraction moves durable information into long-term memory, which the application can search directly.](/images/ai/context-engine/agent-memory-writers.svg) {width="700"} -Both run asynchronously, to keep session writes fast. Extraction also weighs a new memory against existing memories before writing it. Rather than rejecting anything that looks similar, it uses model judgment to decide whether a near-identical memory is a true duplicate or is meaningfully different and worth keeping too. +Both run asynchronously to keep session writes fast. Extraction also compares each new memory with existing ones and uses the model to decide whether it duplicates existing information or adds something new. ## Memory types @@ -37,7 +37,7 @@ You can think of Redis Agent Memory's tiers as analogous to human memory: Session memory is the working set: the current conversation's events, read and written every turn. Long-term memory is what survives after the session ends, durable enough to recall in a conversation the agent hasn't seen before. -This table is a simplified starting point, not the full picture. Redis Agent Memory also maintains an automatically updated summary of each session, as derived session state. It returns this summary separately from long-term memories. You can also define custom long-term memory types for domain-specific information. See [Memory types & extraction]({{< relref "/operate/iris/agent-memory/create-service#memory-types-and-extraction" >}}) for the complete set of built-in and custom types. +Redis Agent Memory also maintains an automatically updated summary of each session, as derived session state. It returns this summary separately from long-term memories. You can also define custom long-term memory types for domain-specific information. See [Memory types & extraction]({{< relref "/operate/iris/agent-memory/create-service#memory-types-and-extraction" >}}) for the complete set of built-in and custom types. ## Adding to session memory @@ -69,7 +69,7 @@ See [Namespaces]({{< relref "/develop/ai/context-engine/agent-memory/namespaces" ## FAQ **Do I still need a separate store for conversation state?** -No, not for that purpose. Session memory holds the ordered events of a conversation, with automatic expiration based on a time-to-live (TTL) you set, and no schema to design. It doesn't replace a general-purpose cache or your application's other session data unrelated to the conversation. +No. Session memory stores the ordered events of a conversation, with automatic expiration based on a time-to-live (TTL) you set, and no schema to design. It doesn't replace a general-purpose cache or your application's other session data unrelated to the conversation. **What happens if I write directly to long-term memory instead of letting extraction do it?** Both paths are supported. Use direct writes for bulk imports or external knowledge sources: anything that didn't originate in a conversation. Automatic extraction is for facts that emerge from session events themselves. From 0185cb1b9c34ddbd22b1779f05480945b0c90f1f Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Fri, 23 Oct 2026 14:19:14 -0700 Subject: [PATCH 25/26] Remove repetition and correct Agent Memory setup --- .../ai/context-engine/agent-memory/_index.md | 6 +- .../agent-memory/ingestion-webhooks.md | 6 +- .../agent-memory/long-term-memory.md | 10 +--- .../context-engine/agent-memory/memory-etl.md | 6 +- .../agent-memory/namespace-routing.md | 2 +- .../context-engine/agent-memory/overview.md | 4 +- .../context-engine/agent-memory/quickstart.md | 6 +- .../context-engine/agent-memory/sessions.md | 8 +-- ...-agent-memory-quickstart-create-service.md | 2 +- content/operate/iris/agent-memory/_index.md | 1 - .../iris/agent-memory/create-service.md | 8 +-- .../iris/agent-memory/model-configuration.md | 1 - .../iris/agent-memory/self-managed/_index.md | 6 +- .../agent-memory/self-managed/api-examples.md | 56 ++++++++++++------- .../iris/agent-memory/self-managed/deploy.md | 25 +++------ .../agent-memory/self-managed/operations.md | 16 +++--- .../self-managed/prerequisites.md | 20 +++---- .../agent-memory/self-managed/reference.md | 4 +- .../operate/iris/agent-memory/view-service.md | 4 -- 19 files changed, 87 insertions(+), 104 deletions(-) diff --git a/content/develop/ai/context-engine/agent-memory/_index.md b/content/develop/ai/context-engine/agent-memory/_index.md index 45b797f5e0..b8cb718890 100644 --- a/content/develop/ai/context-engine/agent-memory/_index.md +++ b/content/develop/ai/context-engine/agent-memory/_index.md @@ -9,8 +9,6 @@ description: Store agent memory for AI applications in Redis. linkTitle: Agent Memory hideListLinks: true weight: 10 -bannerText: Redis Agent Memory is currently available in preview. Features and behavior are subject to change. -bannerChildren: true aliases: - /develop/ai/agent-memory/ --- @@ -100,7 +98,7 @@ Agent: Good to know! I'll help you find some vegetarian-friendly restaurants in ## Get started with Redis Agent Memory {#get-started} -Get started with Redis Agent Memory on Redis Cloud or join the private preview for Redis Software. +Get started with Redis Agent Memory on Redis Cloud or deploy it with Redis Software.
@@ -109,7 +107,7 @@ Get started with Redis Agent Memory on Redis Cloud or join the private preview f

}}">Open the Redis Cloud setup guide

-

Redis Software private preview

+

Redis Software

Deploy Redis Agent Memory on Kubernetes with Redis Software.

}}">Open the self-managed deployment guide

diff --git a/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md b/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md index 6540044503..df29eb329f 100644 --- a/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md +++ b/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md @@ -13,7 +13,7 @@ weight: 15 Ingestion webhooks let your application accept, reject, or transform content before Redis Agent Memory saves it. A callback returns a decision; successful HTTP delivery alone does not approve a write. -The supported setup uses a self-managed deployment with the Redis background-job backend. Hooks are off by default. This guide does not assume Cloud account controls or a Cloud webhook rollout. +Ingestion webhooks require a self-managed deployment with the Redis background-job backend. Hooks are off by default. ## Choose what to review @@ -64,7 +64,7 @@ Read the timestamp, event ID, key ID, and signature from `X-Redis-Webhook-Timest This runnable receiver uses FastAPI and the Python standard library. It: 1. Checks the four signature headers and the timestamp. -1. Verifies the HMAC signature over the raw body. +1. Verifies the hash-based message authentication code (HMAC) over the raw body. 1. Returns the saved decision if the event and body match a previous request. 1. Rejects content containing `[reject]` and accepts other signed content. @@ -338,7 +338,7 @@ done {{< /multitabs >}} -Lists return `statuses` and `nextCursor`, not `items` and `nextPageToken`. Continue when a page is empty but has a cursor; ownership filtering can produce that result. Keep the same store, credential, and list parameters when following a cursor. The maximum page size is 100. +Lists return `statuses` and `nextCursor`. Pass `nextCursor` as `cursor`, even when the page is empty; ownership filtering can produce that result. Keep the same store, credential, and list parameters. The maximum page size is 100. Private pending content expires within 24 hours or the earlier operation deadline. Compact status and write receipts remain for seven days after completion or expiry. Status omits private input, generated content, prompts, endpoint configuration, and keys. diff --git a/content/develop/ai/context-engine/agent-memory/long-term-memory.md b/content/develop/ai/context-engine/agent-memory/long-term-memory.md index 0bad82eac0..1ec0fa2e63 100644 --- a/content/develop/ai/context-engine/agent-memory/long-term-memory.md +++ b/content/develop/ai/context-engine/agent-memory/long-term-memory.md @@ -47,9 +47,7 @@ A long-term memory record includes: ## Search long-term memory -Search long-term memory semantically and narrow results with filters. Scope results with filters for owners, sessions, namespaces, topics, and memory types. - -Use `ownerId` to restrict recall to the relevant user or entity. Add narrower filters when the application needs memories from a particular session, namespace, topic, or memory type. +Search by meaning and use `ownerId` to restrict recall to the relevant user or entity. Add filters for a session, namespace, topic, or memory type as needed. For request fields, filter operators, and response schemas, see [`SearchLongTermMemory`]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}#operation/SearchLongTermMemory). @@ -421,7 +419,7 @@ JSON {{< /multitabs >}} -Inspect the bulk response for per-record errors. Run the custom-memory search again to retrieve the record. Direct creation does not wait for background extraction and does not apply the extraction prompt or sensitive-data exclusions. Creating an eligible record with the same `id` again replaces that record. Ownership checks and protection for document-managed records still apply. +Inspect the bulk response for per-record errors. Run the custom-memory search again to retrieve the record. Direct creation does not wait for background extraction and does not apply the extraction prompt or sensitive-data exclusions. Creating an eligible record with the same `id` again replaces that record. Access checks and protection for document-managed records still apply. See [custom memory types]({{< relref "/operate/iris/agent-memory/create-service#custom-memory-types" >}}) for configuration requirements and limits. @@ -552,10 +550,8 @@ JSON {{< /multitabs >}} -Inspect the returned memories. They can retain the hotel name, but should not contain `DEMO-7QX9` because the exclusion prompt covers booking confirmation codes. - > [!NOTE] -> **What to expect:** A memory similar to `User booked Hotel Sakura in Tokyo` without the fictional confirmation code. If the code appears, refine the exclusion prompt and test again. Exclusions remain advisory. +> **What to expect:** A memory similar to `User booked Hotel Sakura in Tokyo` without the fictional confirmation code `DEMO-7QX9`. If the code appears, refine the exclusion prompt and test again. > [!WARNING] > Semantic exclusions are advisory and do not guarantee that sensitive information is excluded. Session content still reaches the extraction model provider. Do not use real sensitive data in this exercise. Exclusions do not apply to directly created long-term memories. diff --git a/content/develop/ai/context-engine/agent-memory/memory-etl.md b/content/develop/ai/context-engine/agent-memory/memory-etl.md index dbabf68dac..c357f781bb 100644 --- a/content/develop/ai/context-engine/agent-memory/memory-etl.md +++ b/content/develop/ai/context-engine/agent-memory/memory-etl.md @@ -26,8 +26,8 @@ Install the SDK using the [quickstart](/content/develop/ai/context-engine/agent- | Variable | Value | |:---------|:------| | `AGENT_MEMORY_URL`, `STORE_ID`, `API_KEY` | The Data Plane endpoint, store, and application credential. | -| `OWNER_ID` | The authenticated application's user ID for this document. | -| `USER_TOKEN` | The user's token for an enforced self-managed service. See [delegation](/content/operate/iris/agent-memory/self-managed/authentication.md#delegate-a-user-with-an-agent-key). | +| `OWNER_ID` | Your application's authenticated user ID for this document. | +| `USER_TOKEN` | The user's token when self-managed ownership enforcement is enabled. See [delegation](/content/operate/iris/agent-memory/self-managed/authentication.md#delegate-a-user-with-an-agent-key). | ## Create an extractor @@ -208,7 +208,7 @@ done {{< /multitabs >}} -REST returns `202 Accepted`; this confirms submission, not successful extraction. In the SDKs, the submission content is under `accepted.result`, while the status read returns `ingestion.status`. Output listing uses `offset` and `memoryIds`, not a page token or an `items` array. +REST returns `202 Accepted`; this confirms submission, not successful extraction. In the SDKs, the submission content is under `accepted.result`, while the status read returns `ingestion.status`. Output listing uses `offset` and returns `memoryIds`. ## Interpret results and recover diff --git a/content/develop/ai/context-engine/agent-memory/namespace-routing.md b/content/develop/ai/context-engine/agent-memory/namespace-routing.md index 599554dea2..e7ba2ad81f 100644 --- a/content/develop/ai/context-engine/agent-memory/namespace-routing.md +++ b/content/develop/ai/context-engine/agent-memory/namespace-routing.md @@ -33,7 +33,7 @@ An automatic move keeps the current placement and returns a per-item error if ro 1. Install the software development kit (SDK) for your language. 1. Set `AGENT_MEMORY_URL`, `STORE_ID`, and `API_KEY` as described in the [developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md#connect-to-a-redis-agent-memory-service). -This example accepts text containing `travel` into that namespace. The regular expression is case-insensitive. **PUT replaces the entire policy.** This example installs only the `travel` rule. On a store with existing rules, include those you want to keep in the `rules` array before sending the PUT. Reading the current policy does not merge it into the example automatically. +This example accepts text containing `travel` into that namespace, ignoring case. **PUT replaces the entire policy.** Include any existing rules you want to keep in `rules`; the example installs only the `travel` rule. {{< multitabs id="namespace-routing-policy" tab1="Python" tab2="TypeScript" tab3="curl" >}} diff --git a/content/develop/ai/context-engine/agent-memory/overview.md b/content/develop/ai/context-engine/agent-memory/overview.md index 4ea0609e1a..6caf531cbd 100644 --- a/content/develop/ai/context-engine/agent-memory/overview.md +++ b/content/develop/ai/context-engine/agent-memory/overview.md @@ -41,7 +41,7 @@ Redis Agent Memory also maintains an automatically updated summary of each sessi ## Adding to session memory -Your application gives Agent Memory the conversation for extracting long-term memories by appending to session memory. Think of session memory as a shared transcript that both your application and the background extraction pipeline read. +Your application appends events to session memory. Both your application and background extraction read this conversation transcript. ### Key terms @@ -56,7 +56,7 @@ Your application gives Agent Memory the conversation for extracting long-term me 2. **Extraction reads the session.** In the background, extraction reads the session's accumulated events, not just the latest one, so it can resolve context across turns. 3. **Long-term memory is written.** Facts worth keeping are stored as separate, searchable records, scoped by owner and memory type. -Extraction reads the whole session. You only need to send each new turn as it happens. You don't need to resend earlier turns for a later one to be understood in context. +Send each new turn once. Extraction reads earlier events from the session, so you don't need to resend them. ## Namespaces diff --git a/content/develop/ai/context-engine/agent-memory/quickstart.md b/content/develop/ai/context-engine/agent-memory/quickstart.md index e2f3c7d133..8d75a4be43 100644 --- a/content/develop/ai/context-engine/agent-memory/quickstart.md +++ b/content/develop/ai/context-engine/agent-memory/quickstart.md @@ -125,7 +125,7 @@ curl --fail-with-body --silent --show-error \ "$AGENT_MEMORY_URL/v1/stores/$STORE_ID/health" | jq {{< /clients-example >}} -The store health response checks the configured Store ID and reports the status of its memory features. Inspect the overall `status` and individual `features` before continuing. A healthy response looks like: +The health response reports the status of the selected store's memory features. A healthy response looks like: ```json { @@ -150,7 +150,7 @@ The store health response checks the configured Store ID and reports the status } ``` -Run Python examples with `python quickstart.py` or TypeScript examples with `npx tsx quickstart.ts`. Each SDK example includes its client setup. Replace the file with the complete example for the next step, and use the same connection values and identifiers. Run curl commands in the shell where you exported the connection values. +Each SDK example includes its client setup. Replace the file at each step, keeping the same connection values and identifiers. Run curl commands in the shell where you exported the connection values. @@ -223,7 +223,7 @@ curl --fail-with-body --silent --show-error \ JSON {{< /clients-example >}} -Run the search. The `items` array should contain memories derived from the conversation, such as the vegetarian requirement or preference for spicy food. Extraction is asynchronous, so run the search again if the array is empty. +Run the search and inspect `items`. Extraction is asynchronous, so run the search again if the array is empty. > [!NOTE] > **What to expect:** Results similar to `User is a vegetarian` and `User prefers spicy food`. Your application did not create these memories directly. Redis Agent Memory derived them from the session event. The exact text and memory types can vary. diff --git a/content/develop/ai/context-engine/agent-memory/sessions.md b/content/develop/ai/context-engine/agent-memory/sessions.md index 5baed23aa1..2fdd3a68c7 100644 --- a/content/develop/ai/context-engine/agent-memory/sessions.md +++ b/content/develop/ai/context-engine/agent-memory/sessions.md @@ -70,7 +70,7 @@ Your application must check that the current user can access this session before To let a user reopen an earlier conversation, list their sessions and retrieve the selected session by ID. Use `GET /v1/stores/{storeId}/sessions`, `list_session_summaries` in Python, or `listSessionSummaries` in TypeScript. These rows do not contain events or generated summary text. See [List and sort sessions](/content/develop/ai/context-engine/agent-memory/list-sessions.md) for SDK examples and pagination. -For session IDs only, use `GET /v1/stores/{storeId}/session-memory` lists IDs by owner or namespace; it does not return the conversation events. Reuse the curl connection values from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#save-the-connection-values" >}}). +For session IDs only, use `GET /v1/stores/{storeId}/session-memory` with an owner or namespace filter. Reuse the curl connection values from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#save-the-connection-values" >}}). For a user's personal conversations, set `filterOwnerId` to that user's authenticated ID. A namespace-only query is suitable for shared conversations only after your application checks access to that namespace. Reserve `includeAll=true` for callers your application authorizes to list the whole store. See [Authorize every memory request](/content/develop/ai/context-engine/agent-memory/developer-guide.md#authorize-every-memory-request). @@ -133,9 +133,7 @@ To group a session and its extracted memories, use an optional [namespace]({{< r {{< embed-md "agent-memory-continue-from-quickstart.md" >}} -Automatic summarization condenses older events and retains the most recent events in full. The retrieved session then contains a `summary` object and the recent `events` array, so the application can provide useful history without filling the model's context window with every original message. - -Follow [Configure automatic summarization](#configure-automatic-summarization). Set **Summarize after** to `6` and **Keep most recent** to `2` for this example. When the session reaches six events, Redis Agent Memory summarizes the older events and retains the two most recent events in full. +Follow [Configure automatic summarization](#configure-automatic-summarization). Set **Summarize after** to `6` and **Keep most recent** to `2` for this example. The retrieved session will contain a `summary` object and the two most recent events in `events`. ### Add conversation turns @@ -250,5 +248,3 @@ Run the retrieval again after a short wait if `summary` is not present. Compare > [!NOTE] > **What to expect:** A `summary` object that preserves details such as Tokyo, Kyoto, the travel dates, and food preferences. `summarizedUpToEventId` identifies the last event covered by the summary, while `events` contains the newer turns that remain in full. The exact summary text can vary. - -See [automatic summarization configuration]({{< relref "/operate/iris/agent-memory/create-service#automatic-summarization" >}}) for details. diff --git a/content/embeds/rc-agent-memory-quickstart-create-service.md b/content/embeds/rc-agent-memory-quickstart-create-service.md index 07a1093551..44a2eeb6a0 100644 --- a/content/embeds/rc-agent-memory-quickstart-create-service.md +++ b/content/embeds/rc-agent-memory-quickstart-create-service.md @@ -1,6 +1,6 @@ 1. Sign in to the [Redis Cloud console](https://cloud.redis.io/). 1. Select **Agent Memory** from the navigation menu. -1. If Redis Cloud displays the public preview terms, review and accept them. +1. If Redis Cloud displays service terms, review and accept them. 1. Select **Create custom service**. 1. Enter a service name, select an eligible database, and select its `default` user. 1. Under **Memory configuration**, enter these values: diff --git a/content/operate/iris/agent-memory/_index.md b/content/operate/iris/agent-memory/_index.md index 83cc23a099..affa13b64d 100644 --- a/content/operate/iris/agent-memory/_index.md +++ b/content/operate/iris/agent-memory/_index.md @@ -9,7 +9,6 @@ categories: - iris weight: 10 hideListLinks: true -bannerText: Redis Agent Memory on Redis Cloud is currently available as a public preview. Features and behavior are subject to change. --- Redis Agent Memory provides persistent session memory and long-term memory for AI agents and applications. diff --git a/content/operate/iris/agent-memory/create-service.md b/content/operate/iris/agent-memory/create-service.md index 713352a351..ddad9d8b35 100644 --- a/content/operate/iris/agent-memory/create-service.md +++ b/content/operate/iris/agent-memory/create-service.md @@ -9,7 +9,6 @@ hideListLinks: true linktitle: Create service title: Create an Agent Memory service weight: 5 -bannerText: Redis Agent Memory on Redis Cloud is currently available as a public preview. Features and behavior are subject to change. aliases: - /operate/rc/context-engine/agent-memory/create-service/ - /operate/rc/agent-memory/create-service/ @@ -22,7 +21,7 @@ Redis Agent Memory provides a persistent, structured memory layer that AI agents To create a Redis Agent Memory service, you will need a Redis Cloud database. If you don't have one, see [Create a database]({{< relref "/operate/rc/databases/create-database" >}}). -During public preview, Redis Agent Memory doesn't support [Redis Flex]({{< relref "operate/rc/databases/create-database/create-flex-database">}}) databases, databases using [AWS PrivateLink]({{< relref "operate/rc/security/aws-privatelink">}}) connectivity, [Active-Active]({{< relref "/operate/rc/databases/active-active" >}}) databases, or databases with the [default user]({{< relref "/operate/rc/security/access-control/data-access-control/default-user" >}}) turned off. +Redis Agent Memory doesn't support [Redis Flex]({{< relref "operate/rc/databases/create-database/create-flex-database">}}) databases, databases using [AWS PrivateLink]({{< relref "operate/rc/security/aws-privatelink">}}) connectivity, [Active-Active]({{< relref "/operate/rc/databases/active-active" >}}) databases, or databases with the [default user]({{< relref "/operate/rc/security/access-control/data-access-control/default-user" >}}) turned off. ## Create an Agent Memory service @@ -76,7 +75,7 @@ The **General settings** section defines basic properties of your service. |:----------------------|:----------| | **Service name** | Enter a name for your Agent Memory service. We recommend you use a name that describes your service's purpose. | | **Select database** | Select the Redis Cloud database to use for this service from the list. | -| **User for this service** | The [database access user]({{< relref "/operate/rc/security/access-control/data-access-control/role-based-access-control" >}}) to use for this service. Agent Memory only supports the [`default` user]({{< relref "/operate/rc/security/access-control/data-access-control/default-user" >}}) during public preview. | +| **User for this service** | The [database access user]({{< relref "/operate/rc/security/access-control/data-access-control/role-based-access-control" >}}) to use for this service. Agent Memory only supports the [`default` user]({{< relref "/operate/rc/security/access-control/data-access-control/default-user" >}}). | ### Memory configuration @@ -164,9 +163,6 @@ The **Sensitive-data exclusions** section lets you define information that shoul Detector matches are applied deterministically. Semantic exclusions only steer the extraction model, so they are advisory. -> [!NOTE] -> Sensitive-data exclusions are an early-stage feature, enabled for selected accounts. If you want to try them, contact your Redis representative or [contact sales](https://redis.io/contact/). - ![The Sensitive-data exclusions section.](/images/rc/agent-memory-sensitive-data-exclusions.png) Exclusions are applied when long-term memories are created from a session. Session memory is unaffected: events and their summaries are kept as sent. Exclusions are also not applied to long-term memories your application creates directly through the API or an SDK. diff --git a/content/operate/iris/agent-memory/model-configuration.md b/content/operate/iris/agent-memory/model-configuration.md index 075a70da74..82dbd0c192 100644 --- a/content/operate/iris/agent-memory/model-configuration.md +++ b/content/operate/iris/agent-memory/model-configuration.md @@ -9,7 +9,6 @@ hideListLinks: true linktitle: Configure AI models title: Configure AI models for Agent Memory weight: 10 -bannerText: Redis Agent Memory on Redis Cloud is currently available as a public preview. Features and behavior are subject to change. --- You can use Redis-managed credentials for the AI models used by an Agent Memory service, or bring your own model provider API key. diff --git a/content/operate/iris/agent-memory/self-managed/_index.md b/content/operate/iris/agent-memory/self-managed/_index.md index 744158c82d..1fd40fa3df 100644 --- a/content/operate/iris/agent-memory/self-managed/_index.md +++ b/content/operate/iris/agent-memory/self-managed/_index.md @@ -9,7 +9,7 @@ description: Deploy, configure, secure, and operate Redis Agent Memory on a self linkTitle: Self-managed weight: 40 hideListLinks: true -bannerText: Redis Agent Memory self-managed is currently in private preview and subject to change. A license key is required to deploy. Contact your Redis representative or [contact sales](https://redis.io/contact/). +bannerText: A license key is required to deploy. Contact your Redis representative or [contact sales](https://redis.io/contact/). bannerChildren: true aliases: - /develop/ai/context-engine/agent-memory/self-managed/ @@ -31,8 +31,8 @@ documents the self-managed admin endpoints for stores. ## What you are deploying -The `redis-agent-memory` chart `0.8.0` deploys the Data Plane, workers, and Control Plane. -Install the `iris-identity-service` chart `0.10.0` as a separate release first, then connect +The `redis-agent-memory` chart `0.10.2` deploys the Data Plane, workers, and Control Plane. +Install the `iris-identity-service` chart `0.10.2` as a separate release first, then connect Memory to it. Memory does not deploy Identity Service resources or include it as a subchart. You provide the Redis databases. Follow [Deploy](/content/operate/iris/agent-memory/self-managed/deploy.md) to configure both releases. diff --git a/content/operate/iris/agent-memory/self-managed/api-examples.md b/content/operate/iris/agent-memory/self-managed/api-examples.md index b3ddc8616b..84e35ceb0a 100644 --- a/content/operate/iris/agent-memory/self-managed/api-examples.md +++ b/content/operate/iris/agent-memory/self-managed/api-examples.md @@ -216,7 +216,7 @@ See [Create an extractor](#create-an-extractor) for extractor administration and ## Create an extractor -Enable [document ingestion](/content/operate/iris/agent-memory/self-managed/configuration.md#enable-document-ingestion). Set `AGENT_MEMORY_CONTROL_PLANE_URL`, `AGENT_MEMORY_SERVICE_TOKEN`, and `STORE_ID` as described in [Manage a service with the Control Plane SDK](#manage-a-service-with-the-control-plane-sdk). +For model and processing settings, see [Configure document ingestion](/content/operate/iris/agent-memory/self-managed/configuration.md#enable-document-ingestion). Set `AGENT_MEMORY_CONTROL_PLANE_URL`, `AGENT_MEMORY_SERVICE_TOKEN`, and `STORE_ID` as described in [Manage a service with the Control Plane SDK](#manage-a-service-with-the-control-plane-sdk). First register a custom memory type on the store. For a new `document_fact` type, run this once: @@ -325,7 +325,7 @@ jq --arg url "$HOOK_URL" \ "$AGENT_MEMORY_CONTROL_PLANE_URL/v1/stores/$STORE_ID/ingestion-hooks" > hook-config-new.json ``` -The response returns each phase's `keyId` and `signingKeyConfigured`, never its key. Load those IDs and original keys into the receiver. The [receiver example](/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md#verify-callbacks-and-return-decisions) can get it from: +The response returns each phase's `keyId` and `signingKeyConfigured`, never its key. Supply those IDs and original keys to the [receiver example](/content/develop/ai/context-engine/agent-memory/ingestion-webhooks.md#verify-callbacks-and-return-decisions) through `HOOK_KEYS_JSON`: ```sh export HOOK_KEYS_JSON="$(jq -c '{(.beforeInput.keyId):env.HOOK_INPUT_KEY,(.beforeApply.keyId):env.HOOK_OUTPUT_KEY}' hook-config-new.json)" @@ -472,28 +472,30 @@ Set variables: DP_URL="http://localhost:9000" STORE_ID="" RAM_AGENT_KEY="" +OWNER_ID="" +USER_TOKEN="" ``` -For auth-disabled deployments, omit the `Authorization` header and rely on the -deployment's hosting controls. +With ownership enforcement enabled, [configure delegation](/content/operate/iris/agent-memory/self-managed/authentication.md#delegate-a-user-with-an-agent-key), set `USER_TOKEN` to the user's access token, and set `OWNER_ID` to its verified user ID. If enforcement is disabled, unset `USER_TOKEN` and enforce access in your backend. For auth-disabled deployments, also omit `Authorization` and rely on the deployment's hosting controls. ### Write a session event ```bash curl -sS -X POST "$DP_URL/v1/stores/$STORE_ID/session-memory/events" \ -H "Authorization: Bearer $RAM_AGENT_KEY" \ + -H "X-Iris-User-Token: ${USER_TOKEN:-}" \ -H "Content-Type: application/json" \ - -d '{ + -d "$(jq -n --arg owner "$OWNER_ID" --arg now "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" '{ "sessionId": "session-001", - "actorId": "user-001", + "actorId": $owner, "role": "USER", "content": [ { "text": "What is the capital of France?" } ], - "createdAt": "2026-06-25T18:00:00Z" - }' + "createdAt": $now + }')" ``` Request body fields: @@ -510,7 +512,8 @@ Request body fields: ```bash curl -sS "$DP_URL/v1/stores/$STORE_ID/session-memory/session-001" \ - -H "Authorization: Bearer $RAM_AGENT_KEY" + -H "Authorization: Bearer $RAM_AGENT_KEY" \ + -H "X-Iris-User-Token: ${USER_TOKEN:-}" ``` ### List sessions @@ -518,15 +521,18 @@ curl -sS "$DP_URL/v1/stores/$STORE_ID/session-memory/session-001" \ List sessions for a specific owner: ```bash -curl -sS "$DP_URL/v1/stores/$STORE_ID/session-memory?filterOwnerId=user-001" \ - -H "Authorization: Bearer $RAM_AGENT_KEY" +curl -sS --get "$DP_URL/v1/stores/$STORE_ID/session-memory" \ + -H "Authorization: Bearer $RAM_AGENT_KEY" \ + -H "X-Iris-User-Token: ${USER_TOKEN:-}" \ + --data-urlencode "filterOwnerId=$OWNER_ID" ``` -List all sessions: +List all sessions the caller can access: ```bash curl -sS "$DP_URL/v1/stores/$STORE_ID/session-memory?includeAll=true" \ - -H "Authorization: Bearer $RAM_AGENT_KEY" + -H "Authorization: Bearer $RAM_AGENT_KEY" \ + -H "X-Iris-User-Token: ${USER_TOKEN:-}" ``` `filterOwnerId` and `includeAll` are mutually exclusive. @@ -535,8 +541,13 @@ The `/session-memory` list returns session IDs. To get session rows ordered by recent activity, use `/sessions`: ```bash -curl -sS "$DP_URL/v1/stores/$STORE_ID/sessions?filterOwnerId=user-001&sortBy=updatedAt&sortOrder=desc&limit=20" \ - -H "Authorization: Bearer $RAM_AGENT_KEY" +curl -sS --get "$DP_URL/v1/stores/$STORE_ID/sessions" \ + -H "Authorization: Bearer $RAM_AGENT_KEY" \ + -H "X-Iris-User-Token: ${USER_TOKEN:-}" \ + --data-urlencode "filterOwnerId=$OWNER_ID" \ + --data-urlencode 'sortBy=updatedAt' \ + --data-urlencode 'sortOrder=desc' \ + --data-urlencode 'limit=20' ``` Each row includes `sessionId` and, when known, `ownerId`, `createdAt`, @@ -544,7 +555,7 @@ Each row includes `sessionId` and, when known, `ownerId`, `createdAt`, The default order is `updatedAt` descending. You can also sort by `sessionId` or `createdAt`, each in ascending or descending order. Send `nextPageToken` as `pageToken` with the same filters and sort options to fetch the next page. -The new route uses the same filter and authorization rules as `/session-memory`. +Both routes use the same filter and authorization rules. ### Create long-term memories directly @@ -553,8 +564,9 @@ Create a personal namespace for the user's preferences and save its ID. Run this ```bash NAMESPACE_ID=$(curl --fail-with-body -sS -X POST "$DP_URL/v1/stores/$STORE_ID/namespaces" \ -H "Authorization: Bearer $RAM_AGENT_KEY" \ + -H "X-Iris-User-Token: ${USER_TOKEN:-}" \ -H "Content-Type: application/json" \ - -d '{"name":"preferences","scope":"PERSONAL","ownerId":"user-001"}' \ + -d "$(jq -n --arg owner "$OWNER_ID" '{name:"preferences",scope:"PERSONAL",ownerId:$owner}')" \ | jq -er '.namespace.namespaceId') export NAMESPACE_ID ``` @@ -564,14 +576,15 @@ After the request succeeds, place the memory in that namespace: ```bash curl -sS -X POST "$DP_URL/v1/stores/$STORE_ID/long-term-memory" \ -H "Authorization: Bearer $RAM_AGENT_KEY" \ + -H "X-Iris-User-Token: ${USER_TOKEN:-}" \ -H "Content-Type: application/json" \ - -d "$(jq -n --arg ns "$NAMESPACE_ID" '{ + -d "$(jq -n --arg ns "$NAMESPACE_ID" --arg owner "$OWNER_ID" '{ "memories": [ { "id": "pref-email-updates", "text": "User prefers email updates.", "memoryType": "semantic", - "ownerId": "user-001", + "ownerId": $owner, "namespaceRef": {"namespaceId": $ns}, "topics": ["communications"] } @@ -588,13 +601,14 @@ Filter by the namespace ID to recall preferences from that namespace: ```bash curl -sS -X POST "$DP_URL/v1/stores/$STORE_ID/long-term-memory/search" \ -H "Authorization: Bearer $RAM_AGENT_KEY" \ + -H "X-Iris-User-Token: ${USER_TOKEN:-}" \ -H "Content-Type: application/json" \ - -d "$(jq -n --arg ns "$NAMESPACE_ID" '{ + -d "$(jq -n --arg ns "$NAMESPACE_ID" --arg owner "$OWNER_ID" '{ "text": "How should we contact this user?", "filter": { "namespaceRef": {"eq": $ns}, "ownerId": { - "eq": "user-001" + "eq": $owner } }, "limit": 5 diff --git a/content/operate/iris/agent-memory/self-managed/deploy.md b/content/operate/iris/agent-memory/self-managed/deploy.md index d930636688..fb1f117078 100644 --- a/content/operate/iris/agent-memory/self-managed/deploy.md +++ b/content/operate/iris/agent-memory/self-managed/deploy.md @@ -307,12 +307,12 @@ memory: session_summarisation: enabled: false image: - tag: "0.8.0" + tag: "0.10.2" controlplane: internalToken: existingSecret: memory-internal-token image: - tag: "0.8.0" + tag: "0.10.2" config: render: true configData: @@ -347,9 +347,8 @@ tls: `/etc/identity-service/runtime/memory-dp/token`. Do not hand-set the introspection endpoint or credential under `memory.auth` or `shared.auth`. - `tenantId: default` matches `runtime.apiKeyTenant.singleTenantID` in `values-ids.yaml`. The Control Plane creates admin-token stores in this tenant. -- The published Memory chart defaults `image.tag` and `controlplane.image.tag` to `0.8.0`; - the example pins them explicitly. Identity Service uses its own chart's application version, - `0.10.0`, independently. +- Both charts use application version `0.10.2`. The example pins Memory's Data Plane + and Control Plane images to that version. {{< note >}} No external model provider? To try the API with local models, see [Evaluate locally](#evaluate-locally). @@ -474,21 +473,11 @@ The response's `token` is the agent key. It is shown only once. ## 8. Make one authenticated call ```bash -curl -sS -X POST "http://localhost:9000/v1/stores//session-memory/events" \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - -d '{ - "sessionId": "session-001", - "actorId": "user-001", - "role": "USER", - "content": [{"text": "What is the capital of France?"}], - "createdAt": "2026-06-25T18:00:00Z" - }' +curl --fail-with-body -sS "http://localhost:9000/v1/stores//health" \ + -H "Authorization: Bearer " ``` -The first long-term memories appear up to 5 minutes after the session events, because promotion -runs at the end of a clock-aligned 300-second window. To shorten the wait, set -`extractionCadence.activeIntervalSeconds` (60–600) on the store. No API triggers promotion. +Check that the store and its memory features are healthy. This verifies the agent key's store access without requiring a user identity. To write personal data, follow the [Data Plane examples](/content/operate/iris/agent-memory/self-managed/api-examples.md#data-plane-api-examples), including delegated user setup. ## Evaluate locally diff --git a/content/operate/iris/agent-memory/self-managed/operations.md b/content/operate/iris/agent-memory/self-managed/operations.md index 585e6bef46..ee6380f736 100644 --- a/content/operate/iris/agent-memory/self-managed/operations.md +++ b/content/operate/iris/agent-memory/self-managed/operations.md @@ -82,12 +82,12 @@ kubectl -n create secret generic memory-dp-credential \ RUNTIME_CREDENTIAL_CHECKSUM="$(shasum -a 256 ./memory-dp-token | awk '{print $1}')" helm upgrade ids redis-ai/iris-identity-service \ - --version 0.10.0 \ + --version 0.10.2 \ --namespace \ -f values-ids.yaml \ --set-string "runtime.serviceCredentials[0].existingSecretChecksum=$RUNTIME_CREDENTIAL_CHECKSUM" helm upgrade redis-agent-memory redis-ai/redis-agent-memory \ - --version 0.8.0 \ + --version 0.10.2 \ --namespace \ -f ram-values.yaml \ --set-string "identityService.credential.existingSecretChecksum=$RUNTIME_CREDENTIAL_CHECKSUM" @@ -174,8 +174,8 @@ kubectl -n rollout restart deployment Memory and Identity Service have independent release versions. Upgrade each with its own values file and verify their shared connection contract afterward. A Memory upgrade does not -upgrade Identity Service. The published pair documented here is Memory `0.8.0` and Identity -Service `0.10.0`. +upgrade Identity Service. The published pair documented here is Memory `0.10.2` and Identity +Service `0.10.2`. For updates within a supported release path: @@ -287,15 +287,15 @@ Memory also ships a `values-fips.yaml` overlay. Apply it only to Memory, and con Identity Service release's posture separately: ```bash -helm pull redis-ai/redis-agent-memory --version 0.8.0 --untar +helm pull redis-ai/redis-agent-memory --version 0.10.2 --untar helm upgrade redis-agent-memory redis-ai/redis-agent-memory \ - --version 0.8.0 \ + --version 0.10.2 \ --namespace \ -f ram-values.yaml \ -f redis-agent-memory/values-fips.yaml helm upgrade ids redis-ai/iris-identity-service \ - --version 0.10.0 \ + --version 0.10.2 \ --namespace \ -f values-ids.yaml ``` @@ -346,7 +346,7 @@ the direct caller also has a valid Redis Agent Memory credential. The chart includes `networkpolicy.reference.yaml` at the chart root as a reference manifest. Get it with -`helm pull redis-ai/redis-agent-memory --version 0.8.0 --untar`. It is not templated because allowed callers are +`helm pull redis-ai/redis-agent-memory --version 0.10.2 --untar`. It is not templated because allowed callers are environment-specific. Customize the placeholders before applying it: diff --git a/content/operate/iris/agent-memory/self-managed/prerequisites.md b/content/operate/iris/agent-memory/self-managed/prerequisites.md index 75138b4ff0..1f3323f2a2 100644 --- a/content/operate/iris/agent-memory/self-managed/prerequisites.md +++ b/content/operate/iris/agent-memory/self-managed/prerequisites.md @@ -18,7 +18,7 @@ aliases: Redis Agent Memory is distributed as container images on Docker Hub plus a Helm chart in the Redis AI Helm repository. The chart deploys the Redis Agent Memory Data Plane, Redis Agent Memory workers, and the Redis Agent Memory Control Plane. Install Identity -Service separately with the `iris-identity-service` chart `0.10.0` before connecting Memory chart `0.8.0`. +Service separately with the `iris-identity-service` chart `0.10.2` before connecting Memory chart `0.10.2`. You provide the Redis databases, provider credentials, Kubernetes exposure, and license material used by the deployment. @@ -32,7 +32,7 @@ license material used by the deployment. | Item | Where it comes from | | ---- | ------------------- | | Container images | Docker Hub: `redislabs/agent-memory`, `redislabs/agent-memory-control-plane`, and `redislabs/iris-identity-service` | -| Helm charts | `redis-agent-memory` `0.8.0` and `iris-identity-service` `0.10.0` in the Redis AI Helm repository | +| Helm charts | `redis-agent-memory` `0.10.2` and `iris-identity-service` `0.10.2` in the Redis AI Helm repository | | Redis databases | You provide Store Redis, Job Redis, and Metadata Redis | | License key | Contact your Redis representative or [contact sales](https://redis.io/contact/). | | Provider credentials | You provide embedding provider credentials and, when worker features are enabled, promotion or summarization LLM credentials | @@ -124,14 +124,14 @@ To supply a complete configuration file in your own Secret instead, see ## Release artifacts and image tags -The published Memory chart `0.8.0` defaults `image.tag` and `controlplane.image.tag` to `0.8.0`. -Identity Service chart `0.10.0` defaults its image to its own application version, `0.10.0`. +The published Memory chart `0.10.2` defaults `image.tag` and `controlplane.image.tag` to `0.10.2`. +Identity Service chart `0.10.2` defaults its image to its own application version, `0.10.2`. Set tags explicitly when pinning images or using a mirror. The releases have independent versions. Both charts are published at `https://helm.redis.io/ai` as `redis-ai/redis-agent-memory` and `redis-ai/iris-identity-service`. Their standard images are -`redislabs/agent-memory:0.8.0`, `redislabs/agent-memory-control-plane:0.8.0`, and -`redislabs/iris-identity-service:0.10.0`. +`redislabs/agent-memory:0.10.2`, `redislabs/agent-memory-control-plane:0.10.2`, and +`redislabs/iris-identity-service:0.10.2`. ## Air-gapped and private registry installs @@ -147,9 +147,9 @@ Mirror these images into your internal registry: | `python:3.13-alpine` | Optional, with `tests.smoke` | | `curlimages/curl:8.17.0` | Optional, for `helm test` on the Identity Service release (`tests.image` in `values-ids.yaml`) | -For this release, `` is `0.8.0` and `` is `0.10.0`. -Inspect each package with `helm show chart redis-ai/redis-agent-memory --version 0.8.0` -and `helm show chart redis-ai/iris-identity-service --version 0.10.0`. Memory does not pin +For this release, `` is `0.10.2` and `` is `0.10.2`. +Inspect each package with `helm show chart redis-ai/redis-agent-memory --version 0.10.2` +and `helm show chart redis-ai/iris-identity-service --version 0.10.2`. Memory does not pin or install the Identity Service chart. ```bash @@ -198,7 +198,7 @@ Configure the separate Identity Service release in `values-ids.yaml`: ```yaml image: repository: registry.example.com/redislabs/iris-identity-service - tag: "0.10.0" + tag: "0.10.2" tests: image: registry.example.com/curlimages/curl:8.17.0 global: diff --git a/content/operate/iris/agent-memory/self-managed/reference.md b/content/operate/iris/agent-memory/self-managed/reference.md index 80d5199240..b8706001c2 100644 --- a/content/operate/iris/agent-memory/self-managed/reference.md +++ b/content/operate/iris/agent-memory/self-managed/reference.md @@ -82,8 +82,8 @@ CSI-synced Secrets, make sure a Control Plane pod also mounts the corresponding | Need | Reference | | --- | --- | -| Helm chart values | `values.yaml` at the chart root (`helm pull redis-ai/redis-agent-memory --version 0.8.0 --untar`) | -| Identity Service chart values | `helm show values redis-ai/iris-identity-service --version 0.10.0` | +| Helm chart values | `values.yaml` at the chart root (`helm pull redis-ai/redis-agent-memory --version 0.10.2 --untar`) | +| Identity Service chart values | `helm show values redis-ai/iris-identity-service --version 0.10.2` | | FIPS values overlay | `values-fips.yaml` at the chart root | | NetworkPolicy reference | `networkpolicy.reference.yaml` at the chart root | | Chart README | `README.md` at the chart root | diff --git a/content/operate/iris/agent-memory/view-service.md b/content/operate/iris/agent-memory/view-service.md index 52b11ec618..3b8b09d2a7 100644 --- a/content/operate/iris/agent-memory/view-service.md +++ b/content/operate/iris/agent-memory/view-service.md @@ -9,7 +9,6 @@ hideListLinks: true linktitle: View service title: View and manage Agent Memory service weight: 15 -bannerText: Redis Agent Memory on Redis Cloud is currently available as a public preview. Features and behavior are subject to change. aliases: - /operate/rc/context-engine/agent-memory/view-service/ - /operate/rc/agent-memory/view-service/ @@ -101,9 +100,6 @@ To change a custom memory type's name or fields, you must create a new service. The **Sensitive-data exclusions** section shows the [sensitive-data exclusions]({{< relref "/operate/iris/agent-memory/create-service#sensitive-data-exclusions" >}}) configured for the service. -> [!NOTE] -> Sensitive-data exclusions are an early-stage feature, enabled for selected accounts. If you want to try them, contact your Redis representative or [contact sales](https://redis.io/contact/). - ![The Sensitive-data exclusions section for the Agent Memory service.](/images/rc/agent-memory-view-sensitive-data-exclusions.png) | Setting name |Description| From ecb3956c8ee6155ea13f7be0e6c9bb6d5244f5e8 Mon Sep 17 00:00:00 2001 From: Robert Shelton Date: Mon, 26 Oct 2026 16:04:20 -0400 Subject: [PATCH 26/26] Nest list-sessions and namespace-routing under parents; use tile cards on Agent Memory index Addresses Michelle's structural review comments on #4001. Co-Authored-By: Claude Opus 5.5 --- .../ai/context-engine/agent-memory/_index.md | 14 +++++++------- .../context-engine/agent-memory/developer-guide.md | 2 +- .../ai/context-engine/agent-memory/memory-etl.md | 2 +- .../{namespaces.md => namespaces/_index.md} | 2 +- .../{ => namespaces}/namespace-routing.md | 6 ++++-- .../{sessions.md => sessions/_index.md} | 2 +- .../agent-memory/{ => sessions}/list-sessions.md | 2 ++ .../context-engine/agent-memory/summary-views.md | 2 +- .../agent-memory/self-managed/configuration.md | 2 +- 9 files changed, 19 insertions(+), 15 deletions(-) rename content/develop/ai/context-engine/agent-memory/{namespaces.md => namespaces/_index.md} (99%) rename content/develop/ai/context-engine/agent-memory/{ => namespaces}/namespace-routing.md (93%) rename content/develop/ai/context-engine/agent-memory/{sessions.md => sessions/_index.md} (99%) rename content/develop/ai/context-engine/agent-memory/{ => sessions}/list-sessions.md (98%) diff --git a/content/develop/ai/context-engine/agent-memory/_index.md b/content/develop/ai/context-engine/agent-memory/_index.md index b8cb718890..69edb9f244 100644 --- a/content/develop/ai/context-engine/agent-memory/_index.md +++ b/content/develop/ai/context-engine/agent-memory/_index.md @@ -22,12 +22,12 @@ When enabled, automatic summarization compacts session memory by summarizing old Access Redis Agent Memory through the Python and TypeScript SDKs or its REST API. It works with any agent framework or LLM provider.
- {{< image-card image="images/ai-model.svg" alt="Overview icon" title="Overview — What's the same and what's different if you already know Redis" url="/develop/ai/context-engine/agent-memory/overview" >}} - {{< image-card image="images/ai-cube.svg" alt="Quickstart icon" title="Quickstart — Make your first requests" url="/develop/ai/context-engine/agent-memory/quickstart" >}} - {{< image-card image="images/ai-lib.svg" alt="Developer guide icon" title="Developer guide — Add memory to your application" url="/develop/ai/context-engine/agent-memory/developer-guide" >}} - {{< image-card image="images/ai-LLM-memory.svg" alt="Sessions icon" title="Sessions — Store conversation events" url="/develop/ai/context-engine/agent-memory/sessions" >}} - {{< image-card image="images/ai-brain-2.svg" alt="Long-term memory icon" title="Long-term memory — Recall information across conversations" url="/develop/ai/context-engine/agent-memory/long-term-memory" >}} - {{< image-card image="images/ai-semantic-routing.svg" alt="Namespaces icon" title="Namespaces — Organize your memories" url="/develop/ai/context-engine/agent-memory/namespaces" >}} + {{< tile-card icon="images/icon_logo/icon-data-structures-64-midnight.png" title="Overview" description="What's the same and what's different if you already know Redis" url="/develop/ai/context-engine/agent-memory/overview" >}} + {{< tile-card icon="images/icon_logo/icon-developers-64-midnight.png" title="Quickstart" description="Make your first requests" url="/develop/ai/context-engine/agent-memory/quickstart" >}} + {{< tile-card icon="images/dev/icons/icon-redis-code-64-duotone.png" title="Developer guide" description="Add memory to your application" url="/develop/ai/context-engine/agent-memory/developer-guide" >}} + {{< tile-card icon="images/dev/icons/icon-redis-agent-memory-64-duotone.png" title="Sessions" description="Store conversation events" url="/develop/ai/context-engine/agent-memory/sessions" >}} + {{< tile-card icon="images/dev/icons/icon-redis-agent-memory-64-duotone.png" title="Long-term memory" description="Recall information across conversations" url="/develop/ai/context-engine/agent-memory/long-term-memory" >}} + {{< tile-card icon="images/icon_logo/icon-data-structures-64-midnight.png" title="Namespaces" description="Organize your memories" url="/develop/ai/context-engine/agent-memory/namespaces" >}}
## Why use Redis Agent Memory? @@ -131,4 +131,4 @@ After your service is ready, follow the [quickstart]({{< relref "/develop/ai/con * [API reference]({{< relref "/develop/ai/context-engine/agent-memory/api-reference" >}}): Look up request and response schemas. -For shared placement rules, see [Namespace routing](/content/develop/ai/context-engine/agent-memory/namespace-routing.md). To build a conversation list, see [List and sort sessions](/content/develop/ai/context-engine/agent-memory/list-sessions.md). +For shared placement rules, see [Namespace routing](/content/develop/ai/context-engine/agent-memory/namespaces/namespace-routing.md). To build a conversation list, see [List and sort sessions](/content/develop/ai/context-engine/agent-memory/sessions/list-sessions.md). diff --git a/content/develop/ai/context-engine/agent-memory/developer-guide.md b/content/develop/ai/context-engine/agent-memory/developer-guide.md index 9960af21fb..964f1dd1b1 100644 --- a/content/develop/ai/context-engine/agent-memory/developer-guide.md +++ b/content/develop/ai/context-engine/agent-memory/developer-guide.md @@ -61,7 +61,7 @@ Your application must decide which memories the user can access. Put these check * For shared memories, check which shared namespaces that user can access, then restrict the query to those namespace IDs. Do not add a personal owner filter to that shared query: it would hide shared memories written by other users. * For requests that retrieve or change a record by ID, check that the record belongs to the user or to a shared namespace they can access. These endpoints do not all accept an owner filter; a session or memory ID is not proof of permission. -Use the authenticated user on every request to choose the owner filter or the permitted shared namespaces. Search filters limit results; they do not authenticate the user. For examples, see [Namespaces](/content/develop/ai/context-engine/agent-memory/namespaces.md). +Use the authenticated user on every request to choose the owner filter or the permitted shared namespaces. Search filters limit results; they do not authenticate the user. For examples, see [Namespaces](/content/develop/ai/context-engine/agent-memory/namespaces/_index.md). Keep mandatory owner restrictions separate from optional search conditions. Combining an owner and another filter with `filterOp: "any"` matches either condition; it does not require the owner to match. `includeAll` on a list request is also a selection option, not permission to enumerate a store. diff --git a/content/develop/ai/context-engine/agent-memory/memory-etl.md b/content/develop/ai/context-engine/agent-memory/memory-etl.md index c357f781bb..0c60959bee 100644 --- a/content/develop/ai/context-engine/agent-memory/memory-etl.md +++ b/content/develop/ai/context-engine/agent-memory/memory-etl.md @@ -15,7 +15,7 @@ MemoryETL uses extract, transform, load (ETL) processing to turn text or Markdow An **extractor** tells MemoryETL what to extract: a custom memory type and instructions for the model. Creating an extractor is an administrative task. Applications can then submit documents to it. -Use [session extraction](/content/develop/ai/context-engine/agent-memory/sessions.md) for conversation events. Use [direct memory creation](/content/develop/ai/context-engine/agent-memory/long-term-memory.md#create-a-custom-memory-directly) when your application already has the records it wants to save. +Use [session extraction](/content/develop/ai/context-engine/agent-memory/sessions/_index.md) for conversation events. Use [direct memory creation](/content/develop/ai/context-engine/agent-memory/long-term-memory.md#create-a-custom-memory-directly) when your application already has the records it wants to save. ## Prepare the service diff --git a/content/develop/ai/context-engine/agent-memory/namespaces.md b/content/develop/ai/context-engine/agent-memory/namespaces/_index.md similarity index 99% rename from content/develop/ai/context-engine/agent-memory/namespaces.md rename to content/develop/ai/context-engine/agent-memory/namespaces/_index.md index a3e246388e..951f7c610e 100644 --- a/content/develop/ai/context-engine/agent-memory/namespaces.md +++ b/content/develop/ai/context-engine/agent-memory/namespaces/_index.md @@ -318,4 +318,4 @@ Add the Python snippet inside the `with` block and the TypeScript snippet inside ## Route and admit shared memories -An explicit namespace destination must pass its admission rules. Direct creation without a namespace stays unassigned. To route an existing shared memory, omit `namespaceRef` from a move request. Personal and unassigned memories are not candidates for automatic routing. See [Namespace routing](/content/develop/ai/context-engine/agent-memory/namespace-routing.md) for policy setup, examples, and per-item errors. +An explicit namespace destination must pass its admission rules. Direct creation without a namespace stays unassigned. To route an existing shared memory, omit `namespaceRef` from a move request. Personal and unassigned memories are not candidates for automatic routing. See [Namespace routing](/content/develop/ai/context-engine/agent-memory/namespaces/namespace-routing.md) for policy setup, examples, and per-item errors. diff --git a/content/develop/ai/context-engine/agent-memory/namespace-routing.md b/content/develop/ai/context-engine/agent-memory/namespaces/namespace-routing.md similarity index 93% rename from content/develop/ai/context-engine/agent-memory/namespace-routing.md rename to content/develop/ai/context-engine/agent-memory/namespaces/namespace-routing.md index e7ba2ad81f..8181664617 100644 --- a/content/develop/ai/context-engine/agent-memory/namespace-routing.md +++ b/content/develop/ai/context-engine/agent-memory/namespaces/namespace-routing.md @@ -9,6 +9,8 @@ hideListLinks: true linktitle: Namespace routing title: Namespace routing weight: 14 +aliases: +- /develop/ai/context-engine/agent-memory/namespace-routing/ --- Namespace routing selects a shared namespace for an eligible memory. Admission rules check whether a memory can enter a destination. Use routing to organize shared memories by content, and admission rules to reject content that does not belong in a namespace. @@ -29,7 +31,7 @@ An automatic move keeps the current placement and returns a per-item error if ro ## Configure a policy -1. [Create a shared namespace](/content/develop/ai/context-engine/agent-memory/namespaces.md#create-a-shared-namespace) and save its ID as `SHARED_NAMESPACE_ID`. +1. [Create a shared namespace](/content/develop/ai/context-engine/agent-memory/namespaces/_index.md#create-a-shared-namespace) and save its ID as `SHARED_NAMESPACE_ID`. 1. Install the software development kit (SDK) for your language. 1. Set `AGENT_MEMORY_URL`, `STORE_ID`, and `API_KEY` as described in the [developer guide](/content/develop/ai/context-engine/agent-memory/developer-guide.md#connect-to-a-redis-agent-memory-service). @@ -187,4 +189,4 @@ curl --fail-with-body --silent --show-error \ {{< /multitabs >}} -Add the SDK snippet inside the client block or `main` function from [Configure a policy](/content/develop/ai/context-engine/agent-memory/namespace-routing.md#configure-a-policy). Set `MEMORY_ID` before running it. The example memory must already belong to a shared namespace and contain matching text. The request accepts 1–100 IDs. To choose a destination explicitly, also pass `namespace_ref={"namespace_id": ""}` in Python or `namespaceRef: { namespaceId: "" }` in TypeScript or the request body. Explicit placement still runs admission checks. +Add the SDK snippet inside the client block or `main` function from [Configure a policy](/content/develop/ai/context-engine/agent-memory/namespaces/namespace-routing.md#configure-a-policy). Set `MEMORY_ID` before running it. The example memory must already belong to a shared namespace and contain matching text. The request accepts 1–100 IDs. To choose a destination explicitly, also pass `namespace_ref={"namespace_id": ""}` in Python or `namespaceRef: { namespaceId: "" }` in TypeScript or the request body. Explicit placement still runs admission checks. diff --git a/content/develop/ai/context-engine/agent-memory/sessions.md b/content/develop/ai/context-engine/agent-memory/sessions/_index.md similarity index 99% rename from content/develop/ai/context-engine/agent-memory/sessions.md rename to content/develop/ai/context-engine/agent-memory/sessions/_index.md index 2fdd3a68c7..b9e7e287b3 100644 --- a/content/develop/ai/context-engine/agent-memory/sessions.md +++ b/content/develop/ai/context-engine/agent-memory/sessions/_index.md @@ -68,7 +68,7 @@ Your application must check that the current user can access this session before ## List sessions -To let a user reopen an earlier conversation, list their sessions and retrieve the selected session by ID. Use `GET /v1/stores/{storeId}/sessions`, `list_session_summaries` in Python, or `listSessionSummaries` in TypeScript. These rows do not contain events or generated summary text. See [List and sort sessions](/content/develop/ai/context-engine/agent-memory/list-sessions.md) for SDK examples and pagination. +To let a user reopen an earlier conversation, list their sessions and retrieve the selected session by ID. Use `GET /v1/stores/{storeId}/sessions`, `list_session_summaries` in Python, or `listSessionSummaries` in TypeScript. These rows do not contain events or generated summary text. See [List and sort sessions](/content/develop/ai/context-engine/agent-memory/sessions/list-sessions.md) for SDK examples and pagination. For session IDs only, use `GET /v1/stores/{storeId}/session-memory` with an owner or namespace filter. Reuse the curl connection values from the [quickstart]({{< relref "/develop/ai/context-engine/agent-memory/quickstart#save-the-connection-values" >}}). diff --git a/content/develop/ai/context-engine/agent-memory/list-sessions.md b/content/develop/ai/context-engine/agent-memory/sessions/list-sessions.md similarity index 98% rename from content/develop/ai/context-engine/agent-memory/list-sessions.md rename to content/develop/ai/context-engine/agent-memory/sessions/list-sessions.md index fc11e27177..d5eeb0363f 100644 --- a/content/develop/ai/context-engine/agent-memory/list-sessions.md +++ b/content/develop/ai/context-engine/agent-memory/sessions/list-sessions.md @@ -9,6 +9,8 @@ hideListLinks: true linktitle: List sessions title: List and sort sessions weight: 10 +aliases: +- /develop/ai/context-engine/agent-memory/list-sessions/ --- Use session listing to let a user reopen an earlier conversation. The session rows contain IDs and available owner, timestamp, and namespace metadata. They do not contain conversation events or summary text. Retrieve the selected session with `get_session_memory` (Python), `getSessionMemory` (TypeScript), or `GET /v1/stores/{storeId}/session-memory/{sessionId}` to build the agent's conversation context. diff --git a/content/develop/ai/context-engine/agent-memory/summary-views.md b/content/develop/ai/context-engine/agent-memory/summary-views.md index e003419ae7..5028b1bd79 100644 --- a/content/develop/ai/context-engine/agent-memory/summary-views.md +++ b/content/develop/ai/context-engine/agent-memory/summary-views.md @@ -15,7 +15,7 @@ A Summary View turns selected long-term memories into a reusable text or structu A **definition** specifies the sources, filters, grouping, output, and refresh policy. A **partition** is one materialized result for an exact combination of grouping values. An ungrouped definition has one result. Results are derived data; they do not enter long-term-memory search or replace the source records. -Summary Views differ from [session summarization](/content/develop/ai/context-engine/agent-memory/sessions.md#configure-automatic-summarization), which compacts conversation events. The supported source is `long_term`. Document-managed MemoryETL records are excluded from Summary View sources. +Summary Views differ from [session summarization](/content/develop/ai/context-engine/agent-memory/sessions/_index.md#configure-automatic-summarization), which compacts conversation events. The supported source is `long_term`. Document-managed MemoryETL records are excluded from Summary View sources. ## Prepare the service and identity diff --git a/content/operate/iris/agent-memory/self-managed/configuration.md b/content/operate/iris/agent-memory/self-managed/configuration.md index 53526026b0..d1c05e0915 100644 --- a/content/operate/iris/agent-memory/self-managed/configuration.md +++ b/content/operate/iris/agent-memory/self-managed/configuration.md @@ -448,4 +448,4 @@ memory: For a bring-your-own file, put `namespace_features` at the file root. This example enables the `matcher_spec` strategy used by the regex policy in the routing guide. -After rollout, use the read-policy request in [Configure a policy](/content/develop/ai/context-engine/agent-memory/namespace-routing.md#configure-a-policy). A successful response confirms that the policy API is available. Read the returned `policy.enabled` value; saving a policy with `enabled: true` enables it for that store. API availability alone does not prove that automatic routing or admission is enabled, so check the process settings as well. +After rollout, use the read-policy request in [Configure a policy](/content/develop/ai/context-engine/agent-memory/namespaces/namespace-routing.md#configure-a-policy). A successful response confirms that the policy API is available. Read the returned `policy.enabled` value; saving a policy with `enabled: true` enables it for that store. API availability alone does not prove that automatic routing or admission is enabled, so check the process settings as well.