> ## 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.

# Uber: Customer Support

> Rider-aware customer support agent with refunds, lost-item reports, and clean human handoff.

<Check>
  **Status:** Live in Playground · **Try it:** [synap.maximem.ai/playground](https://synap.maximem.ai/playground)
  Open the playground and pick **Uber: Customer Support** to see the reference implementation running before you build.
</Check>

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 to 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

<Frame>
  <img src="https://mintcdn.com/maximemai/hC0v5SwR4z4on8p2/images/cookbook-uber.png?fit=max&auto=format&n=hC0v5SwR4z4on8p2&q=85&s=41f903e9f78d3e815edc1018f9765c8d" alt="Uber rider support agent architecture diagram showing chat, backend, Synap memory fetch, LLM with tools, and turn ingestion" width="1774" height="887" data-path="images/cookbook-uber.png" />
</Frame>

Memory is auto-organized into a [MACA](/concepts/memory-architecture) 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

| Layer              | Choice                                                                                                                |
| ------------------ | --------------------------------------------------------------------------------------------------------------------- |
| **Synap SDK**      | `maximem-synap` (Python) / `@maximem/synap-js-sdk` (TypeScript)                                                       |
| **Framework**      | [OpenAI Agents SDK](/integrations/openai-agents) (Python) / [Vercel AI SDK](/integrations/vercel-ai-sdk) (TypeScript) |
| **Memory adapter** | `maximem-synap-openai-agents` / `@maximem/synap-vercel-adk`                                                           |
| **LLM**            | OpenAI `gpt-4o` (swap for any supported model)                                                                        |
| **Channel**        | Your existing chat surface (in-app, web widget, etc.)                                                                 |

## Prerequisites

* A Synap API key, see [Authentication](/setup/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)

<Warning>
  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](/setup/installation#javascript-typescript-sdk).
</Warning>

### Install

<CodeGroup>
  ```bash Python theme={null}
  pip install maximem-synap maximem-synap-openai-agents openai-agents
  ```

  ```bash uv theme={null}
  uv add maximem-synap maximem-synap-openai-agents openai-agents
  # pip-compatible (existing venv): uv pip install maximem-synap maximem-synap-openai-agents openai-agents
  ```

  ```bash TypeScript theme={null}
  npm install @maximem/synap-js-sdk @maximem/synap-vercel-adk ai @ai-sdk/openai zod
  ```
</CodeGroup>

### Configure

<CodeGroup>
  ```bash Python theme={null}
  # .env
  SYNAP_API_KEY=...
  OPENAI_API_KEY=...
  ```

  ```bash TypeScript theme={null}
  # .env.local
  SYNAP_API_KEY=...
  OPENAI_API_KEY=...
  ```
</CodeGroup>

## 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

<Note>
  `conversation_id`, `user_id`, and `customer_id` must be valid UUIDs. Generate session ids with `crypto.randomUUID()` (JS) or `str(uuid.uuid4())` (Python), as shown below.
</Note>

<CodeGroup>
  ```python Python theme={null}
  # 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()))
  ```

  ```typescript TypeScript theme={null}
  // session_id → conversation_id mapping. Use Redis in production.
  const SESSIONS = new Map<string, string>();

  function convFor(sessionId: string): string {
    if (!SESSIONS.has(sessionId)) SESSIONS.set(sessionId, crypto.randomUUID());
    return SESSIONS.get(sessionId)!;
  }
  ```
</CodeGroup>

### 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.

<CodeGroup>
  ```python Python theme={null}
  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)
  ```

  ```typescript TypeScript theme={null}
  import { tool } from "ai";
  import { z } from "zod";

  const businessTools = {
    get_recent_rides: tool({
      description: "Return the rider's most recent rides.",
      parameters: z.object({ riderId: z.string(), limit: z.number().default(5) }),
      execute: async ({ riderId, limit }) => ridesApi.recent(riderId, limit),
    }),
    request_refund: tool({
      description: "Initiate a refund for a specific ride. Returns refund_id and ETA.",
      parameters: z.object({ rideId: z.string(), reason: z.string() }),
      execute: async ({ rideId, reason }) => refundsApi.create(rideId, reason),
    }),
    create_lost_item_report: tool({
      description: "Open a lost-item report tied to a specific ride.",
      parameters: z.object({
        riderId: z.string(),
        rideId: z.string(),
        description: z.string(),
      }),
      execute: async ({ riderId, rideId, description }) =>
        lostItemsApi.create(riderId, rideId, description),
    }),
    escalate_to_human: tool({
      description: "Open a Tier-2 ticket and hand off the conversation.",
      parameters: z.object({ riderId: z.string(), summary: z.string() }),
      execute: async ({ riderId, summary }) => ticketsApi.open(riderId, summary, 2),
    }),
  };
  ```
</CodeGroup>

### 3. System prompt

Tight, outcome-focused, and explicit about memory and escalation rules.

```text System prompt theme={null}
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](/integrations/openai-agents) 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](/integrations/vercel-ai-sdk) which wraps the model: context fetch and turn ingestion happen automatically on every call, so you only declare your business tools.

<CodeGroup>
  ```python Python theme={null}
  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
  ```

  ```typescript TypeScript theme={null}
  import { generateText } from "ai";
  import { openai } from "@ai-sdk/openai";
  import { createSynap } from "@maximem/synap-vercel-adk";

  const synap = await createSynap({ apiKey: process.env.SYNAP_API_KEY! });

  const 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.`;

  export async function handleMessage(
    riderId: string,
    sessionId: string,
    text: string,
  ): Promise<string> {
    const conversationId = convFor(sessionId);

    // synap.wrap injects rider context as a system message before the LLM call
    // and ingests the completed turn afterward. No manual fetch / ingest needed.
    const model = synap.wrap(openai("gpt-4o"), {
      userId: riderId,
      customerId: "uber",
      conversationId,
    });

    const { text: reply } = await generateText({
      model,
      system: SYSTEM,
      prompt: text,
      tools: businessTools,
    });

    return reply;
  }
  ```
</CodeGroup>

**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](/integrations/vercel-ai-sdk).

## 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:

```text Session 1 theme={null}
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.
```

```text Session 2 (next day, new conversation_id) theme={null}
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

<CodeGroup>
  ```python Python theme={null}
  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])
  ```

  ```typescript TypeScript theme={null}
  const docs = await synap.sdk.memories.list({
    userId: "rider_42",
    customerId: "uber",
    limit: 20,
  });
  for (const d of docs) {
    console.log(d.documentType, ":", d.content.slice(0, 120));
  }
  ```
</CodeGroup>

## Customize / extend

* **Voice channel** → swap the chat handler for [Voice Concierge](/cookbook/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](/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](/integrations/overview).
* **Slack human-handoff side-channel** → when `escalate_to_human` fires, post into Slack using the [Slack pattern](/patterns/slack-bot). The Tier-2 agent has the full memory thread.
* **Catalog-grounded variant** → see [Amazon: Shopping Assistant](/cookbook/consumer-amazon) 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.

## Related

* **Integrations:** [OpenAI Agents SDK](/integrations/openai-agents) · [Vercel AI SDK](/integrations/vercel-ai-sdk)
* **Concepts:** [Customized Memory Architectures](/concepts/memory-architecture) · [Memory Scopes](/concepts/memory-scopes) · [Conversational Context Lifecycle](/concepts/context-end-to-end#short-term-context)
* **Patterns:** [Multi-Tenant SaaS](/patterns/multi-tenant-saas) · [Graceful Degradation](/patterns/graceful-degradation)
* **Vity:** [Maximem Vity overview](/vity/overview)
