對訊息和記憶體使用存留時間

訊息和記憶在建立訊息的對話結束後仍可繼續搜尋。存留時間可讓應用程式在定義的期間之後自動將記錄設為過期,例如滿足合規性需求、移除可能過時的資訊或節省儲存空間。

本指南說明如何設定 Oracle DB 備份訊息和備忘錄的記錄到期。它也說明永久清除工作與每一記錄存留時間覆寫。

Oracle Agent Memory 會在兩層套用存留時間:

選擇每個記錄錨點時,請使用 TimeToLiveAnchor.CREATED_AT 來計算 Oracle 儲存資料列時的來源,或改用 TimeToLiveAnchor.TIMESTAMP 來計算記錄的事件時戳。

備註:當記錄應在定義的期間內保持可用狀態,然後自動到期時,請使用「存留時間」。例如,應用程式可能會保留 30 天的支援詳細資料,或保留一週的臨時任務資訊。

適用下列保留規則:

提示:如需設定套裝程式,請參閱開始使用代理程式記憶體。如果此範例需要本機 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_daysttl_anchor,否則請記住,更新會保留目前的到期時間。

API 參照:OracleThread

確定受管理的永久清除工作存在

當 Oracle Agent Memory 建立或升級其受管理的 Oracle 綱要時,也會建立每日 DBMS_SCHEDULER 工作,以刪除過期的訊息和記憶體資料列及其擷取區塊。工作會以批次方式永久清除資料列,而不是發出一個大型刪除、在批次之間確認,以及在前面擷取一個參照時戳,讓單一執行使用一個一致的到期截止。

排程器定義也會將 schedule_limit 設為一天,因此可以略過開始太晚的執行,而不是任意延遲執行。工作主體會獨立停止從執行開始時間算起一天後執行新批次。該執行時期限制會在批次之間檢查,因此允許已進行中的批次完成並確認。在執行結束時,工作會寫入簡短的 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;

如果您想要立即驗證安裝,請手動執行工作一次:

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