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:

Quando si sceglie 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.

Si applicano le seguenti regole di conservazione:

Suggerimento: per l'impostazione dei package, vedere Introduzione alla memoria agente. Se hai bisogno di un database Oracle 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.

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,
    ),
)

In questa configurazione:

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.

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

In questo esempio:

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 Oracle Agent Memory crea o aggiorna il proprio schema Oracle gestito, crea anche un job DBMS_SCHEDULER giornaliero che elimina le righe di messaggio e memoria scadute e i relativi chunk di recupero. Il job rimuove le righe in batch invece di emettere un'eliminazione di grandi dimensioni, esegue il commit tra i batch e acquisisce un indicatore orario di riferimento in anticipo in modo che una singola esecuzione utilizzi un limite di scadenza coerente.

La definizione dello scheduler imposta inoltre schedule_limit su un giorno in modo che un'esecuzione che inizia troppo tardi possa essere saltata invece di essere eseguita arbitrariamente in ritardo. Il corpo del job interrompe separatamente l'utilizzo di nuovi batch dopo un giorno dall'ora di inizio 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, il job scrive un breve riepilogo DBMS_OUTPUT che include se è stato raggiunto il limite di runtime e il numero di righe eliminate per categoria.

Quando SchemaPolicy.CREATE_IF_NECESSARY deve creare il 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 dalle ricerche, ma non vengono rimossi fisicamente finché un utente con privilegi non crea il job.

Se si utilizza table_name_prefix, applicare lo stesso prefisso ai nomi di job e tabella riportati di seguito. Il nome del job gestito predefinito è PURGE_EXPIRED_RECORDS_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 riportato di seguito è 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;
/

È 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 = 'PURGE_EXPIRED_RECORDS_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 = 'PURGE_EXPIRED_RECORDS_J'
ORDER BY log_date DESC;

Eseguire il 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 il job di rimozione gestito rimuove le righe scadute e come aggiornare le scadenze esistenti da Python.

Per ulteriori informazioni sull'applicazione di filtri ai record in base ai metadati, vedere Usa filtro metadati e metadati.

Codice completo

Il codice seguente combina gli esempi in questa guida.

#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,
)