メッセージおよびメモリーの存続時間の使用

メッセージと記憶は、それらを作成した会話が終了した後も検索可能なままにできます。存続時間により、アプリケーションは、コンプライアンス要件を満たす、古い可能性がある情報を削除する、ストレージ領域を節約するなど、定義された期間後にレコードを自動的に期限切れにできます。

このガイドでは、Oracle DBでバックアップされたメッセージおよびメモリーのレコード有効期限を構成する方法を示します。また、パージ・ジョブおよびレコードごとの存続時間上書きについても説明します。

Oracle Agent Memoryは、次の2つのレイヤーに存続期間を適用します。

レコードごとのアンカーを選択する場合は、TimeToLiveAnchor.CREATED_ATを使用してOracleが行を格納した時点からカウントするか、TimeToLiveAnchor.TIMESTAMPを使用してレコードのイベント・タイムスタンプからカウントします。

ノート:レコードを定義した期間使用可能にしたまま、自動的に失効する場合は、存続時間を使用します。たとえば、アプリケーションはサポートの詳細を30日間保持したり、一時的なタスク情報を1週間保持したりできます。

次の保持ルールが適用されます。

ヒント:パッケージの設定については、エージェント・メモリーのスタート・ガイドを参照してください。この例にローカル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,
    ),
)

この構成では、次のことが行われます。

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

この例では:

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