Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.maximem.ai/llms.txt

Use this file to discover all available pages before exploring further.

Give an OpenAI Agent persistent, per-user memory by exposing two function tools: one that searches Synap and one that stores new memories. The agent decides when to call each — no hidden injection, no chain modifications.

Overview

This guide shows how to add Synap to an OpenAI Agents SDK application to build agents that:
  • Recall user-specific facts, preferences, and past conversations
  • Persist new information surfaced during a conversation for future runs
  • Stay fully in control of when memory is queried or written
The Synap OpenAI Agents integration ships two factory functions — each returns an async callable that fits directly into a FunctionTool.
ExportReturnsPurpose
create_search_toolasync (query, max_results) -> list[dict]Function tool that searches Synap memory
create_store_toolasync (content, memory_type) -> dictFunction tool that stores a memory in Synap

Setup

Install the package alongside the OpenAI Agents SDK:
pip install maximem-synap-openai-agents openai-agents
Configure your API key. Generate one from the Synap Dashboard.
.env
SYNAP_API_KEY=synap_your_key_here
OPENAI_API_KEY=your-openai-api-key
Initialize the SDK once at application startup:
from maximem_synap import MaximemSynapSDK

sdk = MaximemSynapSDK()
await sdk.initialize()
See SDK Initialization for the full lifecycle and configuration options.

Basic integration

The smallest useful integration registers both tools on an agent and runs a query. The agent reads its tool descriptions, decides whether to search or store, and calls the matching function:
from agents import Agent, FunctionTool, Runner
from synap_openai_agents import create_search_tool, create_store_tool

search_fn = create_search_tool(sdk=sdk, user_id="alice", customer_id="acme")
store_fn = create_store_tool(sdk=sdk, user_id="alice", customer_id="acme")

agent = Agent(
    name="Memory Agent",
    instructions=(
        "Use synap_search to recall facts about the user. "
        "Use synap_store to remember new information they share."
    ),
    tools=[
        FunctionTool(search_fn, name_override="synap_search"),
        FunctionTool(store_fn, name_override="synap_store"),
    ],
)

result = await Runner.run(agent, "What do you know about my project deadlines?")
print(result.final_output)
The scoping triple (user_id, optional customer_id) is bound when you construct the tool — the agent only ever sees the query and content parameters, never the user identity. This keeps the model from leaking or spoofing user IDs.

Core concepts

Search tool

create_search_tool returns an async callable that takes a natural-language query and returns a list of memory objects.
from synap_openai_agents import create_search_tool

search_fn = create_search_tool(
    sdk=sdk,
    user_id="alice",
    customer_id="acme",   # optional — required for B2B instances
)
Tool signature exposed to the model:
synap_search(query: str, max_results: int = 5) -> list[dict]
Each result has the shape {"content": "...", "type": "fact", "confidence": 0.91}. The agent sees the JSON list and can quote or reason over the entries directly. Search failures degrade gracefully — the tool returns an empty list and logs an error, so the agent continues without recall rather than aborting.

Store tool

create_store_tool returns a companion callable that ingests a new memory.
from synap_openai_agents import create_store_tool

store_fn = create_store_tool(
    sdk=sdk,
    user_id="alice",
    customer_id="acme",
)
Tool signature exposed to the model:
synap_store(content: str, memory_type: str = "fact") -> dict
Returns {"status": "stored", "id": "..."} on success. Store failures surface explicitly — the tool raises SynapIntegrationError so the agent (and you) know if persistence failed.

Complete example: assistant with explicit memory control

The following agent is told to consult its memory before answering and store anything the user shares. The Synap calls only happen when the model elects to invoke the tools:
from agents import Agent, FunctionTool, Runner
from synap_openai_agents import create_search_tool, create_store_tool


def build_memory_agent(sdk, user_id: str, customer_id: str | None = None) -> Agent:
    search_fn = create_search_tool(sdk=sdk, user_id=user_id, customer_id=customer_id)
    store_fn = create_store_tool(sdk=sdk, user_id=user_id, customer_id=customer_id)

    return Agent(
        name="Personal Assistant",
        instructions=(
            "You are a personal assistant with long-term memory.\n"
            "1. Always call synap_search FIRST for any question about the user, "
            "their preferences, or past conversations.\n"
            "2. When the user shares a new fact, preference, or decision, "
            "call synap_store to remember it.\n"
            "3. Never fabricate facts — if synap_search returns nothing, "
            "say you don't know yet."
        ),
        tools=[
            FunctionTool(search_fn, name_override="synap_search"),
            FunctionTool(store_fn, name_override="synap_store"),
        ],
    )


# Usage
agent = build_memory_agent(sdk, user_id="alice", customer_id="acme")

# First conversation — agent stores a fact
await Runner.run(agent, "I just upgraded to the Pro plan.")

# Later conversation — agent recalls it
result = await Runner.run(agent, "What plan am I on?")
print(result.final_output)  # → "You're on the Pro plan."
Three things to notice in this pattern:
  1. The agent owns the memory loop. Search and store calls are model-driven, not framework-driven — the prompt steers the behavior.
  2. Scope is bound at construction. The model never sees user_id or customer_id, so even a prompt-injection attempt cannot make it query memories for someone else.
  3. Failure modes split. Search is best-effort (empty list on failure); store is strict (raises on failure) so silent data loss is impossible.

Advanced patterns

Multi-tenant scoping

Both factories accept the same scoping triple — user_id (required), optional customer_id. customer_id is required on B2B Synap instances and ignored on single-tenant ones. See Memory Scopes.
# User-scoped only
search_fn = create_search_tool(sdk=sdk, user_id="alice")

# Organization-scoped (user sees org-shared memories too)
search_fn = create_search_tool(sdk=sdk, user_id="alice", customer_id="acme-corp")

Per-request scoping

If your service handles many users in one process, build a fresh pair of tools per request rather than caching them — each agent run should have its scope baked in to prevent cross-user leakage:
def per_request_agent(sdk, user_id: str, customer_id: str | None = None):
    return build_memory_agent(sdk, user_id, customer_id)

Failure semantics

The integration follows the Synap-wide contract:
  • Search failures degrade gracefullysynap_search returns [] and logs an error so the agent can continue.
  • Store failures surface explicitlysynap_store raises SynapIntegrationError so the agent (and caller) know persistence failed.
This is by design: a transient outage shouldn’t break a user-facing answer, but a silent write failure would let memory drift away from reality.

Next steps

Pydantic AI

Type-safe deps and tools for Pydantic AI agents.

AutoGen

BaseTool implementations for AutoGen agents.

Context Fetch

The retrieval API that powers synap_search — modes, scopes, and response shapes.

Memory Scopes

How user_id and customer_id interact across reads and writes.