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:
- Os padrões de retenção no nível do esquema são provenientes de
MemoryRetentionConfig. Os valoresttl_daysomitidos em novas gravações usamMemoryRetentionConfig.default_ttl_days. - Os valores
ttl_daysettl_anchorpor registro permitem que uma mensagem ou memória expire mais cedo, mais tarde ou de um horário de ancoragem diferente do padrão do esquema.
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:
- A omissão de
ttl_daysem uma nova gravação usa o esquema padrão. - A transmissão de
ttl_days=Noneusamax_ttl_daysquando um máximo é configurado ou armazena uma linha sem expiração quando nenhum máximo é configurado. - Os valores acima de
max_ttl_dayssão limitados a esse máximo com uma advertência. - As APIs de atualização preservam a expiração atual, a menos que
ttl_daysouttl_anchorseja fornecido.
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:
- novas gravações que omitem o padrão
ttl_dayspara30dias - valores
ttl_daysexplícitos acima de90dias são vinculados a uma advertência ttl_days=Noneexplícito é resolvido como90dias porquemax_ttl_daysestá definido
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:
add_messages()omitettl_days, portanto ambas as mensagens usam o padrão de esquema de30diasmem-ttl-backfillexpira90dias após o timestamp do evento armazenado porque usaTimeToLiveAnchor.TIMESTAMPmem-ttl-shortsubstitui o padrão e expira após7dias
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,
)