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ê escolher 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.

As seguintes regras de retenção se aplicam:

Dica: Para configurar o pacote, consulte Conceitos Básicos da Memória do Agente. Se você precisar de um banco de dados Oracle 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.

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

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.

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

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 um job DBMS_SCHEDULER diário que exclui linhas de mensagem e memória expiradas e seus blocos de recuperação. O job limpa linhas em batches em vez de emitir uma exclusão grande, faz commit entre batches e captura um timestamp de referência antecipadamente para que uma única execução use um limite de expiração consistente.

A definição do scheduler também 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. O 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, o job grava um breve resumo DBMS_OUTPUT que inclui se o limite de runtime foi atingido e quantas linhas foram excluídas por categoria.

Quando o SchemaPolicy.CREATE_IF_NECESSARY precisa criar esse 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, mas eles não são expurgados fisicamente até que um usuário com privilégios crie o job.

Se você usar table_name_prefix, aplique o mesmo prefixo ao job e aos nomes de tabela a seguir. O nome do job gerenciado padrão é PURGE_EXPIRED_RECORDS_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 abaixo é 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;
/

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

Execute o job 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 o job de expurgação gerenciado remove linhas expiradas e como atualizar as expirações existentes do Python.

Para obter mais informações sobre como filtrar registros por metadados, consulte Usar Filtragem de Metadados e Metadados.

Código Inteiro

O código a seguir combina os exemplos deste guia.

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