Utilizza il Time To Live per messaggi e memorie
I messaggi e i ricordi possono rimanere ricercabili al termine della conversazione che li ha creati. Il Time To Live consente alle applicazioni di scadere automaticamente i record dopo un periodo definito, ad esempio per soddisfare i requisiti di conformità, rimuovere informazioni probabilmente obsolete o risparmiare spazio di storage.
Questa guida mostra come configurare la scadenza dei record per i messaggi e le memorie supportati da Oracle DB. Vengono inoltre descritti i job di rimozione e le sostituzioni Time To Live per record.
La memoria agente Oracle applica il time-to-live in due livelli:
- I valori predefiniti di conservazione a livello di schema provengono da
MemoryRetentionConfig. I valorittl_daysomessi nelle nuove scritture utilizzanoMemoryRetentionConfig.default_ttl_days. - I valori
ttl_daysettl_anchorper record consentono la scadenza di un messaggio o di una memoria prima, dopo o da un'ora di ancoraggio diversa da quella predefinita dello schema.
Quando si seleziona un ancoraggio per record, utilizzare TimeToLiveAnchor.CREATED_AT per contare da quando Oracle memorizza la riga oppure TimeToLiveAnchor.TIMESTAMP per contare dall'indicatore orario dell'evento del record.
Nota: utilizzare il Time To Live quando i record devono rimanere disponibili per un periodo definito e quindi scadere automaticamente. Ad esempio, un'applicazione potrebbe conservare i dettagli di supporto per 30 giorni o le informazioni temporanee sui task per una settimana.
Nota: è opportuno tenere presenti alcune regole di conservazione:
- L'omissione di
ttl_daysin una nuova scrittura utilizza il valore predefinito dello schema. - Il passaggio di
ttl_days=Noneutilizzamax_ttl_daysquando è configurato un valore massimo oppure memorizza una riga non in scadenza quando non è configurato alcun valore massimo. - I valori maggiori di
max_ttl_daysvengono bloccati al massimo con un'avvertenza. - Le API di aggiornamento conservano la scadenza corrente a meno che non venga specificato
ttl_daysottl_anchor.
Suggerimento: per l'impostazione dei package, vedere Introduzione alla memoria dell'agente. Se hai bisogno di un Oracle AI Database locale per questo esempio, segui Esegui Oracle AI Database localmente.
Configurare i valori predefiniti di conservazione a livello di schema
Creare un client OracleAgentMemory con schema_policy=SchemaPolicy.CREATE_IF_NECESSARY quando si desidera che l'SDK crei o esegua l'upgrade dello schema Oracle gestito e renda persistente un criterio di conservazione accanto a esso.
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,
),
)
In questa configurazione:
- nuove scritture che omettono
ttl_daysper impostazione predefinita a30giorni - i valori
ttl_daysespliciti maggiori di90giorni vengono bloccati con un'avvertenza ttl_days=Noneesplicito viene risolto in90giorni perchémax_ttl_daysè impostato
Riferimento API: OracleAgentMemory
Aggiungi messaggi e memorie con Time To Live
Utilizzare le scritture di thread per fare affidamento sul TTL predefinito dello schema per alcuni record durante l'impostazione di un TTL diverso per altri.
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
In questo esempio:
add_messages()omettettl_days, pertanto entrambi i messaggi utilizzano il valore predefinito dello schema di30giornimem-ttl-backfillscade90giorni dopo l'indicatore orario dell'evento memorizzato perché utilizzaTimeToLiveAnchor.TIMESTAMPmem-ttl-shortsostituisce l'impostazione predefinita e scade dopo7giorni
Riferimento API: OracleThread
Aggiorna Time To Live su record esistenti
Utilizzare update_message() o update_memory() per aggiornare una scadenza esistente senza sostituire il contenuto memorizzato.
ttl_thread.update_message("msg-ttl-1", ttl_days=14)
ttl_thread.update_memory(
"mem-ttl-backfill",
ttl_days=60,
ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)
Queste chiamate mantengono in vigore il contenuto e i metadati memorizzati durante il ricalcolo della finestra di scadenza. Tenere presente che gli aggiornamenti conservano la scadenza corrente a meno che non si passi ttl_days o ttl_anchor.
Riferimento API: OracleThread
Assicurarsi che il job di rimozione gestita esista
Quando la memoria dell'agente Oracle crea o aggiorna lo schema Oracle gestito, crea anche due job DBMS_SCHEDULER giornalieri. Si eliminano le righe di messaggi e memoria scadute e i relativi chunk di recupero. L'altro elimina i chunk di recupero orfani la cui riga di origine non esiste più o non corrisponde più al relativo tipo di origine. Entrambi i job rimuovono le righe in batch invece di emettere un'eliminazione di grandi dimensioni, eseguono il commit tra i batch e acquisiscono un indicatore orario di riferimento in anticipo in modo che ogni esecuzione utilizzi un limite di scadenza coerente.
Ogni definizione dello scheduler imposta schedule_limit su un giorno in modo che un'esecuzione che inizia troppo tardi possa essere saltata invece di essere eseguita arbitrariamente in ritardo. Ogni corpo del job interrompe separatamente l'utilizzo di nuovi batch dopo un giorno dall'ora di inizio dell'esecuzione. Il limite di runtime specificato viene controllato tra i batch, pertanto è consentito terminare e eseguire il commit di un batch già in corso. Alla fine di un'esecuzione, ogni job scrive un breve riepilogo DBMS_OUTPUT che include se è stato raggiunto il limite di runtime e il numero di righe eliminate.
Quando SchemaPolicy.CREATE_IF_NECESSARY deve creare uno dei due job, ma l'utente dello schema non dispone di CREATE JOB, l'impostazione viene completata con un'avvertenza. I record scaduti rimangono nascosti dalle letture e dalla ricerca e i blocchi isolati rimangono finché un utente con privilegi non crea il job mancante.
Se si imposta memory_store_id in modo esplicito, anteporre ai nomi dei job e delle tabelle l'ID e il carattere di sottolineatura. Se si utilizza ancora il prefisso table_name_prefix obsoleto, applicarlo. I nomi dei job gestiti predefiniti sono PURGE_EXPIRED_RECORDS_J e PURGE_ORPHANED_CHUNKS_J.
Suggerimento: utilizzare le seguenti opzioni DBA quando il proprietario dello schema non dispone di CREATE JOB.
Concedi privilegi job scheduler
GRANT CREATE JOB TO app_schema;
-- Run OracleAgentMemory schema setup as app_schema.
REVOKE CREATE JOB FROM app_schema;
Crea manualmente il job di rimozione
Sostituire APP_SCHEMA con il proprietario dello schema gestito e adeguare i nomi degli oggetti se è configurato un prefisso nome tabella. Il blocco PL/SQL seguente è intenzionalmente compatto, pertanto rimane entro il limite di lunghezza job_action di Oracle Scheduler anche quando i nomi oggetto con prefisso sono lunghi:
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 cleanup dei pezzi orfani viene eseguito come job giornaliero separato:
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;
/
È utile confermare la definizione dello scheduler e ispezionare la cronologia delle esecuzioni recenti. Sostituire APP_SCHEMA se necessario e applicare il prefisso nome tabella configurato al nome del job quando la distribuzione ne utilizza uno.
Verificare che il job di rimozione gestito esista e abbia mantenuto le impostazioni di ispezione previste:
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'
);
Controllare le esecuzioni recenti quando è necessario confermare che le esecuzioni di rimozione sono in corso e sono state completate correttamente:
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;
Eseguire uno dei due job una volta manualmente se si desidera convalidare immediatamente l'installazione:
BEGIN
DBMS_SCHEDULER.RUN_JOB(
job_name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
use_current_session => TRUE
);
END;
/
Conclusione
In questa guida è stato illustrato come i valori predefiniti di conservazione a livello di schema, i valori ttl_days per record e TimeToLiveAnchor funzionano insieme, come i job gestiti rimuovono le righe scadute e orfane e come aggiornare le scadenze esistenti da Python.
→ Dopo aver appreso come controllare la durata dei record, ora è possibile passare a Usa filtro metadati e metadati.
Codice completo
Copiare il codice completo che segue.
#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,
)