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:
- Los valores por defecto de retención de nivel de esquema provienen de
MemoryRetentionConfig. Los valoresttl_daysomitidos en las nuevas escrituras utilizanMemoryRetentionConfig.default_ttl_days. - Los valores
ttl_daysyttl_anchorpor registro permiten que un mensaje o memoria caduque más pronto, más tarde o desde un tiempo de anclaje diferente al predeterminado del esquema.
Al seleccionar un anclaje por registro, utilice TimeToLiveAnchor.CREATED_AT para realizar el recuento desde el momento en que Oracle almacena la fila, o bien TimeToLiveAnchor.TIMESTAMP para realizar el recuento desde el registro de hora del evento del registro.
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.
Nota: Hay que tener en cuenta algunas reglas de retención:
- La omisión de
ttl_daysen una nueva escritura utiliza el esquema por defecto. - Al transferir
ttl_days=Nonese utilizamax_ttl_dayscuando se configura un máximo o se almacena una fila que no caduca cuando no se configura ningún máximo. - Los valores mayores que
max_ttl_daysse sujetan a ese máximo con una advertencia. - Las API de actualización conservan la caducidad actual a menos que se proporcione
ttl_daysottl_anchor.
Indicación: Para la configuración del paquete, consulte Get Started with Agent Memory. Si necesita una instancia local de Oracle AI Database para este ejemplo, siga Ejecución local de Oracle AI Database.
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.
from datetime import datetime, timezone
import oracledb
from oracleagentmemory.apis import TimeToLiveAnchor
from oracleagentmemory.core import (
MemoryRetentionConfig,
OracleAgentMemory,
SchemaPolicy,
)
from oracleagentmemory.core.embedders.embedder import Embedder
from oracleagentmemory.core.llms.llm import Llm
embedder = Embedder(model="YOUR_EMBEDDING_MODEL")
llm = Llm(
model="YOUR_MEMORY_LLM_MODEL",
api_base="YOUR_MEMORY_LLM_BASE_URL",
api_key="YOUR_MEMORY_LLM_API_KEY",
)
db_pool = oracledb.SessionPool(
user="YOUR DB USER",
password="YOUR DB PASSWORD",
dsn="localhost:1521/...",
)
memory_store_id = "T_TIME_TO_LIVE"
memory = OracleAgentMemory(
connection=db_pool,
embedder=embedder,
llm=llm,
schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
memory_store_id=memory_store_id,
memory_retention_config=MemoryRetentionConfig(
default_ttl_days=30,
max_ttl_days=90,
),
)
En esta configuración:
- escrituras nuevas que omiten
ttl_dayspor defecto en30días - los valores
ttl_daysexplícitos de más de90días se sujetan con una advertencia ttl_days=Noneexplícito se resuelve en90días porquemax_ttl_daysestá definido
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.
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:
add_messages()omitettl_days, por lo que ambos mensajes utilizan el valor por defecto del esquema de30díasmem-ttl-backfillcaduca90días después del registro de hora del evento almacenado porque utilizaTimeToLiveAnchor.TIMESTAMPmem-ttl-shortsustituye el valor por defecto y caduca después de7días
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 dos trabajos DBMS_SCHEDULER diarios. Una suprime las filas de memoria y mensajes caducados y sus fragmentos de recuperación. El resto suprime fragmentos de recuperación huérfanos cuya fila de origen ya no existe o ya no coincide con su tipo de origen. Ambos trabajos depuran filas en lotes en lugar de emitir una gran supresión, confirmación entre lotes y capturan un registro de hora de referencia por adelantado para que cada ejecución utilice un corte de caducidad consistente.
Cada definición del programador 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. Cada cuerpo de 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, cada 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.
Cuando SchemaPolicy.CREATE_IF_NECESSARY necesita crear cualquier trabajo pero el usuario de esquema carece de CREATE JOB, la configuración se completa con una advertencia. Los registros caducados permanecen ocultos en las lecturas y búsquedas, y los fragmentos huérfanos permanecen hasta que un usuario con privilegios crea el trabajo que falta.
Si define memory_store_id explícitamente, agregue un prefijo a los nombres de trabajo y tabla con ese ID más un guion bajo. Si sigue utilizando el table_name_prefix en desuso, aplique ese prefijo en su lugar. Los nombres de trabajos gestionados por defecto son PURGE_EXPIRED_RECORDS_J y PURGE_ORPHANED_CHUNKS_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 siguiente bloque PL/SQL 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;
/
La limpieza de fragmentos huérfanos se ejecuta como un trabajo diario independiente:
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
job_name => 'APP_SCHEMA.PURGE_ORPHANED_CHUNKS_J',
job_type => 'PLSQL_BLOCK',
job_action => q'[
DECLARE
bs CONSTANT PLS_INTEGER := 1000;
n PLS_INTEGER;
oc 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 NOT (
(
c.source_record_type = 'message'
AND EXISTS (
SELECT 1
FROM APP_SCHEMA.MESSAGE m
WHERE m.record_id = c.source_id
)
)
OR (
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
)
)
OR (
c.source_record_type = 'user_profile'
AND EXISTS (
SELECT 1
FROM APP_SCHEMA.ACTOR_PROFILE a
WHERE a.actor_id = c.source_id
AND a.actor_type = 'user'
)
)
OR (
c.source_record_type = 'agent_profile'
AND EXISTS (
SELECT 1
FROM APP_SCHEMA.ACTOR_PROFILE a
WHERE a.actor_id = c.source_id
AND a.actor_type = 'agent'
)
)
)
FETCH FIRST bs ROWS ONLY
)
);
n := SQL%ROWCOUNT;
oc := oc + n;
EXIT WHEN n = 0;
COMMIT;
END LOOP;
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory orphan cleanup reference_time='
|| TO_CHAR(rt, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZH:TZM')
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory orphan cleanup run_deadline='
|| TO_CHAR(dl, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZH:TZM')
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory orphan cleanup runtime_limit_reached='
|| CASE WHEN tl = 1 THEN 'true' ELSE 'false' END
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory orphan cleanup deleted chunk rows='
|| TO_CHAR(oc)
);
END;
]',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=DAILY;INTERVAL=1',
enabled => FALSE,
auto_drop => FALSE,
comments => 'OracleAgentMemory orphaned-chunk cleanup'
);
DBMS_SCHEDULER.SET_ATTRIBUTE(
name => 'APP_SCHEMA.PURGE_ORPHANED_CHUNKS_J',
attribute => 'logging_level',
value => DBMS_SCHEDULER.LOGGING_RUNS
);
DBMS_SCHEDULER.SET_ATTRIBUTE(
name => 'APP_SCHEMA.PURGE_ORPHANED_CHUNKS_J',
attribute => 'store_output',
value => TRUE
);
DBMS_SCHEDULER.SET_ATTRIBUTE(
name => 'APP_SCHEMA.PURGE_ORPHANED_CHUNKS_J',
attribute => 'schedule_limit',
value => INTERVAL '1 00:00:00' DAY TO SECOND
);
DBMS_SCHEDULER.ENABLE('APP_SCHEMA.PURGE_ORPHANED_CHUNKS_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 IN (
'PURGE_EXPIRED_RECORDS_J',
'PURGE_ORPHANED_CHUNKS_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 IN (
'PURGE_EXPIRED_RECORDS_J',
'PURGE_ORPHANED_CHUNKS_J'
)
ORDER BY log_date DESC;
Ejecute cualquier 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 los trabajos gestionados eliminan filas caducadas y huérfanas y cómo refrescar las vencimientos existentes de Python.
→ Después de haber aprendido a controlar la duración del registro, ahora puede continuar con Use Metadata and Metadata Filtering (Usar filtrado de metadatos y metadatos).
Código Completo
Copie el código completo que aparece a continuación.
#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 (
MemoryRetentionConfig,
OracleAgentMemory,
SchemaPolicy,
)
from oracleagentmemory.core.embedders.embedder import Embedder
from oracleagentmemory.core.llms.llm import Llm
embedder = Embedder(model="YOUR_EMBEDDING_MODEL")
llm = Llm(
model="YOUR_MEMORY_LLM_MODEL",
api_base="YOUR_MEMORY_LLM_BASE_URL",
api_key="YOUR_MEMORY_LLM_API_KEY",
)
db_pool = oracledb.SessionPool(
user="YOUR DB USER",
password="YOUR DB PASSWORD",
dsn="localhost:1521/...",
)
memory_store_id = "T_TIME_TO_LIVE"
memory = OracleAgentMemory(
connection=db_pool,
embedder=embedder,
llm=llm,
schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
memory_store_id=memory_store_id,
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,
)