对消息和内存使用生存时间
消息和记忆可以在创建它们的对话结束后保持可搜索。通过上线时间,应用可以在定义的时段后自动使记录失效,例如满足合规性要求、删除可能已过时的信息或节省存储空间。
本指南说明如何为 Oracle DB 支持的消息和内存配置记录失效。它还解释了清除作业和按记录的生存时间覆盖。
Oracle Agent Memory 在两个层中应用生存时间:
- 方案级保留默认值来自
MemoryRetentionConfig。在新写入时省略了ttl_days值,请使用MemoryRetentionConfig.default_ttl_days。 - 每条记录
ttl_days和ttl_anchor值允许一条消息或内存比模式默认值更快、更晚或从不同的锚点时间到期。
当您选择每条记录锚点时,请使用 TimeToLiveAnchor.CREATED_AT 从 Oracle 存储行的时间开始计数,或者使用 TimeToLiveAnchor.TIMESTAMP 从记录的事件时间戳中计数。
注:当记录应在定义的期间内保持可用,然后自动到期时,请使用生存时间。例如,应用程序可能会将支持详细信息保留 30 天,或将临时任务信息保留一周。
注:一些保留规则值得注意:
- 在新写入时省略
ttl_days将使用模式默认值。 - 在配置最大值时传递
ttl_days=None使用max_ttl_days,在未配置最大值时存储非失效行。 - 大于
max_ttl_days的值将固定到该最大值并显示警告。 - 更新 API 将保留当前到期时间,除非提供
ttl_days或ttl_anchor。
提示:有关软件包设置,请参见 Get Started with Agent Memory 。如果在此示例中需要本地 Oracle AI Database,请遵循在本地运行 Oracle AI Database 。
配置方案级保留默认值
如果希望 SDK 创建或升级托管 Oracle 方案并与其一起保留保留策略,请使用 schema_policy=SchemaPolicy.CREATE_IF_NECESSARY 创建 OracleAgentMemory 客户端。
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,
),
)
在此配置中:
- 忽略
ttl_days的新写入默认为30天 - 超过
90天的显式ttl_days值将受到警告的限制 - 显式
ttl_days=None解析为90天,因为已设置max_ttl_days
API 参考:OracleAgentMemory
使用实时添加消息和内存
在为其他记录设置不同的 TTL 时,使用线程写入来依赖某些记录的模式默认 TTL。
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
在本例中:
add_messages()省略ttl_days,因此两条消息都使用模式默认值30天mem-ttl-backfill在存储的事件时间戳后90天到期,因为它使用TimeToLiveAnchor.TIMESTAMPmem-ttl-short覆盖默认值,并在7天后过期
API 参考:OracleThread
刷新现有记录上的生存时间
使用 update_message() 或 update_memory() 刷新现有到期,而不替换存储的内容。
ttl_thread.update_message("msg-ttl-1", ttl_days=14)
ttl_thread.update_memory(
"mem-ttl-backfill",
ttl_days=60,
ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)
这些调用将保留存储的内容和元数据,同时重新计算到期窗口。请记住,除非您传递 ttl_days 或 ttl_anchor,否则更新将保留当前到期。
API 参考:OracleThread
确保存在托管清除作业
当 Oracle Agent Memory 创建或升级其托管 Oracle 方案时,它还会创建两个每日 DBMS_SCHEDULER 作业。一个删除过期消息行和内存行及其检索块。另一个删除源行不再存在或不再与其源类型匹配的孤立检索块。这两个作业将批量清除行,而不是在批处理之间发出一个大型删除、提交并获取一个参考时间戳,以便每次运行都使用一个一致的失效截止。
每个调度程序定义将 schedule_limit 设置为一天,以便跳过启动太晚的运行,而不是任意延迟运行。每个作业主体分别停止从运行开始时间起一天后获取新批。在批之间检查该运行时限制,以便允许正在进行的批完成并提交。在运行结束时,每个作业都会写入一个简短的 DBMS_OUTPUT 摘要,其中包括是否已达到运行时限制以及删除了多少行。
当 SchemaPolicy.CREATE_IF_NECESSARY 需要创建任一作业但方案用户缺少 CREATE JOB 时,设置将完成并显示警告。过期的记录不会被读取和搜索隐藏,孤立的块会一直保留,直到特权用户创建缺少的作业为止。
如果显式设置了 memory_store_id,请在作业和表名前面添加该 ID 和下划线。如果仍使用过时的 table_name_prefix,请改为应用该前缀。缺省托管作业名称为 PURGE_EXPIRED_RECORDS_J 和 PURGE_ORPHANED_CHUNKS_J。
提示:方案所有者没有 CREATE JOB 时,请使用以下 DBA 选项。
授予调度程序作业权限
GRANT CREATE JOB TO app_schema;
-- Run OracleAgentMemory schema setup as app_schema.
REVOKE CREATE JOB FROM app_schema;
手动创建清除作业
将 APP_SCHEMA 替换为托管方案所有者,并调整对象名称(如果配置了表名前缀)。以下 PL/SQL 块是有意压缩的,因此即使前缀对象名称很长,它仍会保留在 Oracle Scheduler 的 job_action 长度限制内:
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;
/
孤立块清除作为单独的日常作业运行:
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;
/
确认排程引擎定义并检查最近的运行历史记录非常有用。如果需要,请替换 APP_SCHEMA,并在部署使用表名前缀时将配置的表名前缀应用于作业名。
检查托管清除作业是否存在并保留预期的检验设置:
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'
);
在需要确认正在执行清除并成功完成时,检查最近的运行:
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;
如果要立即验证安装,请手动运行任一作业:
BEGIN
DBMS_SCHEDULER.RUN_JOB(
job_name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
use_current_session => TRUE
);
END;
/
结论
在本指南中,我们了解了模式级保留默认值、每记录 ttl_days 值和 TimeToLiveAnchor 如何协同工作、托管作业如何删除过期行和孤立行以及如何从 Python 刷新现有过期行。
→学会了如何控制记录生存期后,现在可以继续使用元数据和元数据筛选。
完整代码
复制后面的完整代码。
#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,
)