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.

Add persistent, per-user memory to a Google ADK agent in one factory call. Synap exposes itself as two FunctionTool instances — the agent decides when to recall or remember.

Overview

This guide shows how to add Synap to a Google ADK application to build agents that:
  • Recall user-specific facts, preferences, and past conversations
  • Persist new information surfaced during a conversation
  • Stay multi-user-safe by binding scope at tool construction
The Synap Google ADK integration ships a single factory — it returns the two FunctionTool instances ready to drop onto an Agent.
ExportReturnsPurpose
create_synap_tools[search_memory, store_memory]Two ADK FunctionTool instances for memory recall and storage

Setup

Install the package alongside the Google ADK:
pip install maximem-synap-google-adk google-adk
Configure your API key. Generate one from the Synap Dashboard.
.env
SYNAP_API_KEY=synap_your_key_here
GOOGLE_API_KEY=your-google-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 calls create_synap_tools and passes the result straight into an Agent. The factory returns a list, so it fits the tools= parameter without unpacking:
from google.adk.agents import Agent
from synap_google_adk import create_synap_tools

tools = create_synap_tools(
    sdk=sdk,
    user_id="alice",
    customer_id="acme",   # optional — required for B2B instances
)

agent = Agent(
    name="MemoryAgent",
    model="gemini-2.0-flash",
    instruction=(
        "Use the synap_search tool to recall context about the user. "
        "Use synap_store to remember new facts."
    ),
    tools=tools,
)
The factory binds user_id and customer_id into the closures it returns — the model only ever sees query, max_results, and content, never the user identity. This prevents prompt-injection attempts from spoofing scope.

Core concepts

create_synap_tools

create_synap_tools returns a two-element list: [search_memory, store_memory]. Both are ADK FunctionTool instances and can be passed directly to Agent(tools=...):
from synap_google_adk import create_synap_tools

tools = create_synap_tools(
    sdk=sdk,
    user_id="alice",
    customer_id="acme",
)
# tools[0] = search_memory
# tools[1] = store_memory

search_memory

Tool signature exposed to the model:
search_memory(query: str, max_results: int = 5) -> list[dict]
Returns a list of memory objects with the shape {"content": "...", "type": "...", "confidence": float}. The agent sees this as JSON and can reason over it directly. Search failures degrade gracefully — the tool returns [] and logs an error so the agent continues without recall rather than aborting.

store_memory

Tool signature exposed to the model:
store_memory(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: per-user memory agent

In a multi-user service, build a fresh tool set per request. Each agent run gets a scope-bound tool list, so two concurrent users cannot see each other’s memories:
from google.adk.agents import Agent
from google.adk.runners import Runner
from synap_google_adk import create_synap_tools


def build_agent_for_user(sdk, user_id: str, customer_id: str | None = None) -> Agent:
    tools = create_synap_tools(sdk=sdk, user_id=user_id, customer_id=customer_id)

    return Agent(
        name="MemoryAgent",
        model="gemini-2.0-flash",
        instruction=(
            "You are a personal assistant with long-term memory.\n"
            "1. Always call search_memory FIRST for any question about the user.\n"
            "2. When the user shares a fact, preference, or decision, "
            "call store_memory before responding.\n"
            "3. If search_memory returns nothing, say so honestly."
        ),
        tools=tools,
    )


async def handle_request(sdk, user_id: str, message: str) -> str:
    agent = build_agent_for_user(sdk, user_id=user_id, customer_id="acme")
    runner = Runner(agent=agent)
    return await runner.run_async(message)


# Usage
await handle_request(sdk, user_id="alice", message="I just upgraded to the Pro plan.")
# Later:
reply = await handle_request(sdk, user_id="alice", message="What plan am I on?")
# → "You're on the Pro plan."
Three things to notice in this pattern:
  1. Per-request agent construction is the safety pattern. Each request gets its own scope-bound tool list — no shared state can leak between users.
  2. The instruction is the policy. Telling the model to always call search_memory first is what produces recall behavior; the tools are just plumbing.
  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

create_synap_tools accepts the standard scoping triple — user_id (required), optional customer_id, optional conversation_id. customer_id is required on B2B Synap instances and ignored on single-tenant ones. See Memory Scopes.
# User-scoped only
tools = create_synap_tools(sdk=sdk, user_id="alice")

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

Multi-agent setups

Each Agent can carry its own tool list. Give a “memory-keeper” agent both tools and a “consumer” agent only search_memory to keep the write surface small:
[search, _store] = create_synap_tools(sdk=sdk, user_id="alice", customer_id="acme")
reader_only = Agent(name="Reader", model="gemini-2.0-flash", tools=[search])

Failure semantics

The integration follows the Synap-wide contract:
  • search_memory degrades gracefully — returns [] and logs an error if Synap is unreachable.
  • store_memory surfaces failures — raises SynapIntegrationError so the agent and caller know persistence failed.
This is by design: read failures shouldn’t break a user-facing answer, but silent write failures would let the memory drift away from reality.

Next steps

Haystack

Pipeline components for Haystack.

Agno

InMemoryDb replacement for Agno agents.

Context Fetch

The retrieval API behind search_memory — modes, scopes, and response shapes.

Memory Scopes

How user_id and customer_id interact across reads and writes.