Utiliser le temps de vie pour les messages et les souvenirs
Les messages et les souvenirs peuvent rester consultables après la fin de la conversation qui les a créés. La durée de vie permet aux applications d'expirer automatiquement les enregistrements après une période définie, par exemple pour répondre aux exigences de conformité, supprimer des informations qui sont probablement obsolètes ou économiser de l'espace de stockage.
Ce guide explique comment configurer l'expiration des enregistrements pour les messages et les mémoires sauvegardés par Oracle DB. Il explique également les tâches de purge et les remplacements de durée de vie par enregistrement.
La mémoire de l'agent Oracle applique la durée de vie en deux couches :
- Les valeurs par défaut de conservation au niveau du schéma proviennent de
MemoryRetentionConfig. Les valeursttl_daysomises sur les nouvelles écritures utilisentMemoryRetentionConfig.default_ttl_days. - Les valeurs
ttl_daysetttl_anchorpar enregistrement permettent à un message ou à une mémoire d'expirer plus tôt, plus tard ou à partir d'une heure d'ancrage différente de la valeur par défaut du schéma.
Lorsque vous choisissez une ancre par enregistrement, utilisez TimeToLiveAnchor.CREATED_AT pour compter à partir de quand Oracle stocke la ligne, ou TimeToLiveAnchor.TIMESTAMP pour compter à partir de l'horodatage de l'événement de l'enregistrement.
Remarque : utilisez le délai de vie lorsque les enregistrements doivent rester disponibles pendant une période définie, puis expirer automatiquement. Par exemple, une application peut conserver les détails de prise en charge pendant 30 jours ou les informations de tâche temporaire pendant une semaine.
Les règles de conservation suivantes s'appliquent :
- L'omission de
ttl_dayslors d'une nouvelle écriture utilise la valeur par défaut du schéma. - La transmission de
ttl_days=Noneutilisemax_ttl_dayslorsqu'une valeur maximale est configurée ou stocke une ligne qui n'expire pas lorsqu'aucune valeur maximale n'est configurée. - Les valeurs au-dessus de
max_ttl_dayssont bloquées à ce maximum avec un avertissement. - Les API de mise à jour conservent l'expiration en cours sauf si
ttl_daysouttl_anchorest fourni.
Conseil : pour la configuration des packages, reportez-vous à Introduction à la mémoire de l'agent. Si vous avez besoin d'une base de données Oracle locale pour cet exemple, suivez Exécuter Oracle AI Database localement.
Configuration des valeurs par défaut de conservation au niveau du schéma
Créez un client OracleAgentMemory avec schema_policy=SchemaPolicy.CREATE_IF_NECESSARY lorsque vous voulez que le kit SDK crée ou mette à niveau le schéma Oracle géré et conserve une stratégie de conservation en parallèle.
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,
),
)
Dans la configuration suivante :
- nouvelles écritures qui omettent
ttl_dayspar défaut à30jours - les valeurs
ttl_daysexplicites au-dessus de90jours sont bloquées avec un avertissement ttl_days=Noneexplicite est résolu en90jours carmax_ttl_daysest défini
Référence d'API : OracleAgentMemory
Ajouter des messages et des souvenirs avec le temps de vie
Utilisez les écritures de thread pour utiliser la durée de vie par défaut du schéma pour certains enregistrements tout en définissant une durée de vie différente pour d'autres.
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
Dans cet exemple :
add_messages()ometttl_days, de sorte que les deux messages utilisent la valeur de schéma par défaut de30joursmem-ttl-backfillexpire90jours après l'horodatage de l'événement stocké car il utiliseTimeToLiveAnchor.TIMESTAMPmem-ttl-shortremplace la valeur par défaut et expire après7jours
Référence d'API : OracleThread
Actualiser la durée de vie sur les enregistrements existants
Utilisez update_message() ou update_memory() pour actualiser une expiration existante sans remplacer le contenu stocké.
ttl_thread.update_message("msg-ttl-1", ttl_days=14)
ttl_thread.update_memory(
"mem-ttl-backfill",
ttl_days=60,
ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)
Ces appels maintiennent le contenu stocké et les métadonnées en place lors du recalcul de la fenêtre d'expiration. N'oubliez pas que les mises à jour conservent l'expiration en cours sauf si vous transmettez ttl_days ou ttl_anchor.
Référence d'API : OracleThread
Vérification de l'existence du travail de purge géré
Lorsque la mémoire de l'agent Oracle crée ou met à niveau son schéma Oracle géré, elle crée également un travail DBMS_SCHEDULER quotidien qui supprime les lignes de message et de mémoire expirées et leurs blocs d'extraction. Le travail purge les lignes par lots au lieu d'émettre une suppression importante, effectue des validations entre les lots et capture un horodatage de référence à l'avance, de sorte qu'une seule exécution utilise une limite d'expiration cohérente.
La définition du planificateur définit également schedule_limit sur un jour afin qu'une exécution qui démarre trop tard puisse être ignorée au lieu d'être exécutée arbitrairement en retard. Le corps du travail cesse séparément de prendre de nouveaux lots après un jour à compter de l'heure de début de l'exécution. Cette limite d'exécution est vérifiée entre les batches, de sorte qu'un batch déjà en cours est autorisé à se terminer et à valider. A la fin d'une exécution, le travail écrit un bref récapitulatif DBMS_OUTPUT qui indique si la limite d'exécution a été atteinte et combien de lignes ont été supprimées par catégorie.
Lorsque SchemaPolicy.CREATE_IF_NECESSARY doit créer ce travail mais que l'utilisateur de schéma n'a pas CREATE JOB, la configuration se termine avec un avertissement. Les enregistrements expirés restent masqués pour les lectures et les recherches, mais ils ne sont pas purgés physiquement tant qu'un utilisateur privilégié n'a pas créé le travail.
Si vous utilisez table_name_prefix, appliquez le même préfixe aux noms de travail et de table ci-dessous. Le nom de travail géré par défaut est PURGE_EXPIRED_RECORDS_J.
A savoir : Utilisez les options DBA suivantes lorsque le propriétaire du schéma n'a pas CREATE JOB.
Accorder des privilèges de travail au planificateur
GRANT CREATE JOB TO app_schema;
-- Run OracleAgentMemory schema setup as app_schema.
REVOKE CREATE JOB FROM app_schema;
Création manuelle du travail de purge
Remplacez APP_SCHEMA par le propriétaire du schéma géré et ajustez les noms d'objet si un préfixe de nom de table est configuré. Le bloc PL/SQL ci-dessous est intentionnellement compact. Il reste donc dans la limite de longueur job_action d'Oracle Scheduler même lorsque les noms d'objet préfixés sont longs :
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;
/
Il est utile de confirmer la définition du planificateur et d'inspecter l'historique des exécutions récentes. Remplacez APP_SCHEMA si nécessaire et appliquez le préfixe de nom de table configuré au nom de travail lorsque votre déploiement en utilise un.
Vérifiez que le travail de purge géré existe et conservez les paramètres d'inspection attendus :
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';
Vérifiez les exécutions récentes lorsque vous devez vérifier que les exécutions de purge se déroulent et se terminent correctement :
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;
Exécutez le travail une fois manuellement si vous souhaitez valider l'installation immédiatement :
BEGIN
DBMS_SCHEDULER.RUN_JOB(
job_name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
use_current_session => TRUE
);
END;
/
Conclusion
Dans ce guide, nous avons appris comment les valeurs par défaut de conservation au niveau du schéma, les valeurs ttl_days par enregistrement et TimeToLiveAnchor fonctionnent ensemble, comment le travail de purge géré supprime les lignes expirées et comment actualiser les expirations existantes de Python.
Pour plus d'informations sur le filtrage des enregistrements par métadonnées, reportez-vous à Utilisation du filtrage des métadonnées et des métadonnées.
Code complet
Le code suivant combine les exemples de ce guide.
#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,
)