Stores and Schema

This page presents the core store abstractions and schema controls used by the Oracle Agent Memory SDK.

Store API

Store Write Semantics

Store writes keep a clear separation between the text an application stores and the payload the store uses for retrieval. Most applications can use the memory-level and thread-level APIs and let the store prepare the search rows it needs for vector, keyword, or hybrid retrieval. The lower-level store APIs expose index_texts, index_text, embeddings, and embedding for advanced integrations that already know what text or vectors should be used for retrieval.

Think of each write as two related pieces:

If no search override or explicit embedding is provided, the store uses the resolved stored text as the retrieval text. Non-empty text is chunked by the store when chunking is configured. Empty text stores the record text but provides no retrieval text.

The table below describes how retrieval text is chosen before explicit vector payloads are considered.

Store-level retrieval payloads

Input add() update()
index_texts or index_text omitted Each record uses its resolved contents value for retrieval. A replacement text value is used for retrieval. If text is also omitted, embedding-only updates reuse the record’s existing retrieval text rows.
String index_texts entry or string index_text The string replaces the retrieval text for that record. The store may chunk it before writing retrieval rows. The string replaces the retrieval text for that record. The store may chunk it before writing retrieval rows.
list[str] index_texts entry or list[str] index_text The list is treated as caller-owned chunks. Each non-empty string is written as one retrieval row, and the store does not chunk it again. The list is treated as caller-owned chunks. Each non-empty string is written as one retrieval row, and the store does not chunk it again.
None index_texts entry or index_text=None None in the outer index_texts list means “use the stored content for this record.” index_text=None clears the retrieval rows while leaving stored text unchanged unless text is also supplied.
Empty string or empty chunk list Stores the record text and provides no retrieval text for that record. Updates the record text when text is supplied and clears retrieval text for that record.

Explicit embeddings are optional. When they are omitted, the store derives local vectors from the retrieval text when local vector storage is configured; keyword or hybrid stores may also use text-only retrieval rows. When explicit embeddings or embedding values are supplied, the store writes those vectors directly and does not call its embedder for those vectors.

In add(), embeddings=None behaves like omitting embeddings. In update(), embedding=None is explicit: the store keeps or rewrites the retrieval text according to text and index_text, but stores those rows without local vectors. If text and index_text are both omitted, this clears vectors from the existing retrieval rows.

The vector shape tells the store how much chunk ownership the caller is taking:

Some combinations are rejected so stored text, retrieval text, and vectors do not drift apart. Passing text=None clears stored text and retrieval rows, so it cannot be combined with non-null index_text or embedding values; actor profile records do not support text=None. Passing index_text=None in update() means “clear retrieval rows”, so non-empty explicit embeddings are not allowed in the same call. Multiple explicit vectors require explicit chunk text, unless the update is embedding-only and the existing retrieval rows already provide the chunk text.

class oracleagentmemory.core.OracleMemoryStore

Bases: IMemoryStore

Common store interface used by OracleAgentMemory.

A store implementation is responsible for persisting text records and performing similarity search over them. Both synchronous and asynchronous entry points are defined so higher-level APIs can expose matching sync/async surfaces without duplicating store-specific logic.

method add

Add records to the store.

Notes

Use add_batches() when the caller already has one or more PendingRecordBatch objects.

method add_agent (abstract)

Add an agent profile record.

method add_async (async)

Asynchronously add row-oriented records to the store.

Accepts the same arguments and returns the same identifiers as add().

method add_batches

Add caller-prepared logical batches to the store.

Examples

store.add_batches(
    [
        PendingRecordBatch(
            texts=["pizza batch"],
            record_type="memory",
            record_ids="mem-batch-docs",
        )
    ]
)
['mem-batch-docs']

method add_batches_async (async)

Asynchronously add caller-prepared logical batches to the store.

Accepts the same arguments and returns the same identifiers as add_batches().

method add_user (abstract)

Add a user profile record.

method delete (abstract)

Delete one stored record by identifier.

method delete_thread (abstract)

Delete a thread and its associated stored data.

Notes

This is the store-level operation for removing a thread and the thread-scoped records managed by the store. Prefer thread deletion when retention requirements call for deleting both source messages and derived thread-scoped memory data, because message-level deletes do not imply that separately persisted derived records are removed.

method get (abstract)

Retrieve one stored record by type and identifier.

method list (abstract)

List stored records for one record type.

method list_thread_messages (abstract)

List the message history stored for one thread.

method search (abstract)

Search records by similarity.

Examples

store.add(
    ["Searchable abstract memory"],
    record_type="memory",
    record_ids="mem-search-abstract-docs",
)
['mem-search-abstract-docs']
store.search("Searchable", 1, record_types={"memory"})[0][0].id
'mem-search-abstract-docs'

Filter on a scalar metadata value:

store.add(
    ["pizza release"],
    record_type="memory",
    record_ids="mem-search-meta-source-docs",
    metadata={"source": "slack"},
)
['mem-search-meta-source-docs']
any(
    record.id == "mem-search-meta-source-docs"
    for record, _ in store.search(
        "pizza",
        k=3,
        metadata_filter={"source": "slack"},
    )
)
True

Filter on nested metadata:

store.add(
    ["pizza review"],
    record_type="memory",
    record_ids="mem-search-meta-review-docs",
    metadata={"review": {"status": "open"}},
)
['mem-search-meta-review-docs']
any(
    record.id == "mem-search-meta-review-docs"
    for record, _ in store.search(
        "pizza",
        k=3,
        metadata_filter={"review": {"status": "open"}},
    )
)
True

Match a list value exactly, including order:

store.add(
    ["pizza tags"],
    record_type="memory",
    record_ids="mem-search-meta-tags-docs",
    metadata={"tags": ["prod", "urgent"]},
)
['mem-search-meta-tags-docs']
any(
    record.id == "mem-search-meta-tags-docs"
    for record, _ in store.search(
        "pizza",
        k=3,
        metadata_filter={"tags": ["prod", "urgent"]},
    )
)
True

Filter when a metadata array contains a value:

any(
    record.id == "mem-search-meta-tags-docs"
    for record, _ in store.search(
        "pizza",
        k=3,
        metadata_filter={"tags": {"$array_contains": "prod"}},
    )
)
True

Combine multiple metadata conditions. A record must satisfy every key:

store.add(
    ["pizza rollout"],
    record_type="memory",
    record_ids="mem-search-meta-combined-docs",
    metadata={
        "source": "slack",
        "review": {"status": "open"},
        "tags": ["prod", "urgent"],
    },
)
['mem-search-meta-combined-docs']
any(
    record.id == "mem-search-meta-combined-docs"
    for record, _ in store.search(
        "pizza",
        k=5,
        metadata_filter={
            "source": "slack",
            "review": {"status": "open"},
            "tags": ["prod", "urgent"],
        },
    )
)
True

method search_async (async)

Asynchronously search records by semantic similarity.

method update (abstract)

Update stored record content, embedding data, metadata, timestamp, or expiration.

Oracle DB Store

class oracleagentmemory.core.OracleDBMemoryStore

Bases: OracleMemoryStore

Database-backed persistence for messages, memories, and actor profiles.

Create an Oracle DB store.

Warning: SchemaPolicy.CREATE_IF_NECESSARY can be more expensive than normal store startup because it may apply managed schema DDL and best-effort data rewrites before initialization succeeds. Plan the first open of an older managed schema as a migration or maintenance operation when that schema may contain many rows.

If schema setup must create the managed expired-record purge job but the database user lacks the scheduler-job privilege, initialization warns and continues. Expired messages and memories stay hidden from reads and search, but they are not physically purged until the job is created by a user with CREATE JOB or an equivalent scheduler privilege.

When SchemaPolicy.CREATE_IF_NECESSARY first creates a managed hybrid index over an existing schema, Oracle scans the stored search text and builds the managed hybrid-index state from the configured in-database model. Store initialization waits for that DDL to finish, so plan the first hybrid upgrade as a migration or maintenance operation for large schemas. SearchIndexSyncMode controls ongoing maintenance after the index exists; it does not make the first index build asynchronous.

Creating that managed hybrid index also creates a DBMS_VECTOR_CHAIN vectorizer preference named by the managed schema. The preference stores lightweight vectorizer configuration metadata from the configured OracleDBEmbedder model. It can be inspected with Oracle Text preference views such as CTX_USER_PREFERENCES and CTX_USER_PREFERENCE_VALUES.

method add

Add records to the Oracle DB store.

Examples

store.add(
    ["Index this stored text"],
    record_type="memory",
    record_ids="mem-db-add-docs",
)
['mem-db-add-docs']
store.add(
    ["Stored text"],
    record_type="memory",
    index_texts=["Search this text"],
    record_ids="mem-db-index-text-docs",
)
['mem-db-index-text-docs']
store.add(
    ["Short-lived event"],
    record_type="memory",
    record_ids="mem-db-ttl-docs",
    timestamps="2026-01-01T12:00:00+00:00",
    ttl_days=7,
    ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)
['mem-db-ttl-docs']

method add_agent

Add an agent profile record.

Notes

Agent profile records are unscoped. The inserted public record identifier is the same value passed as agent_id.

Examples

store.add_agent("a-docs-agent", "Support assistant")
'a-docs-agent'

method add_async (async)

Asynchronously add row-oriented records to the store.

Accepts the same arguments and returns the same identifiers as add().

method add_batches

Add caller-prepared logical batches to the store.

Examples

store.add_batches(
    [
        PendingRecordBatch(
            texts=["pizza batch"],
            record_type="memory",
            record_ids="mem-batch-docs",
        )
    ]
)
['mem-batch-docs']

method add_batches_async (async)

Asynchronously add caller-prepared logical batches to the store.

Accepts the same arguments and returns the same identifiers as add_batches().

method add_user

Add a user profile record.

Notes

User profile records are unscoped. The inserted public record identifier is the same value passed as user_id.

Examples

store.add_user("u-docs-profile", "Prefers concise answers.")
'u-docs-profile'

method delete

Delete one managed row and its chunk rows by identifier.

Notes

The operation runs inside one transaction. When cascade is enabled for a supported top-level target, the profile delete and all scoped child deletes are committed or rolled back together.

Examples

store.add(["Delete me"], record_type="memory", record_ids="mem-delete-docs")
['mem-delete-docs']
store.delete("memory", "mem-delete-docs")
1

method delete_thread

Delete a thread and its associated stored rows.

Notes

Use this operation when you need thread-scoped cascading cleanup. In the DB-backed store, deleting the thread removes the managed thread row together with associated message and memory rows, plus the search data maintained for retrieval. This is broader than a message-level delete, which removes only the raw message row. Thread deletion removes dependent message and memory rows together with their associated retrieval data in the same transaction.

Examples

store.delete_thread("c1")
0

method get

Retrieve a stored record by identifier.

Examples

store.add(["Remember this"], record_type="memory", record_ids="mem-get-docs")
['mem-get-docs']
store.get("memory", "mem-get-docs").id
'mem-get-docs'

method list

Enumerate persisted records for a record type.

Notes

"user_profile" and "agent_profile" are unscoped record types. For those record types, thread_id, user_id, and agent_id are ignored and actor identity remains in record.id.

Examples

store.add(
    ["First listed", "Second listed"],
    record_type="memory",
    record_ids=["mem-list-docs-1", "mem-list-docs-2"],
)
['mem-list-docs-1', 'mem-list-docs-2']
[record.id for record in store.list("memory", limit=2)]
['mem-list-docs-1', 'mem-list-docs-2']
store.add_user("u-list-docs", "Prefers concise answers.")
'u-list-docs'
any(
    record.id == "u-list-docs"
    for record in store.list("user_profile", user_id=None, limit=10)
)
True

method list_thread_messages

Return persisted messages for a thread.

Examples

store.list_thread_messages("c1")
[]

Search records by similarity.

The active search backend depends on the store’s configured SearchStrategy. SearchStrategy.VECTOR ranks the query vector against stored record vectors. SearchStrategy.HYBRID queries Oracle’s managed hybrid index over the stored search text and its managed index state. SearchStrategy.KEYWORD ranks only by text matching over the stored search text.

Examples

store.add(
    ["pizza preference"],
    record_type="memory",
    record_ids="mem-search-docs",
    thread_ids="c-search-docs",
)
['mem-search-docs']
results = store.search(
    "pizza",
    1,
    thread_id="c-search-docs",
    exact_thread_match=True,
    record_types={"memory"},
)
results[0][0].id
'mem-search-docs'

Filter on a scalar metadata value:

store.add(
    ["pizza release"],
    record_type="memory",
    record_ids="mem-search-meta-source-docs",
    metadata={"source": "slack"},
)
['mem-search-meta-source-docs']
any(
    record.id == "mem-search-meta-source-docs"
    for record, _ in store.search(
        "pizza",
        k=3,
        metadata_filter={"source": "slack"},
    )
)
True

Filter on nested metadata:

store.add(
    ["pizza review"],
    record_type="memory",
    record_ids="mem-search-meta-review-docs",
    metadata={"review": {"status": "open"}},
)
['mem-search-meta-review-docs']
any(
    record.id == "mem-search-meta-review-docs"
    for record, _ in store.search(
        "pizza",
        k=3,
        metadata_filter={"review": {"status": "open"}},
    )
)
True

Match a list value exactly, including order:

store.add(
    ["pizza tags"],
    record_type="memory",
    record_ids="mem-search-meta-tags-docs",
    metadata={"tags": ["prod", "urgent"]},
)
['mem-search-meta-tags-docs']
any(
    record.id == "mem-search-meta-tags-docs"
    for record, _ in store.search(
        "pizza",
        k=3,
        metadata_filter={"tags": ["prod", "urgent"]},
    )
)
True

Filter when a metadata array contains a value:

any(
    record.id == "mem-search-meta-tags-docs"
    for record, _ in store.search(
        "pizza",
        k=3,
        metadata_filter={"tags": {"$array_contains": "prod"}},
    )
)
True

Combine multiple metadata conditions. A record must satisfy every key:

store.add(
    ["pizza rollout"],
    record_type="memory",
    record_ids="mem-search-meta-combined-docs",
    metadata={
        "source": "slack",
        "review": {"status": "open"},
        "tags": ["prod", "urgent"],
    },
)
['mem-search-meta-combined-docs']
any(
    record.id == "mem-search-meta-combined-docs"
    for record, _ in store.search(
        "pizza",
        k=5,
        metadata_filter={
            "source": "slack",
            "review": {"status": "open"},
            "tags": ["prod", "urgent"],
        },
    )
)
True

method search_async (async)

Asynchronously search records by semantic similarity.

method update

Update stored record content, search state, metadata, and timestamp values.

Examples

store.add(["Original note"], record_type="memory", record_ids="mem-update-docs")
['mem-update-docs']
store.update("memory", "mem-update-docs", text="Updated note")
1
store.get("memory", "mem-update-docs").content
'Updated note'

Search Strategy

class oracleagentmemory.core.dbsearch.SearchStrategy

Bases: Enum

Search behavior for Oracle DB stores.

DB store initialization uses the selected strategy to choose the managed schema search capability. VECTOR search stores local embeddings. KEYWORD search stores searchable text and a text index. HYBRID search stores searchable text plus Oracle-managed hybrid vector-index state. The DB store validates this schema capability at startup so an incompatible strategy does not silently return incomplete results.

VECTOR
Search by vector similarity only. The store embeds the query with the configured embedder, or uses a caller-provided query_vector, and ranks records by distance from the stored vectors. Use this with a DB schema configured for vector search.
HYBRID
Search with Oracle’s managed hybrid index. Oracle combines text matching over stored search text with vector ranking from the in-database hybrid index. Use this when users may search by natural language as well as exact identifiers, aliases, or product names. This strategy requires the store’s main embedder to be an OracleDBEmbedder so the managed index and store share one in-database model.
KEYWORD
Search only by keyword/text matching over stored search text. This mode does not create local query embeddings and does not need an Oracle DB embedder. When opened against an existing hybrid schema, it can use the text branch of that hybrid index without creating a new hybrid index. Use this when exact identifiers, aliases, product names, or short phrases should drive retrieval without vector fusion.

HYBRID = ‘hybrid’

KEYWORD = ‘keyword’

VECTOR = ‘vector’

Search Index Sync Mode

class oracleagentmemory.core.dbsearch.SearchIndexSyncMode

Bases: Enum

Refresh behavior for managed DB search indexes.

This setting controls when Oracle makes new or changed search text visible to DB-backed text-aware search. SearchStrategy.HYBRID uses Oracle’s managed hybrid vector index. SearchStrategy.KEYWORD uses an Oracle Text index. SearchStrategy.VECTOR does not use this setting.

ON_COMMIT
Refresh the index when the write transaction commits. This is the default and the simplest choice for most applications because records are searchable immediately after a successful write. It can add work to write transactions because the index is kept up to date right away.
MANUAL
Do not refresh the index automatically. New or updated records may not appear in keyword or hybrid search until you run the database-side index sync operation yourself. This is useful for bulk loads or maintenance windows where you want to control when refresh work runs.
AUTO
Let Oracle refresh the managed hybrid index asynchronously. Writes can avoid the immediate refresh cost, but search results may lag behind recent writes until Oracle completes the background refresh. This mode is supported only with SearchStrategy.HYBRID.

Warning: This setting controls ongoing maintenance after the managed search index exists. It does not make the first index build asynchronous. Creating a managed hybrid index over existing stored search text can be long-running because Oracle builds the managed hybrid-index state from that text.

AUTO = ‘auto’

MANUAL = ‘manual’

ON_COMMIT = ‘on_commit’

Time-to-Live

class oracleagentmemory.core.retention.MemoryRetentionConfig

Bases: object

Schema-level retention settings for Oracle DB-backed records.

class oracleagentmemory.apis.ttl.TimeToLiveAnchor

Bases: Enum

Anchor used to compute an expiration timestamp from a time-to-live duration.

CREATED_AT
Compute expiration from the record’s database creation timestamp. This is the default when callers omit ttl_anchor.
TIMESTAMP
Compute expiration from the record’s stored event timestamp. Use this when a message or memory represents an older event and should expire relative to that event time instead of insert time.

CREATED_AT = ‘created_at’

TIMESTAMP = ‘timestamp’

Schema Policy

class oracleagentmemory.core.SchemaPolicy

Bases: str, Enum

Schema creation policy for Oracle DB stores.

REQUIRE_EXISTING

Validate that the full managed schema already exists and is up-to-date. Do not create or modify DB objects.

CREATE_IF_EMPTY

If no managed objects exist, bootstrap schema. If objects already exist, require a complete and up-to-date managed schema.

CREATE_IF_NECESSARY

Create missing managed objects and apply supported managed schema upgrades.

RECREATE

Drop and recreate all managed schema objects. This is destructive.