メッセージおよびメモリーの存続時間の使用
メッセージと記憶は、それらを作成した会話が終了した後も検索可能なままにできます。存続時間により、アプリケーションは、コンプライアンス要件を満たす、古い可能性がある情報を削除する、ストレージ領域を節約するなど、定義された期間後にレコードを自動的に期限切れにできます。
このガイドでは、Oracle DBでバックアップされたメッセージおよびメモリーのレコード有効期限を構成する方法を示します。また、パージ・ジョブおよびレコードごとの存続時間上書きについても説明します。
Oracle Agent Memoryは、次の2つのレイヤーに存続期間を適用します。
- スキーマ・レベルの保存のデフォルトは、
MemoryRetentionConfigから取得されます。新しい書込みで省略されたttl_days値は、MemoryRetentionConfig.default_ttl_daysを使用します。 - レコードごとの
ttl_daysおよびttl_anchor値を使用すると、1つのメッセージまたはメモリーが、スキーマのデフォルトとは異なるアンカー時間より早く、または後で期限切れになります。
レコードごとのアンカーを選択する場合は、TimeToLiveAnchor.CREATED_ATを使用してOracleが行を格納した時点からカウントするか、TimeToLiveAnchor.TIMESTAMPを使用してレコードのイベント・タイムスタンプからカウントします。
ノート:レコードを定義した期間使用可能にしたまま、自動的に失効する場合は、存続時間を使用します。たとえば、アプリケーションはサポートの詳細を30日間保持したり、一時的なタスク情報を1週間保持したりできます。
次の保持ルールが適用されます。
- 新しい書込みで
ttl_daysを省略すると、スキーマのデフォルトが使用されます。 ttl_days=Noneを渡すと、最大値が構成されている場合はmax_ttl_daysを使用し、最大値が構成されていない場合は失効しない行が格納されます。max_ttl_daysを超える値は、警告付きでその最大値に固定されます。ttl_daysまたはttl_anchorが指定されていないかぎり、更新APIは現在の有効期限を保持します。
ヒント:パッケージの設定については、エージェント・メモリーのスタート・ガイドを参照してください。この例にローカルOracleデータベースが必要な場合は、「Oracle AI Databaseのローカル実行」に従います。
スキーマレベルの保持のデフォルトの構成
SDKで管理対象Oracleスキーマを作成またはアップグレードし、それとともに保存ポリシーを保持する場合は、schema_policy=SchemaPolicy.CREATE_IF_NECESSARYを使用してOracleAgentMemoryクライアントを作成します。
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,
),
)
この構成では、次のことが行われます。
ttl_daysを省略する新しい書込みは、デフォルトで30日になります。90日を超える明示的なttl_days値は、警告付きで固定されます。max_ttl_daysが設定されているため、明示的なttl_days=Noneは90日に解決されます。
APIリファレンス: OracleAgentMemory
稼働時間を使用したメッセージとメモリーの追加
スレッド書込みを使用して、一部のレコードのスキーマのデフォルトTTLに依存し、他のレコードに別のTTLを設定します。
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
この例では:
add_messages()はttl_daysを省略するため、両方のメッセージでスキーマのデフォルトの30日が使用されます。mem-ttl-backfillはTimeToLiveAnchor.TIMESTAMPを使用するため、格納されたイベント・タイムスタンプの90日後に期限切れになりますmem-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ジョブも作成します。ジョブは、1つの大きな削除を発行するかわりにバッチで行をパージし、バッチ間でコミットし、1つの参照タイムスタンプを前もって取得して、1つの実行で一貫した失効カットオフを使用します。
また、スケジューラ定義はschedule_limitを1日に設定するため、任意に遅れて実行するのではなく、遅すぎて開始する実行をスキップできます。ジョブ本体は、実行開始時間から1日後に新しいバッチの取得を個別に停止します。その実行時制限はバッチ間でチェックされるため、すでに進行中のバッチを終了してコミットできます。実行の終了時に、ジョブは、実行時制限に達したかどうか、およびカテゴリごとに削除された行数を含む短いDBMS_OUTPUTサマリーを書き込みます。
SchemaPolicy.CREATE_IF_NECESSARYでそのジョブを作成する必要があるが、スキーマ・ユーザーにCREATE JOBがない場合、セットアップは警告で完了します。期限切れのレコードは読取りおよび検索から非表示のままですが、特権ユーザーがジョブを作成するまでは物理的にパージされません。
table_name_prefixを使用する場合は、次のジョブおよび表名に同じ接頭辞を適用します。デフォルトの管理対象ジョブ名はPURGE_EXPIRED_RECORDS_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;
/
スケジューラ定義を確認し、最近の実行履歴を検査すると便利です。必要に応じて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 = 'PURGE_EXPIRED_RECORDS_J';
パージ実行が正常に実行され終了していることを確認する必要がある場合は、最近の実行を確認します。
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;
インストールをただちに検証する場合は、ジョブを手動で1回実行します。
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 (
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,
)