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.

Status: Live in Playground · Try it: synap.maximem.ai/playground Open the playground and pick Uber — Customer Support to see the reference implementation running before you build.
A rider support agent that knows who the rider is, what rides they’ve taken, how they like to be communicated with, and how their past issues were resolved. It calls tools to pull ride history, issue refunds, open lost-item reports, and hand off to humans — and it learns from every conversation.

What you’ll build

A chat agent that:
  • Recalls rider context — communication preferences, prior issues, frequent destinations
  • Grounds in real data — pulls recent rides via tools before suggesting actions
  • Resolves common issues — refunds, lost items, rating disputes, charge disputes
  • Escalates cleanly — opens a Tier-2 ticket with a memory-aware summary
Est. build time: 30–45 minutes (assuming you already have ride/refund/ticket APIs to call into).

When to use this recipe

Build this if your product:
  • Has authenticated end users with a stable internal user ID
  • Has internal APIs the agent can call (orders, accounts, tickets, refunds)
  • Wants the agent to remember resolutions so the user doesn’t re-explain on the next chat
  • Needs a deterministic escalation path to a human queue
If you only have one of those, this is still the closest starting point — strip the tools you don’t need.

Architecture at a glance

Uber rider support agent architecture diagram showing chat, backend, Synap memory fetch, LLM with tools, and turn ingestion
Memory is auto-organized into a MACA so things like “preferred contact channel” and “open lost-item report” surface as the right type at the right time. You don’t define MACA fields by hand — the SDK and use-case markdown handle it.

Stack

LayerChoice
Synap SDKmaximem-synap (Python) / @maximem/synap (TypeScript)
FrameworkOpenAI Agents SDK (Python) / Vercel AI SDK (TypeScript)
Memory adaptermaximem-synap-openai-agents / @maximem/synap-vercel-adk
LLMOpenAI gpt-4o (swap for any supported model)
ChannelYour existing chat surface (in-app, web widget, etc.)

Prerequisites

  • A Synap API key — see Authentication
  • Internal APIs for ride history, refunds, lost items, and tickets (real or stubbed for development)
  • Python recipe: Python 3.11+
  • TypeScript recipe: Node 18+ and Python 3.11+ on the host (the JS SDK wraps the Python SDK as a subprocess)
TypeScript recipe runs on Node only. Edge Runtime, Cloudflare Workers, Bun, Deno Deploy, and Lambda Node-only runtimes are not supported. Pin Next.js route handlers to export const runtime = "nodejs". See Installation → JavaScript / TypeScript SDK.

Install

pip install maximem-synap maximem-synap-openai-agents openai-agents

Configure

# .env
SYNAP_API_KEY=...
SYNAP_SERVER_URL=<maximem-server>
OPENAI_API_KEY=...

Build it

1. Identity & scoping

Three scopes do the work:
  • customer_id = "uber" — single tenant; this is Uber’s own product
  • user_id = <stable rider ID> — comes from your authenticated session, never trust the client
  • conversation_id = <session UUID> — one per chat session, stable across messages in the same session
# session_id → conversation_id mapping. Use Redis in production.
SESSIONS: dict[str, str] = {}

def conv_for(session_id: str) -> str:
    return SESSIONS.setdefault(session_id, str(uuid.uuid4()))

2. Business tools

The agent calls these to ground replies in real account state and to take action. Wire each one to your internal API.
from agents import function_tool

@function_tool
async def get_recent_rides(rider_id: str, limit: int = 5) -> list[dict]:
    """Return the rider's most recent rides."""
    return await rides_api.recent(rider_id, limit)

@function_tool
async def request_refund(ride_id: str, reason: str) -> dict:
    """Initiate a refund for a specific ride. Returns refund_id and ETA."""
    return await refunds_api.create(ride_id, reason)

@function_tool
async def create_lost_item_report(rider_id: str, ride_id: str, description: str) -> dict:
    """Open a lost-item report tied to a specific ride."""
    return await lost_items_api.create(rider_id, ride_id, description)

@function_tool
async def escalate_to_human(rider_id: str, summary: str) -> dict:
    """Open a Tier-2 ticket and hand off the conversation."""
    return await tickets_api.open(rider_id, summary, tier=2)

3. System prompt

Tight, outcome-focused, and explicit about memory and escalation rules.
System prompt
You are an Uber rider support agent.

- Always check the rider's recent rides before suggesting actions.
- Use prior resolutions and the rider's communication preferences from memory.
- Escalate to a human if: the rider explicitly asks, the issue involves safety, or the disputed amount exceeds $50.
- Keep replies under 4 sentences. Be warm and outcome-focused.

4. Wire memory + LLM + tools

The Python path uses the OpenAI Agents integration to expose synap_search and synap_store as tools the agent can call when it wants to recall or remember. The TypeScript path uses the Vercel AI SDK integration which wraps the model — context fetch and turn ingestion happen automatically on every call, so you only declare your business tools.
import os, uuid, asyncio
from agents import Agent, FunctionTool, Runner
from maximem_synap import MaximemSynapSDK
from synap_openai_agents import create_search_tool, create_store_tool

sdk = MaximemSynapSDK()
await sdk.initialize()

SYSTEM = """You are an Uber rider support agent.

- Always check the rider's recent rides before suggesting actions.
- Use prior resolutions and the rider's communication preferences from memory.
- Escalate to a human if: the rider explicitly asks, the issue involves safety, or the disputed amount exceeds $50.
- Keep replies under 4 sentences. Be warm and outcome-focused."""

async def handle_message(rider_id: str, session_id: str, text: str) -> str:
    conv_id = conv_for(session_id)

    synap_search = create_search_tool(sdk=sdk, user_id=rider_id, customer_id="uber")
    synap_store  = create_store_tool(sdk=sdk, user_id=rider_id, customer_id="uber")

    agent = Agent(
        name="uber_rider_support",
        instructions=SYSTEM,
        tools=[
            FunctionTool(synap_search, name_override="synap_search"),
            FunctionTool(synap_store,  name_override="synap_store"),
            get_recent_rides,
            request_refund,
            create_lost_item_report,
            escalate_to_human,
        ],
    )

    result = await Runner.run(agent, input=text)
    reply = result.final_output

    # Persist the full turn so the next message has context.
    # Fire-and-forget — never blocks the response.
    asyncio.create_task(sdk.memories.create(
        document=f"Rider: {text}\nAgent: {reply}",
        document_type="ai-chat-conversation",
        user_id=rider_id,
        customer_id="uber",
        metadata={"conversation_id": conv_id, "channel": "in-app-chat"},
    ))
    return reply
Why the two paths look different
  • Python’s synap_search / synap_store are tools the LLM decides to call. The model has agency over when to recall and when to store.
  • TS’s synap.wrap is middleware. Memory is fetched on every call and the turn is ingested afterward, unconditionally. Less control, less code.
Both ship. Pick the one that matches your framework. If you want LLM-driven memory in TS, build it as a custom tool — see the Vercel AI SDK integration.

Run & verify

Stand up the handler behind any HTTP framework (FastAPI, Next.js route handler, etc.) and send two messages from different sessions to see memory carry across:
Session 1
Rider:  Hi, my driver from this morning left my phone in the car.
Agent:  I'm sorry about that. I can see your 8:42 AM ride with driver Raj.
        Want me to open a lost-item report and notify Raj?

Rider:  Yes please. And I prefer texts not calls.
Agent:  Done — report #LI-23901 is open. You'll get a text when Raj responds.
Session 2 (next day, new conversation_id)
Rider:  Any update on the phone?
Agent:  Your lost-item report for the 8:42 AM ride is still pending Raj's
        response. As you preferred, I'll send updates via text only.
The second session is a fresh conversation_id — Synap pulls “prefers texts not calls” and “lost-item report open” from the rider’s long-term memory.

Inspect what got stored

docs = await sdk.memories.list(
    user_id="rider_42",
    customer_id="uber",
    limit=20,
)
for d in docs:
    print(d.document_type, "—", d.content[:120])

Customize / extend

  • Voice channel → swap the chat handler for Voice Concierge (Pipecat + ElevenLabs). Same scoping model, different I/O.
  • Multi-tenant flavor (B2B Uber-for-X) → set customer_id per tenant org. See Patterns → Multi-Tenant SaaS.
  • Different framework → drop the OpenAI Agents / Vercel AI SDK adapter; the SDK calls (memories.create, conversation.context.fetch) work standalone. See AI Integrations.
  • Slack human-handoff side-channel → when escalate_to_human fires, post into Slack using the Slack pattern. The Tier-2 agent has the full memory thread.
  • Catalog-grounded variant → see Amazon — Shopping Assistant for the same shape with product retrieval added.

Troubleshooting

Rider isn’t getting personalized replies
  • Check user_id is stable across sessions — derive it from authenticated session, not from the chat client.
  • Confirm ingestion is firing. The fire-and-forget pattern (asyncio.create_task) swallows errors silently; log inside the task during development.
Memory leaks across riders
  • user_id collision somewhere upstream. Audit the auth flow.
  • For B2B variants, also confirm customer_id is set per tenant.
Agent invents rides or refunds
  • A tool returned an empty result and the model hallucinated. Tighten tool docstrings, return explicit “no rides found” structures, and lower temperature.
TypeScript route times out on Vercel
  • You’re probably on Edge Runtime. Add export const runtime = "nodejs" to your route handler. The JS SDK requires Node + Python on the host.