From cb5e079e0a89a92221df0545dff3146ea19bb5f9 Mon Sep 17 00:00:00 2001 From: Erfan Ballew Date: Wed, 22 Jul 2026 02:19:46 -0700 Subject: [PATCH 01/10] Add agentic memory retention policy documentation Signed-off-by: Erfan Ballew --- .../agentic-memory-retention.md | 759 ++++++++++++++++++ 1 file changed, 759 insertions(+) create mode 100644 _ml-commons-plugin/agentic-memory-retention.md diff --git a/_ml-commons-plugin/agentic-memory-retention.md b/_ml-commons-plugin/agentic-memory-retention.md new file mode 100644 index 0000000000..91245aef7e --- /dev/null +++ b/_ml-commons-plugin/agentic-memory-retention.md @@ -0,0 +1,759 @@ +--- +layout: default +title: Agentic memory retention +parent: Memory and context +nav_order: 30 +--- + +# Agentic memory retention +**Introduced 3.8** +{: .label .label-purple } + +When AI agents use memory containers to store conversations (sessions), distilled knowledge (long-term memory), and audit trails (history), that data grows without bound. Without lifecycle management, storage costs increase continuously, agents retrieve stale or contradictory memories that degrade response quality, and larger context windows drive up inference costs. + +A memory retention policy solves this by letting you define rules that automatically delete old or excess memories on a schedule. You set the rules, and a background job enforces them. This page explains how to configure retention on your [memory containers]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/). + +Retention is opt-in. The feature is gated by a primary switch, `plugins.ml_commons.memory.retention_enabled`, which defaults to `false`. Until an administrator enables it, the memory container APIs reject any `retention_policy` or `pinned` input with a 403 error, and the background job short-circuits without deleting anything. For more information, see [Feature flags and administrator controls](#feature-flags-and-administrator-controls). +{: .note} + +## Memory types at a glance + +A memory container holds four types of memory. The retention policy you configure on a container applies to three of them: `sessions`, `long-term`, and `history`. The fourth type, `working` memory, is never configured directly in the retention policy. + +| Memory type | What it stores | Configured in the retention policy? | Supports `retention_days` | Supports `max_count` | Supports `pinned` | +| :--- | :--- | :--- | :--- | :--- | :--- | +| `sessions` | Conversation sessions between a user and an agent | Yes | Yes | Yes | Yes | +| `long-term` | Distilled knowledge extracted from conversations | Yes | Yes | Yes | Yes | +| `history` | Immutable audit trail of all interactions | Yes | No | Yes | No | +| `working` | Individual messages within a session | No | Not directly configurable | Not directly configurable | No | + +The retention policy is the primary way to control retention, and it covers the first three types only. Working memory is not part of it: it has no `retention_days`, `max_count`, or `pinned` rule of its own. In normal use, working memory is deleted automatically when its parent session expires, so to control how long messages live, you configure retention on `sessions`. + +Working memory is only managed separately in one uncommon case: session-less containers (those created with `disable_session: true`). Because that memory has no parent session to cascade from, two cluster-level administrator settings act as safety nets rather than per-container policy fields: + +- `working_memory_ttl_days` ages out working memory in session-less containers. It is off by default. +- `orphan_ttl_days` sweeps working memory whose parent session no longer exists. + +Both are cluster-wide admin controls, not knobs on an individual container's retention policy. Most users never touch them. For details, see [Working memory time-to-live](#working-memory-time-to-live) and [Orphan sweep](#orphan-sweep). + +## Quick start + +First, an administrator enables retention (it is off by default): + +```json +PUT /_cluster/settings +{ + "persistent": { + "plugins.ml_commons.memory.retention_enabled": true + } +} +``` +{% include copy-curl.html %} + +Then create a memory container with a retention policy that keeps sessions for 90 days (at most 5,000), caps long-term memories at 2,000, and caps history at 100,000: + +```json +POST /_plugins/_ml/memory_containers/_create +{ + "name": "my-agent-memory", + "configuration": { + "retention_policy": { + "sessions": { + "retention_days": 90, + "max_count": 5000 + }, + "long-term": { + "max_count": 2000 + }, + "history": { + "max_count": 100000 + } + } + } +} +``` +{% include copy-curl.html %} + +The background job (which runs every 24 hours by default) enforces these rules automatically. + +## Retention policy structure + +A retention policy is a JSON object nested inside `configuration.retention_policy` on the memory container. It contains up to three keys, one per eligible memory type: + +```json +{ + "configuration": { + "retention_policy": { + "sessions": { + "retention_days": , + "max_count": + }, + "long-term": { + "retention_days": , + "max_count": + }, + "history": { + "max_count": + } + } + } +} +``` +{% include copy.html %} + +### Field reference + +The following table lists the retention policy fields. + +| Field | Data type | Description | +| :--- | :--- | :--- | +| `retention_days` | Integer or null | Deletes memories older than this many days. Age is measured from the memory's `last_updated_time`. | +| `max_count` | Integer or null | Keeps at most this many memories. When the count is exceeded, the oldest are deleted first. Sessions and long-term memory are ordered by `last_updated_time`; history is ordered by `created_time`. | + +Both fields are optional and independent. You can set one, both, or neither for each memory type. When both are set, they act as a logical OR: a memory is deleted if it violates either rule. + +### Constraints + +- Both `retention_days` and `max_count` must be positive integers (greater than zero) when provided. +- `retention_days` is not supported for the `history` type. The API returns a 400 error if you try to set it. +- The `working` key is not allowed. The API returns a 400 error with guidance to configure sessions instead. +- You only need to include the memory types that you want to manage. Omitted types have no retention enforcement. + +## Set a retention policy on a container + +You can set a policy when you create a container or update it later. + +### At creation time + +Include `retention_policy` in the create request: + +```json +POST /_plugins/_ml/memory_containers/_create +{ + "name": "customer-support-agent", + "configuration": { + "retention_policy": { + "sessions": { + "retention_days": 60, + "max_count": 500 + }, + "long-term": { + "max_count": 5000 + } + } + } +} +``` +{% include copy-curl.html %} + +### When you do not provide a policy + +When no `retention_policy` is specified at creation, no retention enforcement occurs by default. Nothing is automatically deleted. + +The only exception applies when retention is enabled (`retention_enabled` is `true`) and your cluster administrator has explicitly configured default retention settings (see [Cluster-level settings for administrators](#cluster-level-settings-for-administrators)). In that case, those values are applied to the container at creation time. Both conditions are required: while retention is disabled, no policy is ever stamped. These administrator defaults are completely optional and are all disabled out of the box. If your administrator has not set them up, a container without an explicit policy simply has no retention, and its data grows without limit until you add a policy yourself. + +## Update a retention policy + +Use a PUT request to modify the policy on an existing container: + +```json +PUT /_plugins/_ml/memory_containers/{memory_container_id} +{ + "configuration": { + "retention_policy": { + "sessions": { + "max_count": 200 + } + } + } +} +``` +{% include copy-curl.html %} + +### Merge behavior + +Updates use field-level merge, which means the following: + +- Memory types that you include are merged into the existing policy. Fields that you specify are updated; fields that you omit within that type are unchanged. +- Memory types that you omit are left untouched. +- To remove a single field, send it explicitly as `null`. The following request removes `retention_days` from sessions while preserving `max_count`: + + ```json + { "configuration": { "retention_policy": { "sessions": { "retention_days": null } } } } + ``` + {% include copy.html %} + +### Examples of merge behavior + +Consider the following starting policy: + +```json +{ + "sessions": { "retention_days": 30, "max_count": 100 }, + "long-term": { "max_count": 5000 } +} +``` +{% include copy.html %} + +The following table shows how different update requests merge into the starting policy. + +| Update request | Resulting policy | +| :--- | :--- | +| `{"sessions": {"max_count": 50}}` | sessions: days=30, count=50; long-term: count=5000 | +| `{"sessions": {"retention_days": null}}` | sessions: count=100 (days removed); long-term: count=5000 | +| `{"history": {"max_count": 10000}}` | sessions: days=30, count=100; long-term: count=5000; history: count=10000 | + +## Opt out of retention + +To disable all retention enforcement for a container, set the policy to `null`: + +```json +PUT /_plugins/_ml/memory_containers/{memory_container_id} +{ + "configuration": { + "retention_policy": null + } +} +``` +{% include copy-curl.html %} + +This is an explicit opt-out. The retention job skips this container entirely, even if cluster-level defaults are configured. This is distinct from simply not having a policy (which allows defaults to be backfilled). + +To opt back in later, provide a new concrete policy in a subsequent update. + +## Pin memories + +Pinning a memory exempts it from all retention enforcement. The retention job never deletes a pinned memory, regardless of its age or the current count. + +### Pin a session + +Pinning a session preserves the entire conversation, including all of its working memory messages: + +```json +PUT /_plugins/_ml/memory_containers/{id}/memories/sessions/{session_id} +{ + "pinned": true +} +``` +{% include copy-curl.html %} + +### Pin a long-term memory + +```json +PUT /_plugins/_ml/memory_containers/{id}/memories/long-term/{memory_id} +{ + "pinned": true +} +``` +{% include copy-curl.html %} + +### Unpin a memory + +Set `pinned` to `false`: + +```json +PUT /_plugins/_ml/memory_containers/{id}/memories/sessions/{session_id} +{ + "pinned": false +} +``` +{% include copy-curl.html %} + +The `pinned` value is sent as a top-level field in the update body, not wrapped in an `update_content` object. Setting `pinned` requires `retention_enabled` to be `true`; otherwise, the update is rejected with a 403 error (the flag is inactive metadata when the job is not running). +{: .note} + +### Pinning rules + +- Sessions can be pinned. Pinning protects the session and all of its working memory from deletion. +- Long-term memories can be pinned. Pinning protects that specific memory from deletion. +- Working memory cannot be pinned. Pin the parent session instead. +- History cannot be pinned. +- Pinned memories do not count toward `max_count`. If you have `max_count: 100` and 120 sessions, of which 30 are pinned, the job sees 90 non-pinned sessions and keeps the 100 newest non-pinned ones (no deletions in this case). +- Pinning does not reset the memory's age. Only content changes (adding messages to a session or updating memory content) extend lifetime by advancing `last_updated_time`. + +## How the retention job works + +A background job runs on a schedule (every 24 hours by default) and processes all memory containers. Understanding what it does helps you predict its behavior. + +### Execution order + +For each container with a retention policy, the job runs these phases in order: + +1. Session retention (time-based, then count-based) +1. Long-term memory retention (time-based, then count-based) +1. History retention (count-based only) +1. Working memory time-to-live (only for session-disabled containers) +1. Orphan sweep (runs after all containers are processed) + +### Session retention in detail + +When `retention_days` is set, sessions whose `last_updated_time` is older than the threshold are deleted. Active conversations (those messaged recently) are safe because adding messages advances `last_updated_time`. + +When `max_count` is set, and the number of non-pinned sessions exceeds the cap, the oldest sessions (by `last_updated_time`) are removed until the count is within bounds. + +When a session is deleted, all of its working memory messages are deleted first, and then the session document itself is deleted. Conversations are never left with gaps. + +### Long-term memory retention in detail + +Long-term memory retention works identically to session retention: time-based deletion on `last_updated_time`, then count-based deletion on `last_updated_time`, oldest first. Pinned memories are excluded from both. + +On very large backlogs, each count-based (`max_count`) pass evicts at most 50,000 documents per type per run (the oldest first). A larger backlog converges over successive runs. +{: .note} + +### History retention in detail + +History retention is count-based only. The oldest entries (by `created_time`) are deleted when the non-pinned count exceeds `max_count`. + +### Working memory time-to-live + +This phase applies only to containers with `disable_session: true` (session-less containers). In these containers, working memory has no parent session to cascade from, so a cluster-level time-to-live (TTL) setting, `plugins.ml_commons.memory.working_memory_ttl_days`, governs when orphaned messages are cleaned up. This TTL defaults to `-1` (disabled), so session-less working memory is kept indefinitely; the pass short-circuits whenever the value is less than or equal to zero. An administrator must set a positive value (from 1 to 365 days) to age it out. + +### Orphan sweep + +The orphan sweep removes two kinds of working memory documents: those whose parent session no longer exists (for example, if a session was manually deleted) and completely unattributable documents. In both cases, only documents whose `created_time` is older than `plugins.ml_commons.memory.orphan_ttl_days` (7 days by default) are removed. Recently orphaned working memory therefore survives until it ages past that cutoff, even when its parent session is already gone. This prevents accumulation of unreachable data. + +The sweep has two safeguards against removing legitimate data: + +- **First-observation grace period.** The first time the sweep sees a container, it stamps a baseline timestamp (write-once) and deletes nothing. It defers all orphan deletion for that container until `baseline + orphan_ttl_days` has elapsed. This gives pre-existing working memory a full window to acquire a backing session before it can be swept. +- **Lazy session creation.** When a client adds working memory under its own `session_id` without first calling create-session, the add-memory path idempotently creates a minimal backing session document. The sweep then sees a live session and does not treat that working memory as orphaned. The system deliberately does not backfill sessions for old pre-existing data, because on old data it cannot distinguish "never created a session" from "the user deleted the session." + +Distinct `session_id` enumeration is capped at 50,000 per run; larger sets converge over multiple runs. + +### Staleness window + +Because the job runs periodically rather than in real time, there is a window between when a memory becomes eligible for deletion and when it is actually removed. This window is at most one job interval (24 hours by default). Expired memories may still appear in queries during this window. + +## Feature flags and administrator controls + +Memory retention is governed by two independent cluster settings that act as on/off switches at different levels. Understanding what each one does prevents confusion when behavior is unexpected. + +### The two switches + +`plugins.ml_commons.agentic_memory_enabled` (default `true`) controls all agentic memory APIs, including creating, updating, retrieving, and deleting containers and memories. If it is `false`, every memory API returns a 403 error, the retention job is never registered, and nothing memory-related works. + +`plugins.ml_commons.memory.retention_enabled` (default `false`) is the primary opt-in switch for the retention feature. It controls the background retention job and the acceptance of `retention_policy` and `pinned` input on the container APIs. While it is `false`, any request that carries a `retention_policy` or a `pinned` field is rejected with a 403 error, and the job short-circuits and deletes nothing. Other memory APIs (creating containers, adding messages, retrieving, and searching) still work normally. Set it to `true` to accept policies and pins and to have the job enforce them on schedule. + +### What each combination means + +The following table describes the behavior of each setting combination. + +| `agentic_memory_enabled` | `retention_enabled` | What happens | +| :--- | :--- | :--- | +| `true` | `false` | This is the default. All memory APIs work normally: creating containers, adding messages, retrieving, and searching. However, `retention_policy` and `pinned` input is rejected with a 403 error, and the job deletes nothing. | +| `true` | `true` | The retention feature is on. Policies and pins are accepted, and the job enforces them on schedule. | +| `false` | (irrelevant) | All memory APIs return a 403 error. The retention job never registers. The entire agentic memory feature is off. | + +### Deciding whether to change these settings + +The `agentic_memory_enabled` setting defaults to `true`, so the memory APIs work out of the box. The `retention_enabled` setting defaults to `false`, so the retention feature is opt-in: you must explicitly enable it before you can set any policy or pin any memory. Change these settings in the following situations: + +- **You want to use retention at all.** An administrator must set `retention_enabled` to `true` first. Until then, create and update requests that carry a `retention_policy` (or a `pinned` field on a memory) return a 403 error. +- **You are an administrator who wants to pause retention enforcement** after enabling it. For example, you suspect the job is deleting something it should not, or you are performing a migration and want to freeze all data in place temporarily. Set `retention_enabled` back to `false`. +- **Your organization does not use agentic memory** and wants to disable the feature entirely. Set `agentic_memory_enabled` to `false`. + +### Pause retention + +If something is being deleted that should not be, immediately run the following request: + +```json +PUT /_cluster/settings +{ + "persistent": { + "plugins.ml_commons.memory.retention_enabled": false + } +} +``` +{% include copy-curl.html %} + +This takes effect immediately (it is a dynamic setting and requires no restart). The next time the job fires, it logs a message and exits without touching any data. All of your container policies remain saved; they simply stop being enforced. + +### Resume retention + +```json +PUT /_cluster/settings +{ + "persistent": { + "plugins.ml_commons.memory.retention_enabled": true + } +} +``` +{% include copy-curl.html %} + +On the next scheduled run (within one job interval), the job resumes enforcing all policies as normal. + +### Disable agentic memory entirely + +If your cluster does not use memory containers at all and you want to turn off the feature, run the following request: + +```json +PUT /_cluster/settings +{ + "persistent": { + "plugins.ml_commons.agentic_memory_enabled": false + } +} +``` +{% include copy-curl.html %} + +After this, any API call to `/_plugins/_ml/memory_containers/...` returns the following response: + +```json +{ + "error": { + "type": "status_exception", + "reason": "The Agentic Memory APIs are not enabled. To enable, please update the setting plugins.ml_commons.agentic_memory_enabled" + }, + "status": 403 +} +``` + +### Common mistakes + +The following table lists common mistakes and how to resolve them. + +| Mistake | What actually happens | Fix | +| :--- | :--- | :--- | +| Setting `retention_enabled` to `false` and expecting policies to still be enforced | No deletions occur anywhere. The setting is a global pause, not a per-container control. | Use `"retention_policy": null` on specific containers to opt them out individually, or leave the global setting at `true`. | +| Setting `agentic_memory_enabled` to `false`, thinking it only disables retention | All memory APIs break with a 403 error. Agents can no longer read or write memories. | Use `retention_enabled: false` instead, because it only stops deletions. | +| Changing `retention_enabled` and expecting immediate deletions | The job runs on a schedule (every 24 hours by default). Changes take effect on the next run. | Wait for the next cycle. To run more frequently, set `retention_job_interval_hours` to a lower value before retention is first enabled on the cluster; it cannot be changed on a cluster where the job is already scheduled. | + +## Cluster-level settings for administrators + +Cluster administrators can configure retention behavior using dynamic cluster settings. All settings use the prefix `plugins.ml_commons.memory.` and can be updated at runtime without a restart. One exception is `retention_job_interval_hours`: although the setting itself is dynamic, its value is applied only when the retention job is first scheduled, so a later change does not take effect until the job is recreated. + +### Primary switch + +The following table describes the primary opt-in switch. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `retention_enabled` | `false` | The primary opt-in switch for the retention feature. When it is `false` (the default), the container APIs reject `retention_policy` input and the memory update API rejects `pinned` input, both with a 403 error, and the job deletes nothing. Set it to `true` to enable retention cluster-wide. | + +### Job schedule and throttling + +The following table describes the job schedule and throttling settings. + +| Setting | Default | Range | Description | +| :--- | :--- | :--- | :--- | +| `retention_job_interval_hours` | 24 | 1--168 | How often the retention job runs, in hours. This value is applied only when the job is first scheduled. Set it before retention is first enabled on the cluster; changing it on a cluster where the job is already scheduled does not reschedule the running job. | +| `retention_job_throttle_seconds` | 5 | 1--60 | The pause between containers during job execution, used to reduce cluster load. | + +### Cleanup time-to-live settings + +The following table describes the cleanup time-to-live settings. + +| Setting | Default | Range | Description | +| :--- | :--- | :--- | :--- | +| `working_memory_ttl_days` | -1 (off) | `-1` (off), or 1--365 | The TTL for working memory in session-disabled containers. Defaults to `-1` (disabled), so session-less working memory is kept indefinitely unless an administrator sets a value greater than 0. | +| `orphan_ttl_days` | 7 | 1--365 | The TTL for unattributable orphaned working memory documents. | + +### Cluster-level default retention policy + +These settings are all disabled by default (`-1`). Out of the box, no retention policy is applied to any container unless the user explicitly provides one at creation time. The retention job does nothing to containers that have no policy. + +An administrator can optionally configure these settings to establish organization-wide baseline retention rules. If retention is enabled (`retention_enabled` is `true`) and any of these settings are set to a value greater than zero, then containers that (a) have no explicit policy and (b) have not explicitly opted out receive a policy built from these values, either at creation time or on the next job run through backfill. While `retention_enabled` is `false`, defaults are never stamped. + +If you do not set these settings, nothing happens automatically. There is no built-in retention behavior without explicit configuration. + +The following table describes the default retention policy settings. + +| Setting | Default | Range | Effect when set to a value greater than 0 | +| :--- | :--- | :--- | :--- | +| `default_session_retention_days` | -1 (off) | `-1` (off), or 1--3650 | Applies `retention_days` to sessions on new containers | +| `default_session_max_count` | -1 (off) | `-1` (off), or 1--1,000,000 | Applies `max_count` to sessions on new containers | +| `default_long_term_max_count` | -1 (off) | `-1` (off), or 1--1,000,000 | Applies `max_count` to long-term memory on new containers | +| `default_history_max_count` | -1 (off) | `-1` (off), or 1--10,000,000 | Applies `max_count` to history on new containers | + +### Configure cluster defaults + +If your organization wants all containers to have a baseline retention policy without requiring every user to set one manually, an administrator can configure these settings. + +There are no built-in default values; every setting ships as `-1` (off), and nothing is applied unless an administrator sets it. The numbers in the following example are offered only as a possible starting point: they represent a reasonable middle ground drawn from analysis of typical usage, not a recommendation that fits every deployment. Memory consumption varies drastically between organizations and workloads, so these figures will not be right for everyone. Treat them as a place to begin, then review your own data—storage footprint, retention and compliance requirements, and how quickly memory accumulates—to decide what is best for you and your organization. +{: .important} + +```json +PUT /_cluster/settings +{ + "persistent": { + "plugins.ml_commons.memory.default_session_retention_days": 90, + "plugins.ml_commons.memory.default_session_max_count": 5000, + "plugins.ml_commons.memory.default_long_term_max_count": 2000, + "plugins.ml_commons.memory.default_history_max_count": 100000 + } +} +``` +{% include copy-curl.html %} + +After these settings are configured, the following behavior applies: + +- Newly created containers that do not specify their own policy inherit these values at creation time. +- Existing containers that have no policy and have not opted out receive these values on the next job run through backfill. +- Containers that already have an explicit policy (or that explicitly opted out with `"retention_policy": null`) are never affected. + +Defaults are baked into the container at the time they are applied. If you change cluster defaults later, previously created containers are not retroactively updated. To change a specific container's policy, use the update API. +{: .note} + +### Remove cluster defaults + +To return to having no automatic policy, reset the settings to `-1`: + +```json +PUT /_cluster/settings +{ + "persistent": { + "plugins.ml_commons.memory.default_session_retention_days": -1, + "plugins.ml_commons.memory.default_session_max_count": -1, + "plugins.ml_commons.memory.default_long_term_max_count": -1, + "plugins.ml_commons.memory.default_history_max_count": -1 + } +} +``` +{% include copy-curl.html %} + +After this, no new containers receive automatic policies, and the backfill stops applying to existing containers without policies. + +## Validation rules and error messages + +The following table lists the validation rules that the container and memory APIs enforce. + +| Condition | HTTP status | Error message | +| :--- | :--- | :--- | +| `retention_days` set to 0 or a negative value | 400 | `retention_days must be a positive integer or null` | +| `max_count` set to 0 or a negative value | 400 | `max_count must be a positive integer or null` | +| `retention_days` specified for history | 400 | `retention_days is not supported for history memory type` | +| `working` key included in the policy | 400 | `Working memory retention cannot be configured directly. Working memory is deleted when its parent session expires. To control message lifetime, configure retention on "sessions" instead.` | +| Unrecognized memory type key | 400 | `unknown memory type: ` | +| `pinned` set when adding working memory | 400 | `pinned field is not supported for working memory type. To preserve a conversation, pin the session instead.` | +| `retention_policy` supplied on create or update while `retention_enabled` is `false` | 403 | `Cannot set retention_policy: the memory retention feature is not enabled. To enable it, please update the cluster setting plugins.ml_commons.memory.retention_enabled` | +| `pinned` field present in a memory request while `retention_enabled` is `false` | 403 | `Cannot set pinned: the memory retention feature is not enabled. To enable it, please update the cluster setting plugins.ml_commons.memory.retention_enabled` | + +An explicit `"retention_policy": null` (opt-out) is still accepted while the feature is disabled, because clearing retention is consistent with the feature being off. +{: .note} + +## Worked examples + +The following examples show common retention configurations. + +### Example 1: Customer support agent with aggressive cleanup + +This is a high-volume support agent that processes thousands of conversations daily. You want to keep only recent sessions and a moderate knowledge base: + +```json +POST /_plugins/_ml/memory_containers/_create +{ + "name": "high-volume-support-agent", + "configuration": { + "retention_policy": { + "sessions": { + "retention_days": 7, + "max_count": 1000 + }, + "long-term": { + "retention_days": 180, + "max_count": 10000 + }, + "history": { + "max_count": 100000 + } + } + } +} +``` +{% include copy-curl.html %} + +In this example, sessions older than 7 days are deleted. If more than 1,000 non-pinned sessions exist before 7 days have passed, the oldest are evicted early. Long-term memories are kept for 180 days or until 10,000 accumulate. History keeps the most recent 100,000 entries. + +### Example 2: Research assistant with long memory + +This is a knowledge-heavy agent for which long-term memory is critical and conversations are secondary: + +```json +POST /_plugins/_ml/memory_containers/_create +{ + "name": "research-assistant", + "configuration": { + "retention_policy": { + "sessions": { + "retention_days": 14, + "max_count": 200 + }, + "long-term": { + "max_count": 50000 + } + } + } +} +``` +{% include copy-curl.html %} + +In this example, sessions expire after 14 days. Long-term memory grows up to 50,000 entries with no time-based expiry (knowledge is only evicted when the count is exceeded, oldest first). History has no policy, so no history cleanup occurs. + +### Example 3: Protecting important conversations + +Pin a session that contains an important troubleshooting thread so that it is never deleted, regardless of retention rules: + +```json +PUT /_plugins/_ml/memory_containers/{id}/memories/sessions/{session_id} +{ + "pinned": true +} +``` +{% include copy-curl.html %} + +The pinned session and all of its messages persist indefinitely. It does not count against `max_count`, so it does not block other sessions from being kept. + +### Example 4: Understanding logical OR between retention_days and max_count + +When a memory type has both `retention_days` and `max_count`, the two rules combine as a logical OR: a session is deleted if it violates *either* rule. Understanding how the job applies them helps you predict exactly what will be removed. + +For each container, the job builds a single set of sessions to delete, in this order: + +1. **Time-based rule first.** It adds every non-pinned session whose `last_updated_time` is older than `retention_days`. +2. **Count-based rule second.** It counts *all* non-pinned sessions in the container (not just the ones left after step 1). If that total exceeds `max_count`, it adds the oldest sessions—ordered by `last_updated_time`, oldest first—until only `max_count` would remain. +3. **Deduplication.** Because both rules add to the same set, any session selected by both is listed only once and therefore deleted only once. The two rules never combine to delete more than their union. + +The result is that a session is kept only if it satisfies *both* conditions: it is newer than `retention_days` **and** it is among the most-recently-active `max_count` sessions. Whichever rule is stricter for your data is the one that governs how much is deleted. Pinned sessions are excluded from both steps and never count toward `max_count`. + +The following scenarios all use `sessions: { retention_days: 30, max_count: 100 }`: + +- **Scenario A, only the time rule fires:** You have 50 sessions, one of which is 31 days old. The 31-day-old session is deleted (it violates `retention_days`). The count rule does nothing because 50 is below `max_count`. 49 remain. +- **Scenario B, only the count rule fires:** You have 110 sessions, all less than 30 days old. The time rule finds nothing, but the count rule deletes the 10 oldest to bring the total down to `max_count`. 100 remain. +- **Scenario C, neither rule fires:** You have 80 sessions, all less than 30 days old. No deletions occur, because neither rule is violated. +- **Scenario D, both rules fire and overlap (deduplication):** You have 130 sessions, 40 of which are older than 30 days. The time rule selects those 40. The count rule computes an excess of 30 (130 minus 100) and selects the 30 oldest sessions, which are already inside that set of 40. The union is still 40, so 40 sessions are deleted (not 70) and 90 remain. This is why the two rules never double-count. +- **Scenario E, both rules fire without full overlap:** You have 130 sessions, only 10 of which are older than 30 days. The time rule selects those 10. The count rule selects the 30 oldest, which includes those 10 plus 20 more that are still within `retention_days`. The union is 30, so 30 sessions are deleted and 100 remain. Note that 20 sessions younger than `retention_days` were still removed, because `max_count` alone required it. + +### Example 5: Disabling only time-based retention + +To apply count-based limits only, with no time-based expiry, set `retention_days` to `null`: + +```json +{ + "configuration": { + "retention_policy": { + "sessions": { + "retention_days": null, + "max_count": 500 + }, + "long-term": { + "retention_days": null, + "max_count": 10000 + } + } + } +} +``` +{% include copy.html %} + +### Example 6: Completely opting out + +```json +PUT /_plugins/_ml/memory_containers/{memory_container_id} +{ + "configuration": { + "retention_policy": null + } +} +``` +{% include copy-curl.html %} + +No retention enforcement of any kind applies to this container, even if cluster defaults are configured. + +## Frequently asked questions + +**Does the retention job delete data immediately when a rule is violated?** + +No. The job runs on a schedule (every 24 hours by default). There is a staleness window of up to one job interval during which expired memories may still be visible in query results. + +**What happens to working memory when a session is deleted?** + +All working memory (messages) belonging to that session is deleted first, and then the session itself is removed. Conversations are never left in a partial state. + +**Can I set retention rules on working memory directly?** + +No. The working memory lifecycle is tied to its parent session. Configure `sessions` retention to control how long messages live. + +**Does pinning a session reset its age?** + +No. Pinning is a metadata operation and does not change `last_updated_time`. Adding messages to a session or updating its summary extends its lifetime. However, this does not matter for retention, because pinned sessions are never deleted regardless of age. + +**What if I have more pinned sessions than `max_count`?** + +The job never deletes pinned items. It logs a warning that the container is growing beyond the cap because of pins, but enforcement applies only to non-pinned items. To bring the container back under control, review and unpin sessions that no longer need protection. + +**Do cluster default changes affect existing containers?** + +No. Defaults are applied to a container once (at creation time or on first backfill). Changing cluster defaults later does not update containers that already have a policy. Use the update API to modify individual containers. + +**What is the difference between "no policy" and "opted out"?** + +With "no policy" (the field is absent), the container may receive cluster defaults through backfill on the next job run. With "opted out" (`"retention_policy": null`), the container is permanently skipped by the retention job, and defaults are never backfilled. This is an active choice. + +**Can I trigger the retention job on demand?** + +Not in this version. The job runs on its configured interval. You can choose a shorter interval for faster enforcement, but you must set `retention_job_interval_hours` *before* the retention job is first scheduled on the cluster, for example in `opensearch.yml` or as a cluster setting applied during initial setup: + +```json +PUT /_cluster/settings +{ + "persistent": { + "plugins.ml_commons.memory.retention_job_interval_hours": 1 + } +} +``` +{% include copy-curl.html %} + +The interval is read only once, when the job is first created. Changing it on a cluster where the job is already scheduled updates the setting value but does not reschedule the running job, so the change has no effect until the job is recreated. Support for changing the interval on a running cluster is planned for a future release. + +**What OpenSearch version is required?** + +Memory retention policies require OpenSearch 3.8.0 or later. + +**Does this work with multi-tenancy?** + +Not in this version. The retention job is disabled when multi-tenancy is active. Multi-tenant support is planned for a future release. + +**My memory APIs are returning a 403 error. What is wrong?** + +There are two distinct causes: + +- Every memory API returns a 403 error (including create container, add message, retrieve, and search). This means `plugins.ml_commons.agentic_memory_enabled` is `false`. Set it back to `true` to restore API access. +- Only requests that carry `retention_policy` or `pinned` return a 403 error, while other memory APIs work. This means `plugins.ml_commons.memory.retention_enabled` is `false` (its default). This is expected, because retention is opt-in. Enable it before setting policies or pinning memories. (An explicit `"retention_policy": null` is still accepted while the feature is disabled.) + +Check both settings: + +```json +GET /_cluster/settings?include_defaults=true&filter_path=*.plugins.ml_commons.agentic_memory_enabled,*.plugins.ml_commons.memory.retention_enabled +``` +{% include copy-curl.html %} + +**I set a retention policy, but nothing is being deleted. What should I check?** + +Walk through this checklist in order: + +1. Is `plugins.ml_commons.memory.retention_enabled` set to `true`? It defaults to `false`. If it is `false`, the job does nothing, and you would not have been able to set a policy in the first place (the API returns a 403 error). Enable it first. +1. Is `plugins.ml_commons.agentic_memory_enabled` set to `true`? If it is `false`, the job was never registered. +1. Has enough time passed? The job runs every `retention_job_interval_hours` (24 hours by default). Your policy is not enforced until the next run. +1. Is your container's policy actually set? Check it with `GET /_plugins/_ml/memory_containers/{id}` and inspect `configuration.retention_policy`. +1. Are the memories pinned? Pinned memories are exempt from all retention rules. +1. Is multi-tenancy enabled? The retention job is disabled when multi-tenancy is active. + +## API reference summary + +The following table summarizes the operations used to configure retention. + +| Operation | Method | Endpoint | Body | +| :--- | :--- | :--- | :--- | +| Create a container with a policy | POST | `/_plugins/_ml/memory_containers/_create` | `{"configuration": {"retention_policy": {...}}}` | +| Update a container policy | PUT | `/_plugins/_ml/memory_containers/{id}` | `{"configuration": {"retention_policy": {...}}}` | +| Opt out of retention | PUT | `/_plugins/_ml/memory_containers/{id}` | `{"configuration": {"retention_policy": null}}` | +| Pin a session | PUT | `/_plugins/_ml/memory_containers/{id}/memories/sessions/{session_id}` | `{"pinned": true}` | +| Unpin a session | PUT | `/_plugins/_ml/memory_containers/{id}/memories/sessions/{session_id}` | `{"pinned": false}` | +| Pin a long-term memory | PUT | `/_plugins/_ml/memory_containers/{id}/memories/long-term/{memory_id}` | `{"pinned": true}` | +| Enable retention (administrator) | PUT | `/_cluster/settings` | `{"persistent": {"plugins.ml_commons.memory.retention_enabled": true}}` | +| Set cluster defaults | PUT | `/_cluster/settings` | `{"persistent": {"plugins.ml_commons.memory.default_session_retention_days": 90, ...}}` | +| Disable retention cluster-wide | PUT | `/_cluster/settings` | `{"persistent": {"plugins.ml_commons.memory.retention_enabled": false}}` | + +## Next steps + +- For more information about memory containers and agent memory, see [Agentic memory]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/). +- For the container creation API reference, see [Create a memory container]({{site.url}}{{site.baseurl}}/ml-commons-plugin/api/agentic-memory-apis/create-memory-container/). From e82f85dbf46abbe3e00facaa6acce14f8ef8eb57 Mon Sep 17 00:00:00 2001 From: Erfan Ballew Date: Wed, 22 Jul 2026 14:46:10 -0700 Subject: [PATCH 02/10] Correct retention doc against shipped code - Fix interval guidance: retention_job_interval_hours is set in opensearch.yml at initial setup (runtime change planned for a future release) - Fix 403 attribution: container APIs gate retention_policy, memory update API gates pinned - Fix working memory scope: working_memory_ttl_days applies to session-less containers, orphan_ttl_days to session-enabled containers - Remove unsupported per-type null example; clarify OR/dedup in Example 4 - Update recommended starting values and memory-types overview Signed-off-by: Erfan Ballew --- .../agentic-memory-retention.md | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/_ml-commons-plugin/agentic-memory-retention.md b/_ml-commons-plugin/agentic-memory-retention.md index 91245aef7e..fef04c8b92 100644 --- a/_ml-commons-plugin/agentic-memory-retention.md +++ b/_ml-commons-plugin/agentic-memory-retention.md @@ -29,10 +29,10 @@ A memory container holds four types of memory. The retention policy you configur The retention policy is the primary way to control retention, and it covers the first three types only. Working memory is not part of it: it has no `retention_days`, `max_count`, or `pinned` rule of its own. In normal use, working memory is deleted automatically when its parent session expires, so to control how long messages live, you configure retention on `sessions`. -Working memory is only managed separately in one uncommon case: session-less containers (those created with `disable_session: true`). Because that memory has no parent session to cascade from, two cluster-level administrator settings act as safety nets rather than per-container policy fields: +Working memory is managed separately in two cases that the retention policy does not cover. Two cluster-level administrator settings act as safety nets rather than per-container policy fields: -- `working_memory_ttl_days` ages out working memory in session-less containers. It is off by default. -- `orphan_ttl_days` sweeps working memory whose parent session no longer exists. +- `working_memory_ttl_days` ages out working memory in session-less containers (those created with `disable_session: true`), which have no parent session to cascade from. It is off by default. +- `orphan_ttl_days` sweeps working memory in session-enabled containers whose parent session no longer exists, for example after a session is manually deleted. Both are cluster-wide admin controls, not knobs on an individual container's retention policy. Most users never touch them. For details, see [Working memory time-to-live](#working-memory-time-to-live) and [Orphan sweep](#orphan-sweep). @@ -331,7 +331,7 @@ Memory retention is governed by two independent cluster settings that act as on/ `plugins.ml_commons.agentic_memory_enabled` (default `true`) controls all agentic memory APIs, including creating, updating, retrieving, and deleting containers and memories. If it is `false`, every memory API returns a 403 error, the retention job is never registered, and nothing memory-related works. -`plugins.ml_commons.memory.retention_enabled` (default `false`) is the primary opt-in switch for the retention feature. It controls the background retention job and the acceptance of `retention_policy` and `pinned` input on the container APIs. While it is `false`, any request that carries a `retention_policy` or a `pinned` field is rejected with a 403 error, and the job short-circuits and deletes nothing. Other memory APIs (creating containers, adding messages, retrieving, and searching) still work normally. Set it to `true` to accept policies and pins and to have the job enforce them on schedule. +`plugins.ml_commons.memory.retention_enabled` (default `false`) is the primary opt-in switch for the retention feature. It controls the background retention job and the acceptance of `retention_policy` input on the container APIs and `pinned` input on the memory update API. While it is `false`, any request that carries a `retention_policy` or a `pinned` field is rejected with a 403 error, and the job short-circuits and deletes nothing. Other memory APIs (creating containers, adding messages, retrieving, and searching) still work normally. Set it to `true` to accept policies and pins and to have the job enforce them on schedule. ### What each combination means @@ -415,11 +415,11 @@ The following table lists common mistakes and how to resolve them. | :--- | :--- | :--- | | Setting `retention_enabled` to `false` and expecting policies to still be enforced | No deletions occur anywhere. The setting is a global pause, not a per-container control. | Use `"retention_policy": null` on specific containers to opt them out individually, or leave the global setting at `true`. | | Setting `agentic_memory_enabled` to `false`, thinking it only disables retention | All memory APIs break with a 403 error. Agents can no longer read or write memories. | Use `retention_enabled: false` instead, because it only stops deletions. | -| Changing `retention_enabled` and expecting immediate deletions | The job runs on a schedule (every 24 hours by default). Changes take effect on the next run. | Wait for the next cycle. To run more frequently, set `retention_job_interval_hours` to a lower value before retention is first enabled on the cluster; it cannot be changed on a cluster where the job is already scheduled. | +| Changing `retention_enabled` and expecting immediate deletions | The job runs every 24 hours. Changes take effect on the next run. | Wait for the next cycle. To run more frequently, set `retention_job_interval_hours` in `opensearch.yml` during initial cluster setup; changing it on a running cluster is not yet supported. | ## Cluster-level settings for administrators -Cluster administrators can configure retention behavior using dynamic cluster settings. All settings use the prefix `plugins.ml_commons.memory.` and can be updated at runtime without a restart. One exception is `retention_job_interval_hours`: although the setting itself is dynamic, its value is applied only when the retention job is first scheduled, so a later change does not take effect until the job is recreated. +Cluster administrators can configure retention behavior using dynamic cluster settings. All settings use the prefix `plugins.ml_commons.memory.` and can be updated at runtime without a restart, with one exception: `retention_job_interval_hours` is configured in `opensearch.yml` during initial setup and is applied once, when the retention job is first scheduled. The ability to change it on a running cluster is planned for a future release. ### Primary switch @@ -435,7 +435,7 @@ The following table describes the job schedule and throttling settings. | Setting | Default | Range | Description | | :--- | :--- | :--- | :--- | -| `retention_job_interval_hours` | 24 | 1--168 | How often the retention job runs, in hours. This value is applied only when the job is first scheduled. Set it before retention is first enabled on the cluster; changing it on a cluster where the job is already scheduled does not reschedule the running job. | +| `retention_job_interval_hours` | 24 | 1--168 | How often the retention job runs, in hours. Configure this value in `opensearch.yml` during initial setup; it is applied once, when the job is first scheduled. Unlike the other settings in this section, changing it on a running cluster is not yet supported. | | `retention_job_throttle_seconds` | 5 | 1--60 | The pause between containers during job execution, used to reduce cluster load. | ### Cleanup time-to-live settings @@ -690,19 +690,13 @@ With "no policy" (the field is absent), the container may receive cluster defaul **Can I trigger the retention job on demand?** -Not in this version. The job runs on its configured interval. You can choose a shorter interval for faster enforcement, but you must set `retention_job_interval_hours` *before* the retention job is first scheduled on the cluster, for example in `opensearch.yml` or as a cluster setting applied during initial setup: +Not in this version. The job runs on a fixed schedule of every 24 hours. To use a different interval, set `retention_job_interval_hours` in each node's `opensearch.yml` before the node first starts, as part of your initial cluster setup: -```json -PUT /_cluster/settings -{ - "persistent": { - "plugins.ml_commons.memory.retention_job_interval_hours": 1 - } -} +```yaml +plugins.ml_commons.memory.retention_job_interval_hours: 1 ``` -{% include copy-curl.html %} -The interval is read only once, when the job is first created. Changing it on a cluster where the job is already scheduled updates the setting value but does not reschedule the running job, so the change has no effect until the job is recreated. Support for changing the interval on a running cluster is planned for a future release. +The interval is applied once, when the retention job is first scheduled, so it must be in place before agentic memory is first used on the cluster. Changing it afterward has no effect on the current schedule. Support for adjusting the interval on a running cluster is planned for a future release. **What OpenSearch version is required?** From dab8d424091fb0ca01795a9905caa8c9f7b9b37e Mon Sep 17 00:00:00 2001 From: Erfan Ballew Date: Wed, 22 Jul 2026 14:57:57 -0700 Subject: [PATCH 03/10] Add experimental label and full setting paths per review - Add experimental-feature .warning notice (mingshl review comment) - Use full plugins.ml_commons.memory.* paths on first mention of working_memory_ttl_days and orphan_ttl_days (nathaliellenaa review) Signed-off-by: Erfan Ballew --- _ml-commons-plugin/agentic-memory-retention.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/_ml-commons-plugin/agentic-memory-retention.md b/_ml-commons-plugin/agentic-memory-retention.md index fef04c8b92..4744ef2d9c 100644 --- a/_ml-commons-plugin/agentic-memory-retention.md +++ b/_ml-commons-plugin/agentic-memory-retention.md @@ -9,6 +9,9 @@ nav_order: 30 **Introduced 3.8** {: .label .label-purple } +This is an experimental feature and is not recommended for use in a production environment. For updates on the progress of the feature or if you want to leave feedback, join the discussion on the [OpenSearch forum](https://forum.opensearch.org/). +{: .warning} + When AI agents use memory containers to store conversations (sessions), distilled knowledge (long-term memory), and audit trails (history), that data grows without bound. Without lifecycle management, storage costs increase continuously, agents retrieve stale or contradictory memories that degrade response quality, and larger context windows drive up inference costs. A memory retention policy solves this by letting you define rules that automatically delete old or excess memories on a schedule. You set the rules, and a background job enforces them. This page explains how to configure retention on your [memory containers]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/). @@ -31,8 +34,8 @@ The retention policy is the primary way to control retention, and it covers the Working memory is managed separately in two cases that the retention policy does not cover. Two cluster-level administrator settings act as safety nets rather than per-container policy fields: -- `working_memory_ttl_days` ages out working memory in session-less containers (those created with `disable_session: true`), which have no parent session to cascade from. It is off by default. -- `orphan_ttl_days` sweeps working memory in session-enabled containers whose parent session no longer exists, for example after a session is manually deleted. +- `plugins.ml_commons.memory.working_memory_ttl_days` ages out working memory in session-less containers (those created with `disable_session: true`), which have no parent session to cascade from. It is off by default. +- `plugins.ml_commons.memory.orphan_ttl_days` sweeps working memory in session-enabled containers whose parent session no longer exists, for example after a session is manually deleted. Both are cluster-wide admin controls, not knobs on an individual container's retention policy. Most users never touch them. For details, see [Working memory time-to-live](#working-memory-time-to-live) and [Orphan sweep](#orphan-sweep). From 400a7dd08b5883460ef624ffb7eeb1bc07994a49 Mon Sep 17 00:00:00 2001 From: Erfan Ballew Date: Mon, 27 Jul 2026 13:00:19 -0700 Subject: [PATCH 04/10] Document integer-only handling of retention values - Retention policy retention_days/max_count truncate decimals to whole numbers (89.5 becomes 89) - Cluster default settings reject decimal values Signed-off-by: Erfan Ballew --- _ml-commons-plugin/agentic-memory-retention.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_ml-commons-plugin/agentic-memory-retention.md b/_ml-commons-plugin/agentic-memory-retention.md index 4744ef2d9c..62b41ffcfb 100644 --- a/_ml-commons-plugin/agentic-memory-retention.md +++ b/_ml-commons-plugin/agentic-memory-retention.md @@ -117,7 +117,7 @@ Both fields are optional and independent. You can set one, both, or neither for ### Constraints -- Both `retention_days` and `max_count` must be positive integers (greater than zero) when provided. +- Both `retention_days` and `max_count` must be positive integers (greater than zero) when provided. Only whole numbers are used: a decimal value in a retention policy is rounded down to the nearest integer (for example, `89.5` becomes `89`). - `retention_days` is not supported for the `history` type. The API returns a 400 error if you try to set it. - The `working` key is not allowed. The API returns a 400 error with guidance to configure sessions instead. - You only need to include the memory types that you want to manage. Omitted types have no retention enforcement. @@ -471,7 +471,7 @@ The following table describes the default retention policy settings. If your organization wants all containers to have a baseline retention policy without requiring every user to set one manually, an administrator can configure these settings. -There are no built-in default values; every setting ships as `-1` (off), and nothing is applied unless an administrator sets it. The numbers in the following example are offered only as a possible starting point: they represent a reasonable middle ground drawn from analysis of typical usage, not a recommendation that fits every deployment. Memory consumption varies drastically between organizations and workloads, so these figures will not be right for everyone. Treat them as a place to begin, then review your own data—storage footprint, retention and compliance requirements, and how quickly memory accumulates—to decide what is best for you and your organization. +There are no built-in default values; every setting ships as `-1` (off), and nothing is applied unless an administrator sets it. The numbers in the following example are offered only as a possible starting point: they represent a reasonable middle ground drawn from analysis of typical usage, not a recommendation that fits every deployment. Memory consumption varies drastically between organizations and workloads, so these figures will not be right for everyone. Treat them as a place to begin, then review your own data—storage footprint, retention and compliance requirements, and how quickly memory accumulates—to decide what is best for you and your organization. When configuring these cluster default settings, provide whole numbers only; unlike a retention policy, these settings do not accept decimal values. {: .important} ```json From 8b7f14f439936935ea98e1b0283c1e081f85b508 Mon Sep 17 00:00:00 2001 From: Erfan Ballew Date: Tue, 28 Jul 2026 14:47:31 -0700 Subject: [PATCH 05/10] Configure containers fully in retention create examples Long-term and history retention rules only act on stored memory, which requires llm_id + strategies + embedding. Show a fully-configured container in the quick-start example and note the assumption on the other create examples. Signed-off-by: Erfan Ballew --- .../agentic-memory-retention.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/_ml-commons-plugin/agentic-memory-retention.md b/_ml-commons-plugin/agentic-memory-retention.md index 62b41ffcfb..bcc5dccc3b 100644 --- a/_ml-commons-plugin/agentic-memory-retention.md +++ b/_ml-commons-plugin/agentic-memory-retention.md @@ -60,6 +60,18 @@ POST /_plugins/_ml/memory_containers/_create { "name": "my-agent-memory", "configuration": { + "embedding_model_type": "TEXT_EMBEDDING", + "embedding_model_id": "your-embedding-model-id", + "embedding_dimension": 1024, + "llm_id": "your-llm-model-id", + "disable_session": false, + "disable_history": false, + "strategies": [ + { + "type": "SEMANTIC", + "namespace": ["user_id"] + } + ], "retention_policy": { "sessions": { "retention_days": 90, @@ -77,6 +89,9 @@ POST /_plugins/_ml/memory_containers/_create ``` {% include copy-curl.html %} +The `long-term` and `history` rules only take effect when the container is configured to store those memory types, which requires an LLM (`llm_id`) and one or more `strategies`. Without them, no long-term or history memory is generated, so those rules have nothing to act on. The `disable_session` and `disable_history` fields are shown as `false` for clarity, but that is their default and they can be omitted. +{: .note} + The background job (which runs every 24 hours by default) enforces these rules automatically. ## Retention policy structure @@ -130,6 +145,9 @@ You can set a policy when you create a container or update it later. Include `retention_policy` in the create request: +This example assumes a container configured with an LLM (`llm_id`) and strategies, as shown in the [Quick start](#quick-start) example. Without them, the container never stores long-term or history memory, so those rules have nothing to act on. +{: .note} + ```json POST /_plugins/_ml/memory_containers/_create { @@ -541,6 +559,9 @@ The following examples show common retention configurations. This is a high-volume support agent that processes thousands of conversations daily. You want to keep only recent sessions and a moderate knowledge base: +This example assumes a container configured with an LLM (`llm_id`) and strategies, as shown in the [Quick start](#quick-start) example. Without them, the container never stores long-term or history memory, so those rules have nothing to act on. +{: .note} + ```json POST /_plugins/_ml/memory_containers/_create { @@ -570,6 +591,9 @@ In this example, sessions older than 7 days are deleted. If more than 1,000 non- This is a knowledge-heavy agent for which long-term memory is critical and conversations are secondary: +This example assumes a container configured with an LLM (`llm_id`) and strategies, as shown in the [Quick start](#quick-start) example. Without them, the container never stores long-term or history memory, so those rules have nothing to act on. +{: .note} + ```json POST /_plugins/_ml/memory_containers/_create { From 2ece4cb2b126f86f3a56a062e66ee76f14a771e5 Mon Sep 17 00:00:00 2001 From: Fanit Kolchina Date: Tue, 28 Jul 2026 17:59:47 -0400 Subject: [PATCH 06/10] Doc review Signed-off-by: Fanit Kolchina --- .../OpenSearch/SubstitutionsSuggestion.yml | 4 +- .../styles/Vocab/OpenSearch/Words/accept.txt | 1 + .../agentic-memory-retention.md | 724 +++--------------- _ml-commons-plugin/agentic-memory.md | 3 + .../api/agentic-memory-apis/add-memory.md | 2 +- .../create-memory-container.md | 22 +- .../api/agentic-memory-apis/delete-memory.md | 2 +- .../api/agentic-memory-apis/get-memory.md | 2 +- .../hybrid-search-memory.md | 4 +- .../api/agentic-memory-apis/search-memory.md | 2 +- .../semantic-search-memory.md | 4 +- .../api/agentic-memory-apis/update-memory.md | 2 +- 12 files changed, 124 insertions(+), 648 deletions(-) diff --git a/.github/vale/styles/OpenSearch/SubstitutionsSuggestion.yml b/.github/vale/styles/OpenSearch/SubstitutionsSuggestion.yml index 9607fbc5cd..0e5402700a 100644 --- a/.github/vale/styles/OpenSearch/SubstitutionsSuggestion.yml +++ b/.github/vale/styles/OpenSearch/SubstitutionsSuggestion.yml @@ -20,6 +20,7 @@ swap: 'clean up': remove or normalize 'cleans up': removes or normalizes 'cleaning up': removing or normalizing + 'cleaned up': removed or normalized 'covers': includes, describes, or provides 'deal with': process or resolve 'deals with': processes or resolves @@ -93,7 +94,6 @@ swap: 'scale up': scale or increase capacity 'scales up': scales or increases capacity 'scaling up': scaling or increasing capacity - 'see': view or display 'sees': detects or finds 'set up': configure or initialize 'sets up': configures or initializes @@ -135,7 +135,6 @@ swap: 'thinking': evaluating or determining 'under the hood': internally 'walks you through': describes or explains - 'want': require or expect 'wants': requires or expects 'wire up': connect or configure 'wires up': connects or configures @@ -143,7 +142,6 @@ swap: 'wiring': configuration or networking 'wish|desire': want 'work around': mitigate or bypass - 'working': functioning 'works around': mitigates or bypasses 'working around': mitigating or bypassing 'workaround': mitigation or alternative approach diff --git a/.github/vale/styles/Vocab/OpenSearch/Words/accept.txt b/.github/vale/styles/Vocab/OpenSearch/Words/accept.txt index 8ba69e172a..e1f3724ae6 100644 --- a/.github/vale/styles/Vocab/OpenSearch/Words/accept.txt +++ b/.github/vale/styles/Vocab/OpenSearch/Words/accept.txt @@ -87,6 +87,7 @@ gzip [Hh]ostname [Hh]otspots? [Hh]yperparameters +[Ii]dempotent(?:ly|cy)? [Ii]mpactful [Ii]nfographics? [Ii]ngress diff --git a/_ml-commons-plugin/agentic-memory-retention.md b/_ml-commons-plugin/agentic-memory-retention.md index 4744ef2d9c..fd927a2211 100644 --- a/_ml-commons-plugin/agentic-memory-retention.md +++ b/_ml-commons-plugin/agentic-memory-retention.md @@ -1,7 +1,8 @@ --- layout: default title: Agentic memory retention -parent: Memory and context +parent: Agentic memory +grand_parent: Memory and context nav_order: 30 --- @@ -12,36 +13,13 @@ nav_order: 30 This is an experimental feature and is not recommended for use in a production environment. For updates on the progress of the feature or if you want to leave feedback, join the discussion on the [OpenSearch forum](https://forum.opensearch.org/). {: .warning} -When AI agents use memory containers to store conversations (sessions), distilled knowledge (long-term memory), and audit trails (history), that data grows without bound. Without lifecycle management, storage costs increase continuously, agents retrieve stale or contradictory memories that degrade response quality, and larger context windows drive up inference costs. +By default, agentic memories accumulate indefinitely, which increases storage use and can cause agents to retrieve outdated memories. To automatically delete old or excess memories, define a _retention policy_ for a [memory container]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/#memory-containers). The retention policy specifies an age limit, a count limit, or both for each memory type. A background job enforces the policy on a schedule (by default, every 24 hours). It deletes `sessions`, `long-term`, and `history` memories that exceed a count limit, or `sessions` and `long-term` memories that exceed an age limit. In contrast, `working` memory is not subject to the retention policy and is deleted when its parent session no longer exists. To control the amount of time that `working` memories are retained, configure retention for `sessions`. -A memory retention policy solves this by letting you define rules that automatically delete old or excess memories on a schedule. You set the rules, and a background job enforces them. This page explains how to configure retention on your [memory containers]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/). +You can exclude `sessions` and `long-term` memories from the retention policy by pinning them. For more information, see [Pinning memories](#pinning-memories). -Retention is opt-in. The feature is gated by a primary switch, `plugins.ml_commons.memory.retention_enabled`, which defaults to `false`. Until an administrator enables it, the memory container APIs reject any `retention_policy` or `pinned` input with a 403 error, and the background job short-circuits without deleting anything. For more information, see [Feature flags and administrator controls](#feature-flags-and-administrator-controls). -{: .note} - -## Memory types at a glance - -A memory container holds four types of memory. The retention policy you configure on a container applies to three of them: `sessions`, `long-term`, and `history`. The fourth type, `working` memory, is never configured directly in the retention policy. - -| Memory type | What it stores | Configured in the retention policy? | Supports `retention_days` | Supports `max_count` | Supports `pinned` | -| :--- | :--- | :--- | :--- | :--- | :--- | -| `sessions` | Conversation sessions between a user and an agent | Yes | Yes | Yes | Yes | -| `long-term` | Distilled knowledge extracted from conversations | Yes | Yes | Yes | Yes | -| `history` | Immutable audit trail of all interactions | Yes | No | Yes | No | -| `working` | Individual messages within a session | No | Not directly configurable | Not directly configurable | No | - -The retention policy is the primary way to control retention, and it covers the first three types only. Working memory is not part of it: it has no `retention_days`, `max_count`, or `pinned` rule of its own. In normal use, working memory is deleted automatically when its parent session expires, so to control how long messages live, you configure retention on `sessions`. +## Enabling memory retention -Working memory is managed separately in two cases that the retention policy does not cover. Two cluster-level administrator settings act as safety nets rather than per-container policy fields: - -- `plugins.ml_commons.memory.working_memory_ttl_days` ages out working memory in session-less containers (those created with `disable_session: true`), which have no parent session to cascade from. It is off by default. -- `plugins.ml_commons.memory.orphan_ttl_days` sweeps working memory in session-enabled containers whose parent session no longer exists, for example after a session is manually deleted. - -Both are cluster-wide admin controls, not knobs on an individual container's retention policy. Most users never touch them. For details, see [Working memory time-to-live](#working-memory-time-to-live) and [Orphan sweep](#orphan-sweep). - -## Quick start - -First, an administrator enables retention (it is off by default): +Retention is disabled by default. To enable it cluster-wide, configure the following dynamic cluster setting: ```json PUT /_cluster/settings @@ -53,95 +31,57 @@ PUT /_cluster/settings ``` {% include copy-curl.html %} -Then create a memory container with a retention policy that keeps sessions for 90 days (at most 5,000), caps long-term memories at 2,000, and caps history at 100,000: - -```json -POST /_plugins/_ml/memory_containers/_create -{ - "name": "my-agent-memory", - "configuration": { - "retention_policy": { - "sessions": { - "retention_days": 90, - "max_count": 5000 - }, - "long-term": { - "max_count": 2000 - }, - "history": { - "max_count": 100000 - } - } - } -} -``` -{% include copy-curl.html %} - -The background job (which runs every 24 hours by default) enforces these rules automatically. +## Defining a retention policy -## Retention policy structure - -A retention policy is a JSON object nested inside `configuration.retention_policy` on the memory container. It contains up to three keys, one per eligible memory type: +The `retention_policy` object is specified in the container's `configuration` object and maps each memory type to the retention limits for that type. Replace `{memory_type}` with `sessions`, `long-term`, or `history`: ```json -{ - "configuration": { - "retention_policy": { - "sessions": { - "retention_days": , - "max_count": - }, - "long-term": { - "retention_days": , - "max_count": - }, - "history": { - "max_count": - } +"configuration": { + "retention_policy": { + "{memory_type}": { + "retention_days": 90, + "max_count": 5000 } } } ``` -{% include copy.html %} - -### Field reference -The following table lists the retention policy fields. +Each `{memory_type}` object accepts the following optional fields. When both fields are set, a memory is deleted if it violates either rule. -| Field | Data type | Description | -| :--- | :--- | :--- | -| `retention_days` | Integer or null | Deletes memories older than this many days. Age is measured from the memory's `last_updated_time`. | -| `max_count` | Integer or null | Keeps at most this many memories. When the count is exceeded, the oldest are deleted first. Sessions and long-term memory are ordered by `last_updated_time`; history is ordered by `created_time`. | +Field | Data type | Supported memory types | Description +:--- | :--- | :--- | :--- +`retention_days` | Integer | `sessions`, `long-term` | Deletes memories older than this many days, measured from the memory's `last_updated_time`. +`max_count` | Integer | `sessions`, `long-term`, `history` | Keeps at most this many memories, deleting the oldest first. Sessions and long-term memory are ordered by `last_updated_time`; history is ordered by `created_time`. -Both fields are optional and independent. You can set one, both, or neither for each memory type. When both are set, they act as a logical OR: a memory is deleted if it violates either rule. - -### Constraints - -- Both `retention_days` and `max_count` must be positive integers (greater than zero) when provided. -- `retention_days` is not supported for the `history` type. The API returns a 400 error if you try to set it. -- The `working` key is not allowed. The API returns a 400 error with guidance to configure sessions instead. -- You only need to include the memory types that you want to manage. Omitted types have no retention enforcement. - -## Set a retention policy on a container - -You can set a policy when you create a container or update it later. +A retention rule takes effect only if the container stores that memory type. For `long-term` and `history` memories to be stored, you must configure `strategies`; otherwise, retention rules for those types have no effect. For more information, see [The created indexes]({{site.url}}{{site.baseurl}}/ml-commons-plugin/api/agentic-memory-apis/create-memory-container/#the-created-indexes). +{: .note} -### At creation time +### Configuring a retention policy -Include `retention_policy` in the create request: +To configure a policy when you create a memory container, include the `retention_policy` field in the create request. The following example creates a container with session tracking, a strategy, and a retention policy that retains up to 5,000 sessions for 90 days and limits long-term memory to 2,000 entries: ```json POST /_plugins/_ml/memory_containers/_create { - "name": "customer-support-agent", + "name": "my-agent-memory", "configuration": { + "embedding_model_type": "TEXT_EMBEDDING", + "embedding_model_id": "your-embedding-model-id", + "embedding_dimension": 1024, + "llm_id": "your-llm-model-id", + "strategies": [ + { + "type": "SEMANTIC", + "namespace": ["user_id"] + } + ], "retention_policy": { "sessions": { - "retention_days": 60, - "max_count": 500 + "retention_days": 90, + "max_count": 5000 }, "long-term": { - "max_count": 5000 + "max_count": 2000 } } } @@ -149,15 +89,7 @@ POST /_plugins/_ml/memory_containers/_create ``` {% include copy-curl.html %} -### When you do not provide a policy - -When no `retention_policy` is specified at creation, no retention enforcement occurs by default. Nothing is automatically deleted. - -The only exception applies when retention is enabled (`retention_enabled` is `true`) and your cluster administrator has explicitly configured default retention settings (see [Cluster-level settings for administrators](#cluster-level-settings-for-administrators)). In that case, those values are applied to the container at creation time. Both conditions are required: while retention is disabled, no policy is ever stamped. These administrator defaults are completely optional and are all disabled out of the box. If your administrator has not set them up, a container without an explicit policy simply has no retention, and its data grows without limit until you add a policy yourself. - -## Update a retention policy - -Use a PUT request to modify the policy on an existing container: +You can also add a retention policy for an existing container at any time by sending the `retention_policy` object in an update request: ```json PUT /_plugins/_ml/memory_containers/{memory_container_id} @@ -165,7 +97,11 @@ PUT /_plugins/_ml/memory_containers/{memory_container_id} "configuration": { "retention_policy": { "sessions": { - "max_count": 200 + "retention_days": 90, + "max_count": 5000 + }, + "long-term": { + "max_count": 2000 } } } @@ -173,390 +109,28 @@ PUT /_plugins/_ml/memory_containers/{memory_container_id} ``` {% include copy-curl.html %} -### Merge behavior +### Viewing a retention policy -Updates use field-level merge, which means the following: - -- Memory types that you include are merged into the existing policy. Fields that you specify are updated; fields that you omit within that type are unchanged. -- Memory types that you omit are left untouched. -- To remove a single field, send it explicitly as `null`. The following request removes `retention_days` from sessions while preserving `max_count`: - - ```json - { "configuration": { "retention_policy": { "sessions": { "retention_days": null } } } } - ``` - {% include copy.html %} - -### Examples of merge behavior - -Consider the following starting policy: +To view the stored policy, retrieve the container using the [Get Memory Container API]({{site.url}}{{site.baseurl}}/ml-commons-plugin/api/agentic-memory-apis/get-memory-container/): ```json -{ - "sessions": { "retention_days": 30, "max_count": 100 }, - "long-term": { "max_count": 5000 } -} -``` -{% include copy.html %} - -The following table shows how different update requests merge into the starting policy. - -| Update request | Resulting policy | -| :--- | :--- | -| `{"sessions": {"max_count": 50}}` | sessions: days=30, count=50; long-term: count=5000 | -| `{"sessions": {"retention_days": null}}` | sessions: count=100 (days removed); long-term: count=5000 | -| `{"history": {"max_count": 10000}}` | sessions: days=30, count=100; long-term: count=5000; history: count=10000 | - -## Opt out of retention - -To disable all retention enforcement for a container, set the policy to `null`: - -```json -PUT /_plugins/_ml/memory_containers/{memory_container_id} -{ - "configuration": { - "retention_policy": null - } -} +GET /_plugins/_ml/memory_containers/{memory_container_id} ``` {% include copy-curl.html %} -This is an explicit opt-out. The retention job skips this container entirely, even if cluster-level defaults are configured. This is distinct from simply not having a policy (which allows defaults to be backfilled). - -To opt back in later, provide a new concrete policy in a subsequent update. - -## Pin memories - -Pinning a memory exempts it from all retention enforcement. The retention job never deletes a pinned memory, regardless of its age or the current count. - -### Pin a session - -Pinning a session preserves the entire conversation, including all of its working memory messages: - -```json -PUT /_plugins/_ml/memory_containers/{id}/memories/sessions/{session_id} -{ - "pinned": true -} -``` -{% include copy-curl.html %} - -### Pin a long-term memory - -```json -PUT /_plugins/_ml/memory_containers/{id}/memories/long-term/{memory_id} -{ - "pinned": true -} -``` -{% include copy-curl.html %} - -### Unpin a memory - -Set `pinned` to `false`: - -```json -PUT /_plugins/_ml/memory_containers/{id}/memories/sessions/{session_id} -{ - "pinned": false -} -``` -{% include copy-curl.html %} - -The `pinned` value is sent as a top-level field in the update body, not wrapped in an `update_content` object. Setting `pinned` requires `retention_enabled` to be `true`; otherwise, the update is rejected with a 403 error (the flag is inactive metadata when the job is not running). -{: .note} - -### Pinning rules - -- Sessions can be pinned. Pinning protects the session and all of its working memory from deletion. -- Long-term memories can be pinned. Pinning protects that specific memory from deletion. -- Working memory cannot be pinned. Pin the parent session instead. -- History cannot be pinned. -- Pinned memories do not count toward `max_count`. If you have `max_count: 100` and 120 sessions, of which 30 are pinned, the job sees 90 non-pinned sessions and keeps the 100 newest non-pinned ones (no deletions in this case). -- Pinning does not reset the memory's age. Only content changes (adding messages to a session or updating memory content) extend lifetime by advancing `last_updated_time`. - -## How the retention job works - -A background job runs on a schedule (every 24 hours by default) and processes all memory containers. Understanding what it does helps you predict its behavior. - -### Execution order - -For each container with a retention policy, the job runs these phases in order: - -1. Session retention (time-based, then count-based) -1. Long-term memory retention (time-based, then count-based) -1. History retention (count-based only) -1. Working memory time-to-live (only for session-disabled containers) -1. Orphan sweep (runs after all containers are processed) - -### Session retention in detail - -When `retention_days` is set, sessions whose `last_updated_time` is older than the threshold are deleted. Active conversations (those messaged recently) are safe because adding messages advances `last_updated_time`. - -When `max_count` is set, and the number of non-pinned sessions exceeds the cap, the oldest sessions (by `last_updated_time`) are removed until the count is within bounds. - -When a session is deleted, all of its working memory messages are deleted first, and then the session document itself is deleted. Conversations are never left with gaps. - -### Long-term memory retention in detail - -Long-term memory retention works identically to session retention: time-based deletion on `last_updated_time`, then count-based deletion on `last_updated_time`, oldest first. Pinned memories are excluded from both. - -On very large backlogs, each count-based (`max_count`) pass evicts at most 50,000 documents per type per run (the oldest first). A larger backlog converges over successive runs. -{: .note} - -### History retention in detail - -History retention is count-based only. The oldest entries (by `created_time`) are deleted when the non-pinned count exceeds `max_count`. - -### Working memory time-to-live - -This phase applies only to containers with `disable_session: true` (session-less containers). In these containers, working memory has no parent session to cascade from, so a cluster-level time-to-live (TTL) setting, `plugins.ml_commons.memory.working_memory_ttl_days`, governs when orphaned messages are cleaned up. This TTL defaults to `-1` (disabled), so session-less working memory is kept indefinitely; the pass short-circuits whenever the value is less than or equal to zero. An administrator must set a positive value (from 1 to 365 days) to age it out. - -### Orphan sweep - -The orphan sweep removes two kinds of working memory documents: those whose parent session no longer exists (for example, if a session was manually deleted) and completely unattributable documents. In both cases, only documents whose `created_time` is older than `plugins.ml_commons.memory.orphan_ttl_days` (7 days by default) are removed. Recently orphaned working memory therefore survives until it ages past that cutoff, even when its parent session is already gone. This prevents accumulation of unreachable data. - -The sweep has two safeguards against removing legitimate data: - -- **First-observation grace period.** The first time the sweep sees a container, it stamps a baseline timestamp (write-once) and deletes nothing. It defers all orphan deletion for that container until `baseline + orphan_ttl_days` has elapsed. This gives pre-existing working memory a full window to acquire a backing session before it can be swept. -- **Lazy session creation.** When a client adds working memory under its own `session_id` without first calling create-session, the add-memory path idempotently creates a minimal backing session document. The sweep then sees a live session and does not treat that working memory as orphaned. The system deliberately does not backfill sessions for old pre-existing data, because on old data it cannot distinguish "never created a session" from "the user deleted the session." - -Distinct `session_id` enumeration is capped at 50,000 per run; larger sets converge over multiple runs. - -### Staleness window - -Because the job runs periodically rather than in real time, there is a window between when a memory becomes eligible for deletion and when it is actually removed. This window is at most one job interval (24 hours by default). Expired memories may still appear in queries during this window. - -## Feature flags and administrator controls - -Memory retention is governed by two independent cluster settings that act as on/off switches at different levels. Understanding what each one does prevents confusion when behavior is unexpected. - -### The two switches - -`plugins.ml_commons.agentic_memory_enabled` (default `true`) controls all agentic memory APIs, including creating, updating, retrieving, and deleting containers and memories. If it is `false`, every memory API returns a 403 error, the retention job is never registered, and nothing memory-related works. - -`plugins.ml_commons.memory.retention_enabled` (default `false`) is the primary opt-in switch for the retention feature. It controls the background retention job and the acceptance of `retention_policy` input on the container APIs and `pinned` input on the memory update API. While it is `false`, any request that carries a `retention_policy` or a `pinned` field is rejected with a 403 error, and the job short-circuits and deletes nothing. Other memory APIs (creating containers, adding messages, retrieving, and searching) still work normally. Set it to `true` to accept policies and pins and to have the job enforce them on schedule. - -### What each combination means - -The following table describes the behavior of each setting combination. - -| `agentic_memory_enabled` | `retention_enabled` | What happens | -| :--- | :--- | :--- | -| `true` | `false` | This is the default. All memory APIs work normally: creating containers, adding messages, retrieving, and searching. However, `retention_policy` and `pinned` input is rejected with a 403 error, and the job deletes nothing. | -| `true` | `true` | The retention feature is on. Policies and pins are accepted, and the job enforces them on schedule. | -| `false` | (irrelevant) | All memory APIs return a 403 error. The retention job never registers. The entire agentic memory feature is off. | - -### Deciding whether to change these settings - -The `agentic_memory_enabled` setting defaults to `true`, so the memory APIs work out of the box. The `retention_enabled` setting defaults to `false`, so the retention feature is opt-in: you must explicitly enable it before you can set any policy or pin any memory. Change these settings in the following situations: - -- **You want to use retention at all.** An administrator must set `retention_enabled` to `true` first. Until then, create and update requests that carry a `retention_policy` (or a `pinned` field on a memory) return a 403 error. -- **You are an administrator who wants to pause retention enforcement** after enabling it. For example, you suspect the job is deleting something it should not, or you are performing a migration and want to freeze all data in place temporarily. Set `retention_enabled` back to `false`. -- **Your organization does not use agentic memory** and wants to disable the feature entirely. Set `agentic_memory_enabled` to `false`. - -### Pause retention - -If something is being deleted that should not be, immediately run the following request: - -```json -PUT /_cluster/settings -{ - "persistent": { - "plugins.ml_commons.memory.retention_enabled": false - } -} -``` -{% include copy-curl.html %} +### Updating a retention policy -This takes effect immediately (it is a dynamic setting and requires no restart). The next time the job fires, it logs a message and exits without touching any data. All of your container policies remain saved; they simply stop being enforced. +Updating a `retention_policy` merges your changes into the existing policy rather than replacing it. Memory types and fields that you omit are left unchanged. -### Resume retention +To remove a single field, set it to `null`. For example, the following request updates the policy in the [Configuring a retention policy](#configuring-a-retention-policy) example by removing `retention_days` from `sessions` while keeping the `max_count` of 5,000: ```json -PUT /_cluster/settings -{ - "persistent": { - "plugins.ml_commons.memory.retention_enabled": true - } -} -``` -{% include copy-curl.html %} - -On the next scheduled run (within one job interval), the job resumes enforcing all policies as normal. - -### Disable agentic memory entirely - -If your cluster does not use memory containers at all and you want to turn off the feature, run the following request: - -```json -PUT /_cluster/settings -{ - "persistent": { - "plugins.ml_commons.agentic_memory_enabled": false - } -} -``` -{% include copy-curl.html %} - -After this, any API call to `/_plugins/_ml/memory_containers/...` returns the following response: - -```json -{ - "error": { - "type": "status_exception", - "reason": "The Agentic Memory APIs are not enabled. To enable, please update the setting plugins.ml_commons.agentic_memory_enabled" - }, - "status": 403 -} -``` - -### Common mistakes - -The following table lists common mistakes and how to resolve them. - -| Mistake | What actually happens | Fix | -| :--- | :--- | :--- | -| Setting `retention_enabled` to `false` and expecting policies to still be enforced | No deletions occur anywhere. The setting is a global pause, not a per-container control. | Use `"retention_policy": null` on specific containers to opt them out individually, or leave the global setting at `true`. | -| Setting `agentic_memory_enabled` to `false`, thinking it only disables retention | All memory APIs break with a 403 error. Agents can no longer read or write memories. | Use `retention_enabled: false` instead, because it only stops deletions. | -| Changing `retention_enabled` and expecting immediate deletions | The job runs every 24 hours. Changes take effect on the next run. | Wait for the next cycle. To run more frequently, set `retention_job_interval_hours` in `opensearch.yml` during initial cluster setup; changing it on a running cluster is not yet supported. | - -## Cluster-level settings for administrators - -Cluster administrators can configure retention behavior using dynamic cluster settings. All settings use the prefix `plugins.ml_commons.memory.` and can be updated at runtime without a restart, with one exception: `retention_job_interval_hours` is configured in `opensearch.yml` during initial setup and is applied once, when the retention job is first scheduled. The ability to change it on a running cluster is planned for a future release. - -### Primary switch - -The following table describes the primary opt-in switch. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `retention_enabled` | `false` | The primary opt-in switch for the retention feature. When it is `false` (the default), the container APIs reject `retention_policy` input and the memory update API rejects `pinned` input, both with a 403 error, and the job deletes nothing. Set it to `true` to enable retention cluster-wide. | - -### Job schedule and throttling - -The following table describes the job schedule and throttling settings. - -| Setting | Default | Range | Description | -| :--- | :--- | :--- | :--- | -| `retention_job_interval_hours` | 24 | 1--168 | How often the retention job runs, in hours. Configure this value in `opensearch.yml` during initial setup; it is applied once, when the job is first scheduled. Unlike the other settings in this section, changing it on a running cluster is not yet supported. | -| `retention_job_throttle_seconds` | 5 | 1--60 | The pause between containers during job execution, used to reduce cluster load. | - -### Cleanup time-to-live settings - -The following table describes the cleanup time-to-live settings. - -| Setting | Default | Range | Description | -| :--- | :--- | :--- | :--- | -| `working_memory_ttl_days` | -1 (off) | `-1` (off), or 1--365 | The TTL for working memory in session-disabled containers. Defaults to `-1` (disabled), so session-less working memory is kept indefinitely unless an administrator sets a value greater than 0. | -| `orphan_ttl_days` | 7 | 1--365 | The TTL for unattributable orphaned working memory documents. | - -### Cluster-level default retention policy - -These settings are all disabled by default (`-1`). Out of the box, no retention policy is applied to any container unless the user explicitly provides one at creation time. The retention job does nothing to containers that have no policy. - -An administrator can optionally configure these settings to establish organization-wide baseline retention rules. If retention is enabled (`retention_enabled` is `true`) and any of these settings are set to a value greater than zero, then containers that (a) have no explicit policy and (b) have not explicitly opted out receive a policy built from these values, either at creation time or on the next job run through backfill. While `retention_enabled` is `false`, defaults are never stamped. - -If you do not set these settings, nothing happens automatically. There is no built-in retention behavior without explicit configuration. - -The following table describes the default retention policy settings. - -| Setting | Default | Range | Effect when set to a value greater than 0 | -| :--- | :--- | :--- | :--- | -| `default_session_retention_days` | -1 (off) | `-1` (off), or 1--3650 | Applies `retention_days` to sessions on new containers | -| `default_session_max_count` | -1 (off) | `-1` (off), or 1--1,000,000 | Applies `max_count` to sessions on new containers | -| `default_long_term_max_count` | -1 (off) | `-1` (off), or 1--1,000,000 | Applies `max_count` to long-term memory on new containers | -| `default_history_max_count` | -1 (off) | `-1` (off), or 1--10,000,000 | Applies `max_count` to history on new containers | - -### Configure cluster defaults - -If your organization wants all containers to have a baseline retention policy without requiring every user to set one manually, an administrator can configure these settings. - -There are no built-in default values; every setting ships as `-1` (off), and nothing is applied unless an administrator sets it. The numbers in the following example are offered only as a possible starting point: they represent a reasonable middle ground drawn from analysis of typical usage, not a recommendation that fits every deployment. Memory consumption varies drastically between organizations and workloads, so these figures will not be right for everyone. Treat them as a place to begin, then review your own data—storage footprint, retention and compliance requirements, and how quickly memory accumulates—to decide what is best for you and your organization. -{: .important} - -```json -PUT /_cluster/settings -{ - "persistent": { - "plugins.ml_commons.memory.default_session_retention_days": 90, - "plugins.ml_commons.memory.default_session_max_count": 5000, - "plugins.ml_commons.memory.default_long_term_max_count": 2000, - "plugins.ml_commons.memory.default_history_max_count": 100000 - } -} -``` -{% include copy-curl.html %} - -After these settings are configured, the following behavior applies: - -- Newly created containers that do not specify their own policy inherit these values at creation time. -- Existing containers that have no policy and have not opted out receive these values on the next job run through backfill. -- Containers that already have an explicit policy (or that explicitly opted out with `"retention_policy": null`) are never affected. - -Defaults are baked into the container at the time they are applied. If you change cluster defaults later, previously created containers are not retroactively updated. To change a specific container's policy, use the update API. -{: .note} - -### Remove cluster defaults - -To return to having no automatic policy, reset the settings to `-1`: - -```json -PUT /_cluster/settings -{ - "persistent": { - "plugins.ml_commons.memory.default_session_retention_days": -1, - "plugins.ml_commons.memory.default_session_max_count": -1, - "plugins.ml_commons.memory.default_long_term_max_count": -1, - "plugins.ml_commons.memory.default_history_max_count": -1 - } -} -``` -{% include copy-curl.html %} - -After this, no new containers receive automatic policies, and the backfill stops applying to existing containers without policies. - -## Validation rules and error messages - -The following table lists the validation rules that the container and memory APIs enforce. - -| Condition | HTTP status | Error message | -| :--- | :--- | :--- | -| `retention_days` set to 0 or a negative value | 400 | `retention_days must be a positive integer or null` | -| `max_count` set to 0 or a negative value | 400 | `max_count must be a positive integer or null` | -| `retention_days` specified for history | 400 | `retention_days is not supported for history memory type` | -| `working` key included in the policy | 400 | `Working memory retention cannot be configured directly. Working memory is deleted when its parent session expires. To control message lifetime, configure retention on "sessions" instead.` | -| Unrecognized memory type key | 400 | `unknown memory type: ` | -| `pinned` set when adding working memory | 400 | `pinned field is not supported for working memory type. To preserve a conversation, pin the session instead.` | -| `retention_policy` supplied on create or update while `retention_enabled` is `false` | 403 | `Cannot set retention_policy: the memory retention feature is not enabled. To enable it, please update the cluster setting plugins.ml_commons.memory.retention_enabled` | -| `pinned` field present in a memory request while `retention_enabled` is `false` | 403 | `Cannot set pinned: the memory retention feature is not enabled. To enable it, please update the cluster setting plugins.ml_commons.memory.retention_enabled` | - -An explicit `"retention_policy": null` (opt-out) is still accepted while the feature is disabled, because clearing retention is consistent with the feature being off. -{: .note} - -## Worked examples - -The following examples show common retention configurations. - -### Example 1: Customer support agent with aggressive cleanup - -This is a high-volume support agent that processes thousands of conversations daily. You want to keep only recent sessions and a moderate knowledge base: - -```json -POST /_plugins/_ml/memory_containers/_create +PUT /_plugins/_ml/memory_containers/{memory_container_id} { - "name": "high-volume-support-agent", "configuration": { "retention_policy": { "sessions": { - "retention_days": 7, - "max_count": 1000 - }, - "long-term": { - "retention_days": 180, - "max_count": 10000 - }, - "history": { - "max_count": 100000 + "retention_days": null } } } @@ -564,193 +138,91 @@ POST /_plugins/_ml/memory_containers/_create ``` {% include copy-curl.html %} -In this example, sessions older than 7 days are deleted. If more than 1,000 non-pinned sessions exist before 7 days have passed, the oldest are evicted early. Long-term memories are kept for 180 days or until 10,000 accumulate. History keeps the most recent 100,000 entries. - -### Example 2: Research assistant with long memory +### Disabling retention for a container -This is a knowledge-heavy agent for which long-term memory is critical and conversations are secondary: +To disable retention for the entire container, set `retention_policy` to `null`: ```json -POST /_plugins/_ml/memory_containers/_create +PUT /_plugins/_ml/memory_containers/{memory_container_id} { - "name": "research-assistant", "configuration": { - "retention_policy": { - "sessions": { - "retention_days": 14, - "max_count": 200 - }, - "long-term": { - "max_count": 50000 - } - } + "retention_policy": null } } ``` {% include copy-curl.html %} -In this example, sessions expire after 14 days. Long-term memory grows up to 50,000 entries with no time-based expiry (knowledge is only evicted when the count is exceeded, oldest first). History has no policy, so no history cleanup occurs. - -### Example 3: Protecting important conversations +Setting `retention_policy` to `null` differs from omitting it. Omitting the policy leaves the container eligible for [cluster-level default settings](#memory-retention-settings); setting it to `null` exempts the container from those defaults. You can send this request even when retention is disabled cluster-wide. -Pin a session that contains an important troubleshooting thread so that it is never deleted, regardless of retention rules: - -```json -PUT /_plugins/_ml/memory_containers/{id}/memories/sessions/{session_id} -{ - "pinned": true -} -``` -{% include copy-curl.html %} +## Pinning memories -The pinned session and all of its messages persist indefinitely. It does not count against `max_count`, so it does not block other sessions from being kept. +Pinning a memory exempts it from deletion under a container's [retention policy](#defining-a-retention-policy). You can pin `sessions` and `long-term` memories. Pinning `sessions` retains all of their `working` memories. Because pinned memories are never deleted, they can accumulate over time. To reduce the container size, unpin `sessions` that no longer require protection. -### Example 4: Understanding logical OR between retention_days and max_count +Pinning protects a memory from deletion but does not change its age. A memory's age is measured from its `last_updated_time`, which advances only when its content changes, such as when you add messages to a session or edit memory content. Pinning does not update this timestamp, so if you later unpin the memory, its age still reflects its last content change and it may become immediately eligible for deletion. -When a memory type has both `retention_days` and `max_count`, the two rules combine as a logical OR: a session is deleted if it violates *either* rule. Understanding how the job applies them helps you predict exactly what will be removed. +Use the following request field to pin a memory. -For each container, the job builds a single set of sessions to delete, in this order: - -1. **Time-based rule first.** It adds every non-pinned session whose `last_updated_time` is older than `retention_days`. -2. **Count-based rule second.** It counts *all* non-pinned sessions in the container (not just the ones left after step 1). If that total exceeds `max_count`, it adds the oldest sessions—ordered by `last_updated_time`, oldest first—until only `max_count` would remain. -3. **Deduplication.** Because both rules add to the same set, any session selected by both is listed only once and therefore deleted only once. The two rules never combine to delete more than their union. - -The result is that a session is kept only if it satisfies *both* conditions: it is newer than `retention_days` **and** it is among the most-recently-active `max_count` sessions. Whichever rule is stricter for your data is the one that governs how much is deleted. Pinned sessions are excluded from both steps and never count toward `max_count`. - -The following scenarios all use `sessions: { retention_days: 30, max_count: 100 }`: - -- **Scenario A, only the time rule fires:** You have 50 sessions, one of which is 31 days old. The 31-day-old session is deleted (it violates `retention_days`). The count rule does nothing because 50 is below `max_count`. 49 remain. -- **Scenario B, only the count rule fires:** You have 110 sessions, all less than 30 days old. The time rule finds nothing, but the count rule deletes the 10 oldest to bring the total down to `max_count`. 100 remain. -- **Scenario C, neither rule fires:** You have 80 sessions, all less than 30 days old. No deletions occur, because neither rule is violated. -- **Scenario D, both rules fire and overlap (deduplication):** You have 130 sessions, 40 of which are older than 30 days. The time rule selects those 40. The count rule computes an excess of 30 (130 minus 100) and selects the 30 oldest sessions, which are already inside that set of 40. The union is still 40, so 40 sessions are deleted (not 70) and 90 remain. This is why the two rules never double-count. -- **Scenario E, both rules fire without full overlap:** You have 130 sessions, only 10 of which are older than 30 days. The time rule selects those 10. The count rule selects the 30 oldest, which includes those 10 plus 20 more that are still within `retention_days`. The union is 30, so 30 sessions are deleted and 100 remain. Note that 20 sessions younger than `retention_days` were still removed, because `max_count` alone required it. - -### Example 5: Disabling only time-based retention - -To apply count-based limits only, with no time-based expiry, set `retention_days` to `null`: - -```json -{ - "configuration": { - "retention_policy": { - "sessions": { - "retention_days": null, - "max_count": 500 - }, - "long-term": { - "retention_days": null, - "max_count": 10000 - } - } - } -} -``` -{% include copy.html %} +Field | Data type | Description +:--- | :--- | :--- +`pinned` | Boolean | Set to `true` to pin a memory or `false` to unpin it. Valid for `sessions` and `long-term` memories. -### Example 6: Completely opting out +The following example pins a memory (specify either `sessions` or `long-term` as `memory_type`): ```json -PUT /_plugins/_ml/memory_containers/{memory_container_id} +PUT /_plugins/_ml/memory_containers/{memory_container_id}/memories/{memory_type}/{memory_id} { - "configuration": { - "retention_policy": null - } + "pinned": true } ``` {% include copy-curl.html %} -No retention enforcement of any kind applies to this container, even if cluster defaults are configured. - -## Frequently asked questions - -**Does the retention job delete data immediately when a rule is violated?** - -No. The job runs on a schedule (every 24 hours by default). There is a staleness window of up to one job interval during which expired memories may still be visible in query results. - -**What happens to working memory when a session is deleted?** - -All working memory (messages) belonging to that session is deleted first, and then the session itself is removed. Conversations are never left in a partial state. +## Retention job -**Can I set retention rules on working memory directly?** +A background job enforces retention policies on a schedule (by default, every 24 hours). Because enforcement is scheduled rather than continuous, note the following details about when and how memories are deleted: -No. The working memory lifecycle is tied to its parent session. Configure `sessions` retention to control how long messages live. +- A memory that has passed its age or count limit is still returned in search results until the job next runs and deletes it. +- The `retention_days` limit is measured from a memory's `last_updated_time`. Adding messages to a session advances this timestamp, so active conversations are retained. Pinning a memory does not advance the timestamp. +- When a memory type sets both `retention_days` and `max_count`, a memory is deleted if it exceeds either limit. The count limit applies regardless of age: if a container holds more than `max_count` non-pinned memories, the oldest are deleted until `max_count` remain, even if they are newer than `retention_days`. +- Each run deletes at most 50,000 memories per memory type per container. A larger backlog is reduced over successive runs, so a container that far exceeds its `max_count` may take several runs to reach the limit. +- The job is disabled when multi-tenancy is active. In this case, no retention is enforced, even for containers that have a policy. +- Deleting a session does not immediately remove its messages. Working memories whose parent sessions no longer exists are removed only after they are older than `orphan_ttl_days` (by default, 7 days). The first time the job observes a container, it records a baseline and deletes no orphaned memory, deferring orphan deletion until the interval elapses. -**Does pinning a session reset its age?** +## Pausing retention -No. Pinning is a metadata operation and does not change `last_updated_time`. Adding messages to a session or updating its summary extends its lifetime. However, this does not matter for retention, because pinned sessions are never deleted regardless of age. +To pause retention while preserving configured retention policies, set `plugins.ml_commons.memory.retention_enabled` to `false`. Setting it back to `true` resumes enforcement. -**What if I have more pinned sessions than `max_count`?** +## Memory retention settings -The job never deletes pinned items. It logs a warning that the container is growing beyond the cap because of pins, but enforcement applies only to non-pinned items. To bring the container back under control, review and unpin sessions that no longer need protection. +You can customize retention behavior using the following dynamic cluster settings, which take effect without a cluster restart. -**Do cluster default changes affect existing containers?** +Setting | Default | Description +:--- | :--- | :--- +`plugins.ml_commons.memory.retention_enabled` | `false` | Enables retention cluster-wide. +`plugins.ml_commons.memory.retention_job_throttle_seconds` | `5` | The delay between containers during a job run, in seconds. Valid values are `1`--`60`. Used to reduce cluster load. +`plugins.ml_commons.memory.working_memory_ttl_days` | `-1` (disabled) | Deletes working memory after this many days in containers created with `disable_session` set to `true`. Valid values are `1`--`365`. +`plugins.ml_commons.memory.orphan_ttl_days` | `7` | Deletes working memory whose parent session no longer exists after this many days. Valid values are `1`--`365`. +`plugins.ml_commons.memory.default_session_retention_days` | `-1` (disabled) | Applies a default `retention_days` to sessions in containers that have no explicit policy. Valid values are `1`--`3650`. +`plugins.ml_commons.memory.default_session_max_count` | `-1` (disabled) | Applies a default `max_count` to sessions in containers that have no explicit policy. Valid values are `1`--`1000000`. +`plugins.ml_commons.memory.default_long_term_max_count` | `-1` (disabled) | Applies a default `max_count` to long-term memory in containers that have no explicit policy. Valid values are `1`--`1000000`. +`plugins.ml_commons.memory.default_history_max_count` | `-1` (disabled) | Applies a default `max_count` to history in containers that have no explicit policy. Valid values are `1`--`10000000`. -No. Defaults are applied to a container once (at creation time or on first backfill). Changing cluster defaults later does not update containers that already have a policy. Use the update API to modify individual containers. - -**What is the difference between "no policy" and "opted out"?** - -With "no policy" (the field is absent), the container may receive cluster defaults through backfill on the next job run. With "opted out" (`"retention_policy": null`), the container is permanently skipped by the retention job, and defaults are never backfilled. This is an active choice. - -**Can I trigger the retention job on demand?** - -Not in this version. The job runs on a fixed schedule of every 24 hours. To use a different interval, set `retention_job_interval_hours` in each node's `opensearch.yml` before the node first starts, as part of your initial cluster setup: - -```yaml -plugins.ml_commons.memory.retention_job_interval_hours: 1 -``` - -The interval is applied once, when the retention job is first scheduled, so it must be in place before agentic memory is first used on the cluster. Changing it afterward has no effect on the current schedule. Support for adjusting the interval on a running cluster is planned for a future release. - -**What OpenSearch version is required?** - -Memory retention policies require OpenSearch 3.8.0 or later. - -**Does this work with multi-tenancy?** - -Not in this version. The retention job is disabled when multi-tenancy is active. Multi-tenant support is planned for a future release. - -**My memory APIs are returning a 403 error. What is wrong?** - -There are two distinct causes: - -- Every memory API returns a 403 error (including create container, add message, retrieve, and search). This means `plugins.ml_commons.agentic_memory_enabled` is `false`. Set it back to `true` to restore API access. -- Only requests that carry `retention_policy` or `pinned` return a 403 error, while other memory APIs work. This means `plugins.ml_commons.memory.retention_enabled` is `false` (its default). This is expected, because retention is opt-in. Enable it before setting policies or pinning memories. (An explicit `"retention_policy": null` is still accepted while the feature is disabled.) - -Check both settings: - -```json -GET /_cluster/settings?include_defaults=true&filter_path=*.plugins.ml_commons.agentic_memory_enabled,*.plugins.ml_commons.memory.retention_enabled -``` -{% include copy-curl.html %} +### Default settings -**I set a retention policy, but nothing is being deleted. What should I check?** +The `default_` settings apply to any container that has not set its own retention policy. They take effect only after [retention is enabled for the cluster](#enabling-memory-retention); until then, they are stored but have no effect. Once retention is enabled, the next scheduled job applies these defaults to those containers and deletes any memories that exceed the default values, including memories that were created before you set the defaults. -Walk through this checklist in order: +Defaults are applied to a container only once; changing the `default_` settings later does not update containers to which they were already applied. To exempt a container from the defaults, set its `retention_policy` to `null`. To update a container's policy after the defaults are applied, use the [Update Memory Container API]({{site.url}}{{site.baseurl}}/ml-commons-plugin/api/agentic-memory-apis/update-memory-container/). -1. Is `plugins.ml_commons.memory.retention_enabled` set to `true`? It defaults to `false`. If it is `false`, the job does nothing, and you would not have been able to set a policy in the first place (the API returns a 403 error). Enable it first. -1. Is `plugins.ml_commons.agentic_memory_enabled` set to `true`? If it is `false`, the job was never registered. -1. Has enough time passed? The job runs every `retention_job_interval_hours` (24 hours by default). Your policy is not enforced until the next run. -1. Is your container's policy actually set? Check it with `GET /_plugins/_ml/memory_containers/{id}` and inspect `configuration.retention_policy`. -1. Are the memories pinned? Pinned memories are exempt from all retention rules. -1. Is multi-tenancy enabled? The retention job is disabled when multi-tenancy is active. +### Retention job schedule -## API reference summary + -The following table summarizes the operations used to configure retention. +The following setting controls the retention job schedule. Set it in `opensearch.yml` before starting the cluster. The job reads it once, when it is first scheduled at startup; updating it through the Cluster Settings API on a running cluster has no effect. -| Operation | Method | Endpoint | Body | -| :--- | :--- | :--- | :--- | -| Create a container with a policy | POST | `/_plugins/_ml/memory_containers/_create` | `{"configuration": {"retention_policy": {...}}}` | -| Update a container policy | PUT | `/_plugins/_ml/memory_containers/{id}` | `{"configuration": {"retention_policy": {...}}}` | -| Opt out of retention | PUT | `/_plugins/_ml/memory_containers/{id}` | `{"configuration": {"retention_policy": null}}` | -| Pin a session | PUT | `/_plugins/_ml/memory_containers/{id}/memories/sessions/{session_id}` | `{"pinned": true}` | -| Unpin a session | PUT | `/_plugins/_ml/memory_containers/{id}/memories/sessions/{session_id}` | `{"pinned": false}` | -| Pin a long-term memory | PUT | `/_plugins/_ml/memory_containers/{id}/memories/long-term/{memory_id}` | `{"pinned": true}` | -| Enable retention (administrator) | PUT | `/_cluster/settings` | `{"persistent": {"plugins.ml_commons.memory.retention_enabled": true}}` | -| Set cluster defaults | PUT | `/_cluster/settings` | `{"persistent": {"plugins.ml_commons.memory.default_session_retention_days": 90, ...}}` | -| Disable retention cluster-wide | PUT | `/_cluster/settings` | `{"persistent": {"plugins.ml_commons.memory.retention_enabled": false}}` | +Setting | Default | Description +:--- | :--- | :--- +`plugins.ml_commons.memory.retention_job_interval_hours` | `24` | How often the retention job runs, in hours. Valid values are `1`--`168`. ## Next steps -- For more information about memory containers and agent memory, see [Agentic memory]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/). -- For the container creation API reference, see [Create a memory container]({{site.url}}{{site.baseurl}}/ml-commons-plugin/api/agentic-memory-apis/create-memory-container/). +- For more information about memory containers and agentic memory, see [Agentic memory]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/). +- For the container creation API reference, see [Create Memory Container API]({{site.url}}{{site.baseurl}}/ml-commons-plugin/api/agentic-memory-apis/create-memory-container/). diff --git a/_ml-commons-plugin/agentic-memory.md b/_ml-commons-plugin/agentic-memory.md index 4f68c6fb6e..2a4f714b41 100644 --- a/_ml-commons-plugin/agentic-memory.md +++ b/_ml-commons-plugin/agentic-memory.md @@ -2,6 +2,8 @@ layout: default title: Agentic memory parent: Memory and context +has_children: true +has_toc: false nav_order: 10 --- @@ -263,5 +265,6 @@ GET /_plugins/_ml/memory_containers/{memory_container_id}/memories/working/_sear ## Next steps +- Learn about [agentic memory retention]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory-retention/). - Explore [memory container configuration]({{site.url}}{{site.baseurl}}/ml-commons-plugin/api/agentic-memory-apis/create-memory-container/) options. - Review the complete [Agentic Memory API reference]({{site.url}}{{site.baseurl}}/ml-commons-plugin/api/agentic-memory-apis/). \ No newline at end of file diff --git a/_ml-commons-plugin/api/agentic-memory-apis/add-memory.md b/_ml-commons-plugin/api/agentic-memory-apis/add-memory.md index f619166702..a9199d4312 100644 --- a/_ml-commons-plugin/api/agentic-memory-apis/add-memory.md +++ b/_ml-commons-plugin/api/agentic-memory-apis/add-memory.md @@ -39,7 +39,7 @@ Field | Data type | Required/Optional | Description `structured_data` | Object | Conditional | Structured data content for data memory. Required when `payload_type` is `data`. `binary_data` | String | Optional | Binary data content encoded as a Base64 string for binary payloads. `payload_type` | String | Required | The type of payload. Valid values are `conversational` or `data`. See [Payload types]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/#payload-types). -`namespace` | Object | Optional | The [namespace]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/#namespaces) context for organizing memories (for example, `user_id`, `session_id`, or `agent_id`). If `session_id` is not specified in the `namespace` field and `disable_session: false` (default is `true`), a new session with a new session ID is created. +`namespace` | Object | Optional | The [namespace]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/#namespaces) context for organizing memories (for example, `user_id`, `session_id`, or `agent_id`). If `session_id` is not specified in the `namespace` field and `disable_session` is `false` (the default), a new session with a new session ID is created. `metadata` | Object | Optional | Additional metadata for the memory (for example, `status`, `branch`, or custom fields). `tags` | Object | Optional | Tags for categorizing and organizing memories. `infer` | Boolean | Optional | Whether to use a large language model (LLM) to extract key information from messages. Default is `false`. When `true`, the LLM extracts key information from the original text and stores it as a memory. See [Inference mode]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/#inference-mode). diff --git a/_ml-commons-plugin/api/agentic-memory-apis/create-memory-container.md b/_ml-commons-plugin/api/agentic-memory-apis/create-memory-container.md index 2e9e1bda70..d42ea991a4 100644 --- a/_ml-commons-plugin/api/agentic-memory-apis/create-memory-container.md +++ b/_ml-commons-plugin/api/agentic-memory-apis/create-memory-container.md @@ -28,13 +28,13 @@ The indexes created for a memory container depend on the `configuration` you pro Configuration | Indexes created | Capabilities :--- | :--- | :--- -No `configuration` or no `strategies` | Working memory only (+ session if `disable_session` is `false`) | Raw message storage and retrieval. No semantic search, no long-term memory, no fact extraction. -With `strategies` (requires `llm_id`, `embedding_model_id`, and `embedding_model_type`) | Working memory + long-term memory + history (+ session if `disable_session` is `false`) | Semantic search, fact extraction, memory consolidation (ADD/UPDATE/DELETE decisions), and an audit trail of all long-term memory changes. +No `configuration` or no `strategies` | Working memory + session (unless `disable_session` is `true`) | Raw message storage and retrieval. No semantic search, no long-term memory, no fact extraction. +With `strategies` (requires `llm_id`, `embedding_model_id`, and `embedding_model_type`) | Working memory + session + long-term memory + history (unless `disable_session` is `true`) | Semantic search, fact extraction, memory consolidation (ADD/UPDATE/DELETE decisions), and an audit trail of all long-term memory changes. Each index type serves a specific purpose: - **Working memory**: Stores raw messages as they are received. Always created. -- **Session**: Tracks conversation sessions and their metadata. Created only when `disable_session` is `false` (disabled by default). +- **Session**: Tracks conversation sessions and their metadata. Created by default; set `disable_session` to `true` to opt out. - **Long-term memory**: Stores extracted facts and persistent knowledge produced by strategies. Created only when strategies are configured. - **History**: An audit trail that records every ADD, UPDATE, and DELETE operation on long-term memory. Created only when strategies are configured. Can be opted out of by setting `disable_history` to `true`. @@ -176,19 +176,20 @@ Field | Data type | Required/Optional | Description `index_prefix` | String | Optional | A custom prefix for memory indexes. If not specified, a default prefix is used: `default` when `use_system_index` is `true`, or an 8-character random UUID when `use_system_index` is `false`. `use_system_index` | Boolean | Optional | Whether to use system indexes (hidden indexes prefixed with `.plugins-ml-agentic-memory-`). Default is `true`. `disable_history` | Boolean | Optional | Whether to disable the history audit trail index. Default is `false`. This setting only takes effect when strategies are configured, because the history index records changes to long-term memory. Without strategies, no long-term memory or history index is created regardless of this setting. -`disable_session` | Boolean | Optional | Whether to disable the session tracking index. Default is `true` (sessions are disabled by default). Set to `false` to enable session tracking for organizing conversations. +`disable_session` | Boolean | Optional | Whether to disable the session tracking index. Default is `false` (sessions are enabled by default). Set to `true` to disable session tracking. `max_infer_size` | Integer | Optional | The maximum number of similar existing memories retrieved during memory consolidation to make ADD/UPDATE/DELETE decisions. Default is `5`. Maximum is `10`. -`index_settings` | Object | Optional | Custom OpenSearch index settings for the memory storage indexes that will be created for this container. Each memory type (`sessions`, `working`, `long_term`, and `history`) uses its own index. See [Index settings](#index-settings). +`index_settings` | Object | Optional | Custom OpenSearch index settings for the memory storage indexes that will be created for this container. Each memory type (`sessions`, `working`, `long_term`, and `history`) uses its own index. See [The `index_settings` object](#the-index_settings-object). `strategies` | Array | Optional | An array of [memory processing strategies]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/#memory-processing-strategies). When strategies are provided, both `llm_id` and embedding model fields (`embedding_model_id`, `embedding_model_type`) are required. See [The `strategies` array](#the-strategies-array). `parameters` | Object | Optional | Global parameters for the memory container. See [The `parameters` object](#the-parameters-object). -### Index settings +### The index_settings object You can customize the OpenSearch index settings for the storage indexes that will be created to store memory data. Each memory type uses a dedicated index, and you can configure settings like the number of shards and replicas for performance optimization. The following example shows you how to specify custom index settings in the `configuration` object: ```json +POST /_plugins/_ml/memory_containers/_create { "name": "my-memory-container", "configuration": { @@ -222,7 +223,7 @@ The following example shows you how to specify custom index settings in the `con } } ``` -{% include copy.html %} +{% include copy-curl.html %} ### The strategies array @@ -232,12 +233,12 @@ Field | Data type | Required/Optional | Description :--- | :--- | :--- | :--- `type` | String | Required | The strategy type. Valid values are `SEMANTIC`, `USER_PREFERENCE`, and `SUMMARY`. `namespace` | Array | Required | An array of [namespace]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agentic-memory/#namespaces) dimensions for organizing memories (for example, `["user_id"]` or `["agent_id", "session_id"]`). -`configuration` | Object | Optional | Strategy-specific configuration. See [The strategy `configuration` object](#the-strategy-configuration-object). +`configuration` | Object | Optional | Strategy-specific configuration. See [The `strategies.configuration` object](#the-strategies-configuration-object). `enabled` | Boolean | Optional | Whether to enable the strategy in the memory container. Default is `true`. -### The strategy configuration object +### The strategies configuration object -The strategy `configuration` object supports the following fields. +The `strategies.configuration` object supports the following fields. Field | Data type | Required/Optional | Description :--- | :--- | :--- | :--- @@ -246,6 +247,7 @@ Field | Data type | Required/Optional | Description `llm_id` | String | Optional | The LLM model ID for this strategy. Overrides the global LLM setting. ### The parameters object + The `parameters` object supports the following field. Field | Data type | Required/Optional | Description diff --git a/_ml-commons-plugin/api/agentic-memory-apis/delete-memory.md b/_ml-commons-plugin/api/agentic-memory-apis/delete-memory.md index 46971a668d..4a62dc32e2 100644 --- a/_ml-commons-plugin/api/agentic-memory-apis/delete-memory.md +++ b/_ml-commons-plugin/api/agentic-memory-apis/delete-memory.md @@ -1,6 +1,6 @@ --- layout: default -title: Delete memory +title: Delete agentic memory parent: Agentic memory APIs grand_parent: ML Commons APIs nav_order: 53 diff --git a/_ml-commons-plugin/api/agentic-memory-apis/get-memory.md b/_ml-commons-plugin/api/agentic-memory-apis/get-memory.md index ed2b38d47d..5ac732fc91 100644 --- a/_ml-commons-plugin/api/agentic-memory-apis/get-memory.md +++ b/_ml-commons-plugin/api/agentic-memory-apis/get-memory.md @@ -1,6 +1,6 @@ --- layout: default -title: Get memory +title: Get agentic memory parent: Agentic memory APIs grand_parent: ML Commons APIs nav_order: 51 diff --git a/_ml-commons-plugin/api/agentic-memory-apis/hybrid-search-memory.md b/_ml-commons-plugin/api/agentic-memory-apis/hybrid-search-memory.md index 327d788aa2..f0fc10229f 100644 --- a/_ml-commons-plugin/api/agentic-memory-apis/hybrid-search-memory.md +++ b/_ml-commons-plugin/api/agentic-memory-apis/hybrid-search-memory.md @@ -1,12 +1,12 @@ --- layout: default -title: Hybrid search memory +title: Hybrid search agentic memory parent: Agentic memory APIs grand_parent: ML Commons APIs nav_order: 70 --- -# Hybrid search memory API +# Hybrid Search Agentic Memory API **Introduced 3.6** {: .label .label-purple } diff --git a/_ml-commons-plugin/api/agentic-memory-apis/search-memory.md b/_ml-commons-plugin/api/agentic-memory-apis/search-memory.md index c55b0808b2..6e91cd849d 100644 --- a/_ml-commons-plugin/api/agentic-memory-apis/search-memory.md +++ b/_ml-commons-plugin/api/agentic-memory-apis/search-memory.md @@ -1,6 +1,6 @@ --- layout: default -title: Search memory +title: Search agentic memory parent: Agentic memory APIs grand_parent: ML Commons APIs nav_order: 54 diff --git a/_ml-commons-plugin/api/agentic-memory-apis/semantic-search-memory.md b/_ml-commons-plugin/api/agentic-memory-apis/semantic-search-memory.md index 0ee429b700..5e839df3b1 100644 --- a/_ml-commons-plugin/api/agentic-memory-apis/semantic-search-memory.md +++ b/_ml-commons-plugin/api/agentic-memory-apis/semantic-search-memory.md @@ -1,12 +1,12 @@ --- layout: default -title: Semantic search memory +title: Semantic search agentic memory parent: Agentic memory APIs grand_parent: ML Commons APIs nav_order: 60 --- -# Semantic Search Memory API +# Semantic Search Agentic Memory API **Introduced 3.6** {: .label .label-purple } diff --git a/_ml-commons-plugin/api/agentic-memory-apis/update-memory.md b/_ml-commons-plugin/api/agentic-memory-apis/update-memory.md index f4b1dd73a5..607e3b5164 100644 --- a/_ml-commons-plugin/api/agentic-memory-apis/update-memory.md +++ b/_ml-commons-plugin/api/agentic-memory-apis/update-memory.md @@ -1,6 +1,6 @@ --- layout: default -title: Update memory +title: Update agentic memory parent: Agentic memory APIs grand_parent: ML Commons APIs nav_order: 52 From 6e2e77cd9539dd3f7660688972bf6cb5f204a6f6 Mon Sep 17 00:00:00 2001 From: Fanit Kolchina Date: Tue, 28 Jul 2026 18:33:56 -0400 Subject: [PATCH 07/10] Add integer and decimal info Signed-off-by: Fanit Kolchina --- .../agentic-memory-retention.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/_ml-commons-plugin/agentic-memory-retention.md b/_ml-commons-plugin/agentic-memory-retention.md index fd927a2211..d9d234082e 100644 --- a/_ml-commons-plugin/agentic-memory-retention.md +++ b/_ml-commons-plugin/agentic-memory-retention.md @@ -195,16 +195,16 @@ To pause retention while preserving configured retention policies, set `plugins. You can customize retention behavior using the following dynamic cluster settings, which take effect without a cluster restart. -Setting | Default | Description -:--- | :--- | :--- -`plugins.ml_commons.memory.retention_enabled` | `false` | Enables retention cluster-wide. -`plugins.ml_commons.memory.retention_job_throttle_seconds` | `5` | The delay between containers during a job run, in seconds. Valid values are `1`--`60`. Used to reduce cluster load. -`plugins.ml_commons.memory.working_memory_ttl_days` | `-1` (disabled) | Deletes working memory after this many days in containers created with `disable_session` set to `true`. Valid values are `1`--`365`. -`plugins.ml_commons.memory.orphan_ttl_days` | `7` | Deletes working memory whose parent session no longer exists after this many days. Valid values are `1`--`365`. -`plugins.ml_commons.memory.default_session_retention_days` | `-1` (disabled) | Applies a default `retention_days` to sessions in containers that have no explicit policy. Valid values are `1`--`3650`. -`plugins.ml_commons.memory.default_session_max_count` | `-1` (disabled) | Applies a default `max_count` to sessions in containers that have no explicit policy. Valid values are `1`--`1000000`. -`plugins.ml_commons.memory.default_long_term_max_count` | `-1` (disabled) | Applies a default `max_count` to long-term memory in containers that have no explicit policy. Valid values are `1`--`1000000`. -`plugins.ml_commons.memory.default_history_max_count` | `-1` (disabled) | Applies a default `max_count` to history in containers that have no explicit policy. Valid values are `1`--`10000000`. +Setting | Data type | Default | Description +:--- | :--- | :--- | :--- +`plugins.ml_commons.memory.retention_enabled` | Boolean | `false` | Enables retention cluster-wide. +`plugins.ml_commons.memory.retention_job_throttle_seconds` | Integer | `5` | The delay between containers during a job run, in seconds. Valid values are `1`--`60`. Used to reduce cluster load. +`plugins.ml_commons.memory.working_memory_ttl_days` | Integer | `-1` (disabled) | Deletes working memory after this many days in containers created with `disable_session` set to `true`. Valid values are `1`--`365`. +`plugins.ml_commons.memory.orphan_ttl_days` | Integer | `7` | Deletes working memory whose parent session no longer exists after this many days. Valid values are `1`--`365`. +`plugins.ml_commons.memory.default_session_retention_days` | Integer | `-1` (disabled) | Applies a default `retention_days` to sessions in containers that have no explicit policy. Valid values are `1`--`3650`. +`plugins.ml_commons.memory.default_session_max_count` | Integer | `-1` (disabled) | Applies a default `max_count` to sessions in containers that have no explicit policy. Valid values are `1`--`1000000`. +`plugins.ml_commons.memory.default_long_term_max_count` | Integer | `-1` (disabled) | Applies a default `max_count` to long-term memory in containers that have no explicit policy. Valid values are `1`--`1000000`. +`plugins.ml_commons.memory.default_history_max_count` | Integer | `-1` (disabled) | Applies a default `max_count` to history in containers that have no explicit policy. Valid values are `1`--`10000000`. ### Default settings @@ -218,9 +218,9 @@ Defaults are applied to a container only once; changing the `default_` settings The following setting controls the retention job schedule. Set it in `opensearch.yml` before starting the cluster. The job reads it once, when it is first scheduled at startup; updating it through the Cluster Settings API on a running cluster has no effect. -Setting | Default | Description -:--- | :--- | :--- -`plugins.ml_commons.memory.retention_job_interval_hours` | `24` | How often the retention job runs, in hours. Valid values are `1`--`168`. +Setting | Data type | Default | Description +:--- | :--- | :--- | :--- +`plugins.ml_commons.memory.retention_job_interval_hours` | Integer | `24` | How often the retention job runs, in hours. Valid values are `1`--`168`. ## Next steps From 97725a1f3f6fc6e9dd42e84950128c354da146b6 Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Tue, 28 Jul 2026 18:35:21 -0400 Subject: [PATCH 08/10] Apply suggestion from @kolchfa-aws Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- .github/vale/styles/Vocab/OpenSearch/Words/accept.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/vale/styles/Vocab/OpenSearch/Words/accept.txt b/.github/vale/styles/Vocab/OpenSearch/Words/accept.txt index e1f3724ae6..f7cda8b613 100644 --- a/.github/vale/styles/Vocab/OpenSearch/Words/accept.txt +++ b/.github/vale/styles/Vocab/OpenSearch/Words/accept.txt @@ -87,7 +87,7 @@ gzip [Hh]ostname [Hh]otspots? [Hh]yperparameters -[Ii]dempotent(?:ly|cy)? +[Ii]dempotent [Ii]mpactful [Ii]nfographics? [Ii]ngress From b1992d48ecf1432a38b13b7cd732f1a28a340dc0 Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Tue, 28 Jul 2026 19:11:13 -0400 Subject: [PATCH 09/10] Apply suggestion from @kolchfa-aws Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- .../api/agentic-memory-apis/create-memory-container.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_ml-commons-plugin/api/agentic-memory-apis/create-memory-container.md b/_ml-commons-plugin/api/agentic-memory-apis/create-memory-container.md index d42ea991a4..643a38e26c 100644 --- a/_ml-commons-plugin/api/agentic-memory-apis/create-memory-container.md +++ b/_ml-commons-plugin/api/agentic-memory-apis/create-memory-container.md @@ -34,7 +34,7 @@ With `strategies` (requires `llm_id`, `embedding_model_id`, and `embedding_model Each index type serves a specific purpose: - **Working memory**: Stores raw messages as they are received. Always created. -- **Session**: Tracks conversation sessions and their metadata. Created by default; set `disable_session` to `true` to opt out. +- **Session**: Tracks conversation sessions and their metadata. Created by default. To disable session tracking, set `disable_session` to `true`. - **Long-term memory**: Stores extracted facts and persistent knowledge produced by strategies. Created only when strategies are configured. - **History**: An audit trail that records every ADD, UPDATE, and DELETE operation on long-term memory. Created only when strategies are configured. Can be opted out of by setting `disable_history` to `true`. From 1cc5662e7d8ae8cfb7997a29da472473963d2446 Mon Sep 17 00:00:00 2001 From: AirFunnyB Date: Wed, 29 Jul 2026 12:20:22 -0700 Subject: [PATCH 10/10] Update create-memory-container.md changed incorrect "short_term_memory_index" to correct "working_memory_index" Signed-off-by: AirFunnyB --- .../api/agentic-memory-apis/create-memory-container.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_ml-commons-plugin/api/agentic-memory-apis/create-memory-container.md b/_ml-commons-plugin/api/agentic-memory-apis/create-memory-container.md index 643a38e26c..358444a4cb 100644 --- a/_ml-commons-plugin/api/agentic-memory-apis/create-memory-container.md +++ b/_ml-commons-plugin/api/agentic-memory-apis/create-memory-container.md @@ -201,7 +201,7 @@ POST /_plugins/_ml/memory_containers/_create "number_of_replicas": "2" } }, - "short_term_memory_index": { + "working_memory_index": { "index": { "number_of_shards": "2", "number_of_replicas": "2"