Usar o Tempo de Vida para Mensagens e Memórias

Mensagens e memórias podem permanecer pesquisáveis após a conversa que as criou terminar. O time-to-live permite que os aplicativos expirem automaticamente registros após um período definido, por exemplo, para atender aos requisitos de conformidade, remover informações que provavelmente estão desatualizadas ou economizar espaço de armazenamento.

Este guia mostra como configurar a expiração do registro para mensagens e memórias apoiadas pelo Oracle DB. Ele também explica os trabalhos de limpeza e as substituições de tempo de vida por registro.

O Oracle Agent Memory aplica o tempo de vida em duas camadas:

Quando você selecionar uma âncora por registro, use TimeToLiveAnchor.CREATED_AT para contar a partir de quando o Oracle armazena a linha ou TimeToLiveAnchor.TIMESTAMP para contar a partir do timestamp de evento do registro.

Observação: use o tempo de vida quando os registros devem permanecer disponíveis para um período definido e, em seguida, expirar automaticamente. Por exemplo, um aplicativo pode reter detalhes de suporte por 30 dias ou informações de tarefa temporária por uma semana.

Observação: vale a pena ter em mente algumas regras de retenção:

Dica: Para configurar o pacote, consulte Conceitos Básicos da Memória do Agente. Se você precisar de um Oracle AI Database local para este exemplo, siga Executar o Oracle AI Database localmente.

Configurar padrões de retenção no nível do esquema

Crie um cliente OracleAgentMemory com schema_policy=SchemaPolicy.CREATE_IF_NECESSARY quando quiser que o SDK crie ou faça upgrade do esquema Oracle gerenciado e persista uma política de retenção ao lado dele.

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

Neste configuração:

Referência de API: OracleAgentMemory

Adicione Mensagens e Memórias com o Tempo de Vida

Use gravações de thread para depender do TTL padrão do esquema para alguns registros ao definir um TTL diferente para outros.

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

Neste exemplo:

Referência de API: OracleThread

Atualizar o Tempo de Vida nos Registros Existentes

Use update_message() ou update_memory() para atualizar uma expiração existente sem substituir o conteúdo armazenado.

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

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

Essas chamadas mantêm o conteúdo armazenado e os metadados no local ao recalcular a janela de expiração. Lembre-se de que as atualizações preservam a expiração atual, a menos que você passe ttl_days ou ttl_anchor.

Referência de API: OracleThread

Garantir que o Job de Expurgação Gerenciado Existe

Quando o Oracle Agent Memory cria ou faz upgrade de seu esquema Oracle gerenciado, ele também cria dois jobs DBMS_SCHEDULER diários. Um exclui mensagens expiradas e linhas de memória e seus pedaços de recuperação. O outro exclui partes de recuperação órfãs cuja linha de origem não existe mais ou não corresponde mais ao seu tipo de origem. Ambas as tarefas expurgam linhas em lotes em vez de emitir uma exclusão grande, commit entre lotes e capturam um marcador de data/hora de referência antecipadamente para que cada execução use um limite de expiração consistente.

Cada definição do scheduler define schedule_limit como um dia para que uma execução que começa tarde demais possa ser ignorada em vez de ser executada arbitrariamente tarde. Cada corpo do trabalho para separadamente de tomar novos lotes após um dia a partir do horário de início da execução. Esse limite de tempo de execução é verificado entre lotes, portanto, um lote já em andamento pode ser concluído e confirmado. No final de uma execução, cada job grava um breve resumo DBMS_OUTPUT que inclui se o limite de runtime foi atingido e quantas linhas foram excluídas.

Quando o SchemaPolicy.CREATE_IF_NECESSARY precisa criar um job, mas o usuário do esquema não tem o CREATE JOB, a configuração é concluída com uma advertência. Os registros expirados permanecem ocultos de leituras e pesquisas, e os blocos órfãos permanecem até que um usuário com privilégios crie o job ausente.

Se você definir memory_store_id explicitamente, prefixe os nomes de job e tabela com esse ID mais um sublinhado. Se você ainda usar o table_name_prefix obsoleto, aplique esse prefixo. Os nomes de job gerenciados padrão são PURGE_EXPIRED_RECORDS_J e PURGE_ORPHANED_CHUNKS_J.

Dica: Use as seguintes opções de DBA quando o proprietário do esquema não tiver CREATE JOB.

Conceder Privilégios do Job do Scheduler

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

Criar Trabalho de Expurgação Manualmente

Substitua APP_SCHEMA pelo proprietário do esquema gerenciado e ajuste os nomes dos objetos se um prefixo de nome de tabela estiver configurado. O bloco PL/SQL a seguir é intencionalmente compacto; portanto, ele permanece dentro do limite de tamanho job_action do Oracle Scheduler, mesmo quando os nomes de objetos prefixados são longos:

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;
/

A limpeza de bloco órfão é executada como um trabalho diário separado:

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;
/

É útil confirmar a definição do scheduler e inspecionar o histórico de execução recente. Substitua APP_SCHEMA se necessário e aplique o prefixo de nome de tabela configurado ao nome do job quando sua implantação usar um.

Verifique se o job de expurgação gerenciado existe e manteve as definições de inspeção esperadas:

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'
       );

Verifique as execuções recentes quando precisar confirmar que as execuções de limpeza estão ocorrendo e terminando com êxito:

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;

Execute um dos jobs uma vez manualmente se quiser validar a instalação imediatamente:

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

Conclusão

Neste guia, aprendemos como os padrões de retenção no nível do esquema, os valores ttl_days por registro e TimeToLiveAnchor funcionam juntos, como os jobs gerenciados removem linhas expiradas e órfãs e como atualizar as expirações existentes do Python.

→ Depois de aprender a controlar a vida útil do registro, agora você pode prosseguir para Usar Filtragem de Metadados e Metadados.

Código Inteiro

Copie o código completo a seguir.

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