Uso del tiempo de vida para mensajes y recuerdos

Los mensajes y los recuerdos pueden seguir siendo buscables después de que termine la conversación que los creó. Time-to-live permite que las aplicaciones caduquen automáticamente los registros después de un período definido, por ejemplo, para cumplir con los requisitos de conformidad, eliminar información que probablemente esté obsoleta o ahorrar espacio de almacenamiento.

En esta guía se muestra cómo configurar la caducidad del registro para los mensajes y memorias respaldados por Oracle DB. También explica los trabajos de depuración y las sustituciones de tiempo de actividad por registro.

La memoria del agente de Oracle aplica el tiempo de actividad en dos capas:

Al seleccionar un anclaje por registro, utilice TimeToLiveAnchor.CREATED_AT para contar desde el momento en que Oracle almacena la fila o TimeToLiveAnchor.TIMESTAMP para contar desde el registro de hora del evento del registro en su lugar.

Nota: Utilice el tiempo de actividad cuando los registros deban permanecer disponibles durante un período definido y, a continuación, caduquen automáticamente. Por ejemplo, una aplicación puede conservar los detalles de soporte durante 30 días o la información de tarea temporal durante una semana.

Se aplican las siguientes reglas de retención:

Indicación: Para la configuración del paquete, consulte Introducción a la memoria del agente. Si necesita una base de datos Oracle local para este ejemplo, siga Ejecutar Oracle AI Database localmente.

Configuración de Valores por Defecto de Retención de Nivel de Esquema

Cree un cliente OracleAgentMemory con schema_policy=SchemaPolicy.CREATE_IF_NECESSARY cuando desee que el SDK cree o actualice el esquema de Oracle gestionado y mantenga una política de retención junto con él.

import oracledb

from oracleagentmemory.apis import TimeToLiveAnchor
from oracleagentmemory.core import (
    MemoryExtractionConfig,
    MemoryRetentionConfig,
    OracleAgentMemory,
    SchemaPolicy,
)
from oracleagentmemory.core.embedders.embedder import Embedder


embedder = Embedder(model="YOUR_EMBEDDING_MODEL")
db_pool = oracledb.SessionPool(
    user="YOUR DB USER",
    password="YOUR DB PASSWORD",
    dsn="YOUR DB CONNECT STRING",
)

memory = OracleAgentMemory(
    connection=db_pool,
    embedder=embedder,
    schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
    memory_extraction_config=MemoryExtractionConfig(extract_memories=False),
    memory_retention_config=MemoryRetentionConfig(
        default_ttl_days=30,
        max_ttl_days=90,
    ),
)

En esta configuración:

Referencia de API: OracleAgentMemory

Agregar mensajes y recuerdos con el tiempo de vida

Utilice escrituras de thread para basarse en el TTL por defecto del esquema para algunos registros al definir un TTL diferente para otros.

from datetime import datetime, timezone

ttl_thread = memory.create_thread(
    thread_id="ttl_demo_thread",
    user_id="user_123",
)

message_ids = ttl_thread.add_messages(
    [
        {
            "id": "msg-ttl-1",
            "role": "user",
            "content": (
                "I opened ticket 1042 yesterday because the laptop battery failed."
            ),
            "timestamp": "2026-04-01T09:00:00Z",
        },
        {
            "id": "msg-ttl-2",
            "role": "assistant",
            "content": (
                "I will keep ticket 1042 active and send a replacement checklist."
            ),
            "timestamp": "2026-04-01T09:01:00Z",
        },
    ]
)

timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
#for instance, 2026-04-01T09:00:00Z

backfilled_memory_id = ttl_thread.add_memory(
    "Ticket 1042 battery-failure report was filed on 2026-04-01.",
    memory_id="mem-ttl-backfill",
    timestamp=timestamp,
    ttl_days=90,
    ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)

short_lived_memory_id = ttl_thread.add_memory(
    "Replacement checklist should be revisited within one week.",
    memory_id="mem-ttl-short",
    ttl_days=7,
)

print(message_ids)
print(backfilled_memory_id)
print(short_lived_memory_id)
#['msg-ttl-1', 'msg-ttl-2']
#mem-ttl-backfill
#mem-ttl-short

En este ejemplo:

Referencia de API: OracleThread

Refrescar tiempo de actividad en registros existentes

Utilice update_message() o update_memory() para refrescar una caducidad existente sin sustituir el contenido almacenado.

ttl_thread.update_message("msg-ttl-1", ttl_days=14)

ttl_thread.update_memory(
    "mem-ttl-backfill",
    ttl_days=60,
    ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)

Estas llamadas mantienen el contenido y los metadatos almacenados en su lugar mientras se vuelve a calcular la ventana de caducidad. Recuerde que las actualizaciones conservan la caducidad actual a menos que pase ttl_days o ttl_anchor.

Referencia de API: OracleThread

Asegúrese de que existe el trabajo de depuración gestionado

Cuando la memoria del agente de Oracle crea o actualiza su esquema de Oracle gestionado, también crea un trabajo DBMS_SCHEDULER diario que suprime las filas de memoria y mensajes caducadas y sus fragmentos de recuperación. El trabajo depura las filas en lotes en lugar de emitir una gran supresión, confirmaciones entre lotes y captura un registro de hora de referencia por adelantado para que una sola ejecución utilice un corte de caducidad coherente.

La definición del programador también define schedule_limit en un día para que se pueda omitir una ejecución que se inicie demasiado tarde en lugar de ejecutarse arbitrariamente tarde. El cuerpo del trabajo deja de tomar lotes nuevos por separado después de un día desde la hora de inicio de ejecución. Ese límite de tiempo de ejecución se comprueba entre lotes, por lo que un lote que ya está en curso puede finalizar y confirmar. Al final de una ejecución, el trabajo escribe un breve resumen DBMS_OUTPUT que incluye si se ha alcanzado el límite de tiempo de ejecución y cuántas filas se han suprimido por categoría.

Cuando SchemaPolicy.CREATE_IF_NECESSARY necesita crear ese trabajo pero el usuario de esquema carece de CREATE JOB, la configuración se completa con una advertencia. Los registros caducados permanecen ocultos para las lecturas y la búsqueda, pero no se depuran físicamente hasta que un usuario con privilegios crea el trabajo.

Si utiliza table_name_prefix, aplique el mismo prefijo a los nombres de trabajo y tabla que aparecen a continuación. El nombre del trabajo gestionado por defecto es PURGE_EXPIRED_RECORDS_J.

Consejo: utilice las siguientes opciones de DBA cuando el propietario del esquema no tenga CREATE JOB.

Otorgar Privilegios de Trabajo del Planificador

GRANT CREATE JOB TO app_schema;
-- Run OracleAgentMemory schema setup as app_schema.
REVOKE CREATE JOB FROM app_schema;

Creación Manual del Trabajo de Depuración

Sustituya APP_SCHEMA por el propietario del esquema gestionado y ajuste los nombres de objeto si se configura un prefijo de nombre de tabla. El bloque PL/SQL siguiente es intencionalmente compacto, por lo que permanece dentro del límite de longitud job_action de Oracle Scheduler incluso cuando los nombres de objeto con prefijo son largos:

BEGIN
  DBMS_SCHEDULER.CREATE_JOB(
    job_name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
    job_type => 'PLSQL_BLOCK',
    job_action => q'[
      DECLARE
        bs CONSTANT PLS_INTEGER := 1000;
        n PLS_INTEGER;
        mc PLS_INTEGER := 0;
        xc PLS_INTEGER := 0;
        mr PLS_INTEGER := 0;
        xr PLS_INTEGER := 0;
        tl PLS_INTEGER := 0;
        rt TIMESTAMP(6) WITH TIME ZONE := SYSTIMESTAMP;
        dl TIMESTAMP(6) WITH TIME ZONE := rt + INTERVAL '1 00:00:00' DAY TO SECOND;
      BEGIN
        LOOP
          IF SYSTIMESTAMP >= dl THEN
            tl := 1;
            EXIT;
          END IF;
          DELETE FROM APP_SCHEMA.RECORD_CHUNKS c
          WHERE c.rowid IN (
            SELECT rid
            FROM (
              SELECT c.rowid AS rid
              FROM APP_SCHEMA.RECORD_CHUNKS c
              WHERE c.source_record_type = 'message'
                AND EXISTS (
                  SELECT 1
                  FROM APP_SCHEMA.MESSAGE m
                  WHERE m.record_id = c.source_id
                    AND m.expires_at IS NOT NULL
                    AND m.expires_at <= rt
                )
              FETCH FIRST bs ROWS ONLY
            )
          );
          n := SQL%ROWCOUNT;
          mc := mc + n;
          EXIT WHEN n = 0;
          COMMIT;
        END LOOP;

        LOOP
          IF SYSTIMESTAMP >= dl THEN
            tl := 1;
            EXIT;
          END IF;
          DELETE FROM APP_SCHEMA.RECORD_CHUNKS c
          WHERE c.rowid IN (
            SELECT rid
            FROM (
              SELECT c.rowid AS rid
              FROM APP_SCHEMA.RECORD_CHUNKS c
              WHERE c.source_record_type IN ('fact', 'guideline', 'memory', 'preference')
                AND EXISTS (
                  SELECT 1
                  FROM APP_SCHEMA.MEMORY m
                  WHERE m.record_id = c.source_id
                    AND m.memory_type = c.source_record_type
                    AND m.expires_at IS NOT NULL
                    AND m.expires_at <= rt
                )
              FETCH FIRST bs ROWS ONLY
            )
          );
          n := SQL%ROWCOUNT;
          xc := xc + n;
          EXIT WHEN n = 0;
          COMMIT;
        END LOOP;

        LOOP
          IF SYSTIMESTAMP >= dl THEN
            tl := 1;
            EXIT;
          END IF;
          DELETE FROM APP_SCHEMA.MESSAGE
          WHERE rowid IN (
            SELECT rid
            FROM (
              SELECT rowid AS rid
              FROM APP_SCHEMA.MESSAGE
              WHERE expires_at IS NOT NULL
                AND expires_at <= rt
              FETCH FIRST bs ROWS ONLY
            )
          );
          n := SQL%ROWCOUNT;
          mr := mr + n;
          EXIT WHEN n = 0;
          COMMIT;
        END LOOP;

        LOOP
          IF SYSTIMESTAMP >= dl THEN
            tl := 1;
            EXIT;
          END IF;
          DELETE FROM APP_SCHEMA.MEMORY
          WHERE rowid IN (
            SELECT rid
            FROM (
              SELECT rowid AS rid
              FROM APP_SCHEMA.MEMORY
              WHERE expires_at IS NOT NULL
                AND expires_at <= rt
              FETCH FIRST bs ROWS ONLY
            )
          );
          n := SQL%ROWCOUNT;
          xr := xr + n;
          EXIT WHEN n = 0;
          COMMIT;
        END LOOP;

        DBMS_OUTPUT.PUT_LINE(
          'OracleAgentMemory purge reference_time='
          || TO_CHAR(rt, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZH:TZM')
        );
        DBMS_OUTPUT.PUT_LINE(
          'OracleAgentMemory purge run_deadline='
          || TO_CHAR(dl, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZH:TZM')
        );
        DBMS_OUTPUT.PUT_LINE(
          'OracleAgentMemory purge runtime_limit_reached='
          || CASE WHEN tl = 1 THEN 'true' ELSE 'false' END
        );
        DBMS_OUTPUT.PUT_LINE(
          'OracleAgentMemory purge deleted message chunk rows='
          || TO_CHAR(mc)
        );
        DBMS_OUTPUT.PUT_LINE(
          'OracleAgentMemory purge deleted fact/guideline/memory/preference chunk rows='
          || TO_CHAR(xc)
        );
        DBMS_OUTPUT.PUT_LINE(
          'OracleAgentMemory purge deleted message rows='
          || TO_CHAR(mr)
        );
        DBMS_OUTPUT.PUT_LINE(
          'OracleAgentMemory purge deleted fact/guideline/memory/preference rows='
          || TO_CHAR(xr)
        );
      END;
    ]',
    start_date => SYSTIMESTAMP,
    repeat_interval => 'FREQ=DAILY;INTERVAL=1',
    enabled => FALSE,
    auto_drop => FALSE,
    comments => 'OracleAgentMemory expired-record purge'
  );
  DBMS_SCHEDULER.SET_ATTRIBUTE(
    name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
    attribute => 'logging_level',
    value => DBMS_SCHEDULER.LOGGING_RUNS
  );
  DBMS_SCHEDULER.SET_ATTRIBUTE(
    name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
    attribute => 'store_output',
    value => TRUE
  );
  DBMS_SCHEDULER.SET_ATTRIBUTE(
    name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
    attribute => 'schedule_limit',
    value => INTERVAL '1 00:00:00' DAY TO SECOND
  );
  DBMS_SCHEDULER.ENABLE('APP_SCHEMA.PURGE_EXPIRED_RECORDS_J');
END;
/

Resulta útil confirmar la definición del programador e inspeccionar el historial de ejecuciones recientes. Sustituya APP_SCHEMA si es necesario y aplique el prefijo de nombre de tabla configurado al nombre de trabajo cuando el despliegue utilice uno.

Compruebe que el trabajo de depuración gestionado existe y mantiene la configuración de inspección esperada:

SELECT owner,
       job_name,
       enabled,
       state,
       repeat_interval,
       schedule_limit,
       logging_level,
       store_output,
       start_date,
       last_start_date,
       next_run_date
FROM   all_scheduler_jobs
WHERE  owner = 'APP_SCHEMA'
AND    job_name = 'PURGE_EXPIRED_RECORDS_J';

Compruebe las ejecuciones recientes cuando necesite confirmar que las ejecuciones de depuración se están realizando y finalizan correctamente:

SELECT log_date,
       status,
       run_duration,
       output,
       additional_info
FROM   all_scheduler_job_run_details
WHERE  owner = 'APP_SCHEMA'
AND    job_name = 'PURGE_EXPIRED_RECORDS_J'
ORDER BY log_date DESC;

Ejecute el trabajo una vez manualmente si desea validar la instalación inmediatamente:

BEGIN
  DBMS_SCHEDULER.RUN_JOB(
    job_name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
    use_current_session => TRUE
  );
END;
/

Conclusión

En esta guía hemos aprendido cómo los valores por defecto de retención de nivel de esquema, los valores ttl_days por registro y TimeToLiveAnchor funcionan conjuntamente, cómo el trabajo de depuración gestionado elimina las filas caducadas y cómo refrescar las vencimientos existentes de Python.

Para obtener más información sobre el filtrado de registros por metadatos, consulte Uso del filtrado de metadatos y metadatos.

Código Completo

El siguiente código combina los ejemplos de esta guía.

#Copyright © 2026 Oracle and/or its affiliates.
#This software is under the Apache License 2.0
#(LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License
#(UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option.

#Oracle Agent Memory Code Example - Use Time-to-Live for Messages and Memories
#-----------------------------------------------------------------------------

##Create a retention aware client

from datetime import datetime, timezone
import oracledb

from oracleagentmemory.apis import TimeToLiveAnchor
from oracleagentmemory.core import (
    MemoryExtractionConfig,
    MemoryRetentionConfig,
    OracleAgentMemory,
    SchemaPolicy,
)
from oracleagentmemory.core.embedders.embedder import Embedder


embedder = Embedder(model="YOUR_EMBEDDING_MODEL")
db_pool = oracledb.SessionPool(
    user="YOUR DB USER",
    password="YOUR DB PASSWORD",
    dsn="YOUR DB CONNECT STRING",
)

memory = OracleAgentMemory(
    connection=db_pool,
    embedder=embedder,
    schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
    memory_extraction_config=MemoryExtractionConfig(extract_memories=False),
    memory_retention_config=MemoryRetentionConfig(
        default_ttl_days=30,
        max_ttl_days=90,
    ),
)



##Add messages and memories with time to live

ttl_thread = memory.create_thread(
    thread_id="ttl_demo_thread",
    user_id="user_123",
)

message_ids = ttl_thread.add_messages(
    [
        {
            "id": "msg-ttl-1",
            "role": "user",
            "content": (
                "I opened ticket 1042 yesterday because the laptop battery failed."
            ),
            "timestamp": "2026-04-01T09:00:00Z",
        },
        {
            "id": "msg-ttl-2",
            "role": "assistant",
            "content": (
                "I will keep ticket 1042 active and send a replacement checklist."
            ),
            "timestamp": "2026-04-01T09:01:00Z",
        },
    ]
)

timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
#for instance, 2026-04-01T09:00:00Z

backfilled_memory_id = ttl_thread.add_memory(
    "Ticket 1042 battery-failure report was filed on 2026-04-01.",
    memory_id="mem-ttl-backfill",
    timestamp=timestamp,
    ttl_days=90,
    ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)

short_lived_memory_id = ttl_thread.add_memory(
    "Replacement checklist should be revisited within one week.",
    memory_id="mem-ttl-short",
    ttl_days=7,
)

print(message_ids)
print(backfilled_memory_id)
print(short_lived_memory_id)
#['msg-ttl-1', 'msg-ttl-2']
#mem-ttl-backfill
#mem-ttl-short



##Refresh time to live on existing records

ttl_thread.update_message("msg-ttl-1", ttl_days=14)

ttl_thread.update_memory(
    "mem-ttl-backfill",
    ttl_days=60,
    ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)