Use Oracle Agent Memory Hybrid Search

Semantic search helps find related information even when queries use different words. Keyword search helps find exact identifiers, names, and phrases that semantic search might miss.

This article explains how to use Oracle Agent Memory hybrid search with Oracle Database.

Hybrid search helps Oracle Agent Memory find records by both meaning and exact text. It combines Oracle-managed text search with vector-based ranking, so a query can match identifiers such as issue codes, product names, and aliases while still benefiting from semantic retrieval.

In this guide, you will:

Note: Use hybrid search when neither semantic search nor keyword search alone provides reliable retrieval. Hybrid search is most useful when records include both descriptive content and exact terms that users need to find.

Hint: For package setup, see Get Started with Agent Memory. If you need a local Oracle database for this example, follow Run Oracle AI Database Locally.

Configure an Oracle Hybrid Search Client

Create an Oracle Agent Memory client with hybrid search enabled. The client stores records in Oracle Database and asks Oracle to use a managed hybrid vector index for search.

Use SchemaPolicy.CREATE_IF_NECESSARY when you first enable hybrid search. This lets the SDK add the managed search columns and create the hybrid index if they do not already exist.

Use SearchStrategy.HYBRID when you want Oracle to combine keyword and vector ranking. Hybrid search requires an OracleDBEmbedder as the client’s main embedder so the store and Oracle-managed hybrid vector index share the same in-database model.

If you want text matching only, use SearchStrategy.KEYWORD instead. The next section shows that setup.

Important: This guide requires oracleagentmemory 26.6.0 or newer. If your managed schema was created by an older release, start the application once with SchemaPolicy.CREATE_IF_NECESSARY. This upgrades supported older released schemas in place and adds the search objects required by the selected strategy. That first upgrade may perform managed DDL and data rewrites, so treat it as a planned migration step instead of a cheap normal startup. After the schema is ready, you can switch back to SchemaPolicy.REQUIRE_EXISTING for normal startup.

Development or partially updated schemas that already claim the current release shape are not completed in place; recreate them instead.

If you use SchemaPolicy.REQUIRE_EXISTING before the schema is upgraded, client creation fails and tells you which managed search objects are missing.

After a schema is initialized or upgraded for keyword or hybrid search, do not reopen it with SearchStrategy.VECTOR unless you recreate the schema or run an embedding backfill. Rows written by keyword or hybrid clients may not contain local embeddings, so vector search could otherwise miss records.

Warning: Creating the hybrid index for an existing schema can take time. Oracle must scan existing stored search text and build the managed hybrid-index text/vector state from the configured in-database model. SchemaPolicy.CREATE_IF_NECESSARY waits for that DDL to finish before client startup returns, so run the first hybrid upgrade as a planned migration or maintenance operation for large schemas.

Note: When the SDK creates a managed hybrid vector index, it also creates a DBMS_VECTOR_CHAIN vectorizer preference named by the managed schema. The preference stores the vectorizer configuration from the configured OracleDBEmbedder model and is expected to be lightweight metadata. The generated preference name is deterministic for the managed schema and follows the configured table-name prefix when it fits Oracle Text’s preference-name limit. You can inspect preferences with Oracle Text views such as CTX_USER_PREFERENCES and CTX_USER_PREFERENCE_VALUES.

Note: This example uses OracleDBEmbedder. Oracle uses that embedder’s in-database model when it creates the hybrid index, so the main embedder and the hybrid index share one DB model.

The example also uses SearchIndexSyncMode.ON_COMMIT. This keeps the hybrid index current after each committed write, so newly added memories are searchable immediately. Use SearchIndexSyncMode.MANUAL when you plan to run search-index sync yourself, or SearchIndexSyncMode.AUTO when you want Oracle to refresh the hybrid index in the background and can tolerate search results lagging behind recent writes. This setting applies to ongoing maintenance after the initial search index exists; it does not make the first index build asynchronous.

import oracledb

from oracleagentmemory.core import (
    MemoryExtractionConfig,
    OracleAgentMemory,
    SchemaPolicy,
    SearchIndexSyncMode,
    SearchStrategy,
)
from oracleagentmemory.core.embedders import OracleDBEmbedder


db_pool = oracledb.SessionPool(
    user="YOUR DB USER",
    password="YOUR DB PASSWORD",
    dsn="YOUR DB CONNECT STRING",
)

db_embedder = OracleDBEmbedder(
    connection=db_pool,
    model="YOUR_DB_EMBEDDING_MODEL",
    embedding_dimension=384,
)

memory = OracleAgentMemory(
    connection=db_pool,
    embedder=db_embedder,
    memory_extraction_config=MemoryExtractionConfig(extract_memories=False),
    schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
    search_strategy=SearchStrategy.HYBRID,
    search_index_sync=SearchIndexSyncMode.ON_COMMIT,
)
API Reference: OracleAgentMemory OracleDBEmbedder SchemaPolicy SearchStrategy SearchIndexSyncMode

Use Keyword Search Only

Use SearchStrategy.KEYWORD when you want search to match stored words, identifiers, aliases, product names, or short phrases, and you do not want vector ranking. Keyword search reads the same stored search text used by hybrid search, but it does not use vector fusion.

Keyword search does not need a Python embedder. If the SDK needs to create search objects for a new keyword schema, it creates an Oracle Text index over stored search text. SearchIndexSyncMode.ON_COMMIT and SearchIndexSyncMode.MANUAL control keyword text-index refresh.

keyword_memory = OracleAgentMemory(
    connection=db_pool,
    memory_extraction_config=MemoryExtractionConfig(extract_memories=False),
    schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
    search_strategy=SearchStrategy.KEYWORD,
)

The same add_memory() and search() calls shown below work with SearchStrategy.KEYWORD. The difference is ranking: keyword search returns records based on text matching only, while hybrid search combines text matching with vector ranking.

API Reference: OracleAgentMemory SearchStrategy

Use Compatible Search Strategies

The first time the SDK creates or upgrades the managed schema, it records how stored retrieval data is prepared for search. The recorded mode is checked every time a DB client starts. This prevents a client from using a strategy that could silently miss records.

If the requested strategy is compatible with the schema but does not match the recorded mode, the SDK emits a warning. The client can still start, but the warning helps you notice the difference and confirm that it is intentional.

Warning: The first creation of the hybrid index over existing data can be long-running because Oracle builds the managed hybrid-index state from stored search text. SearchIndexSyncMode controls ongoing index maintenance after the index exists; it does not make the first build asynchronous.

Use SchemaPolicy.CREATE_IF_NECESSARY when you intentionally move a schema from vector to keyword or hybrid, or from keyword to hybrid. Use SchemaPolicy.REQUIRE_EXISTING after the schema is ready and you want startup to validate the schema without changing database objects.

API Reference: SchemaPolicy SearchStrategy

Store a Searchable Memory

Start with a normal memory. With the OracleAgentMemory.add_memory() API, Oracle uses the memory content itself as the searchable text. This is the simplest path and is usually enough when users search for words or phrases that already appear in the memory.

user_id = "user_123"

memory.add_memory(
    "The user calls the Milan outage the renewal blocker.",
    user_id=user_id,
)

API Reference: OracleAgentMemory

Store Identifiers Users May Search For

When users may search by an exact issue code, invoice number, product name, or alias, include that text in the memory you store. Hybrid search can then match the exact text and still use vector ranking for natural-language queries.

Keep the memory readable. A short sentence that includes the important identifier is often enough. In this example, the memories include issue and request identifiers that users are likely to type later.

memory.add_memory(
    "The follow-up fix for the Milan outage is the edge-cluster retry patch, "
    "tracked as ORA-27102.",
    user_id=user_id,
)

memory.add_memory(
    "The renewal request INV-48291 stays attached to the Milan blocker ORA-27102.",
    user_id=user_id,
)

API Reference: OracleAgentMemory

Search by Identifier or Phrase

Use the normal search() API. Hybrid search runs behind the same Oracle Agent Memory search interface, so you can keep using scopes, result limits, and record type filters as usual.

The first query searches for an exact issue code stored in the memory text. The second query searches for a natural-language phrase from the same set of memories.

for query in ["ORA-27102", "renewal blocker"]:
    print(f"\nSearch results for {query!r}:")
    results = memory.search(
        query=query,
        user_id=user_id,
        max_results=5,
        record_types=["memory"],
    )
    print_search_results(results)

Note: Search result order can vary with your stored data, hybrid-index configuration, and Oracle scoring behavior. In tests or demos, check that the expected records are returned rather than relying on one exact ranking.

API Reference: OracleAgentMemory OracleSearchResult

Conclusion

In this guide, you enabled Oracle-managed hybrid search, saw when to use keyword-only search, stored a regular memory, included searchable identifiers in memory text, and queried memories by both exact identifier and natural-language phrase.

After learning how to enable hybrid search in Oracle DB, you may now proceed to Stores and Schema.

Full Code

#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 - Oracle Hybrid Search
#-------------------------------------------------------

##Configure an Oracle hybrid search client

import oracledb

from oracleagentmemory.core import (
    MemoryExtractionConfig,
    OracleAgentMemory,
    SchemaPolicy,
    SearchIndexSyncMode,
    SearchStrategy,
)
from oracleagentmemory.core.embedders import OracleDBEmbedder


db_pool = oracledb.SessionPool(
    user="YOUR DB USER",
    password="YOUR DB PASSWORD",
    dsn="YOUR DB CONNECT STRING",
)

db_embedder = OracleDBEmbedder(
    connection=db_pool,
    model="YOUR_DB_EMBEDDING_MODEL",
    embedding_dimension=384,
)

memory = OracleAgentMemory(
    connection=db_pool,
    embedder=db_embedder,
    memory_extraction_config=MemoryExtractionConfig(extract_memories=False),
    schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
    search_strategy=SearchStrategy.HYBRID,
    search_index_sync=SearchIndexSyncMode.ON_COMMIT,
)



##Configure an Oracle keyword search client

keyword_memory = OracleAgentMemory(
    connection=db_pool,
    memory_extraction_config=MemoryExtractionConfig(extract_memories=False),
    schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
    search_strategy=SearchStrategy.KEYWORD,
)


def print_search_results(results: list) -> None:
    for result in results:
        print(f"- [{result.record.record_type}] {result.content}")



##Store a basic hybrid search memory

user_id = "user_123"

memory.add_memory(
    "The user calls the Milan outage the renewal blocker.",
    user_id=user_id,
)



##Store identifiers users may search for

memory.add_memory(
    "The follow-up fix for the Milan outage is the edge-cluster retry patch, "
    "tracked as ORA-27102.",
    user_id=user_id,
)

memory.add_memory(
    "The renewal request INV-48291 stays attached to the Milan blocker ORA-27102.",
    user_id=user_id,
)



##Search hybrid records by identifier or phrase

for query in ["ORA-27102", "renewal blocker"]:
    print(f"\nSearch results for {query!r}:")
    results = memory.search(
        query=query,
        user_id=user_id,
        max_results=5,
        record_types=["memory"],
    )
    print_search_results(results)