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

# AI Companion

> Conversational companion that learns preferences, communication style, and ongoing context across sessions.

<Check>
  **Status:** Live in Playground · **Try it:** [synap.maximem.ai/playground](https://synap.maximem.ai/playground)
  Open the playground and pick **AI Companion** to see the reference implementation. (This recipe replaces the previous Tinder Dating Support example.)
</Check>

A personal companion that pays attention. It learns how the user likes to be talked to, what's going on in their life, what they care about, and what's off-limits, and brings that context forward conversation after conversation without making the user repeat themselves.

## What you'll build

A conversational agent that:

* **Tracks personal context**: name, pronouns, life events, ongoing situations, communication style
* **Adapts tone** to the user (formal vs casual, brief vs warm, emoji vs plain)
* **Respects boundaries**: topics the user has marked off-limits stay off-limits
* **Holds threads across sessions**: picks up open conversations without recap prompts

**Est. build time:** 20-30 minutes. This is the simplest recipe in the Cookbook by surface area, but the highest-leverage memory.

## When to use this recipe

Build this if:

* The product is a 1:1 conversational experience (companion, journaling buddy, lifestyle assistant)
* Personalization across sessions is the core value, not tool execution
* The user owns their context: strict per-user isolation
* You want minimal tools; the memory does the heavy lifting

## Architecture at a glance

```mermaid theme={null}
flowchart TD
    Chat[User chat<br/>mobile or web] --> Backend[Your backend]
    Backend -->|fetch| Synap1[(Synap context fetch<br/>prefs, style, ongoing threads, boundaries)]
    Synap1 --> LLM[LLM<br/>light or no tools]
    LLM --> Reply[Reply to user]
    Reply -.->|fire-and-forget| Synap2[(Synap ingest turn)]
```

Memory is the agent. Tools are optional.

## 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` (warmer voice) or `claude-sonnet-4-6` (better long-form)                                              |
| **Channel**        | Native mobile / web chat                                                                                              |

## Prerequisites

* A Synap API key. See [Authentication](/setup/authentication)
* **Python:** Python 3.11+
* **TypeScript:** Node 18+ **and** Python 3.11+ on the host

<Warning>
  TypeScript recipe runs on Node only. 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
  ```
</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

* `customer_id = "companion"`: single tenant (your app)
* `user_id = <stable user ID>`: strict per-user isolation; no cross-user leakage
* `conversation_id = <session UUID>`: one per app session

Use a long-lived `conversation_id` (per app install, not per app open) if you want the companion to feel like one continuous relationship rather than a series of standalone chats.

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

<CodeGroup>
  ```python Python theme={null}
  SESSIONS: dict[str, str] = {}

  def conv_for(user_id: str) -> str:
      # One conversation per user. Tune to per-session if you prefer.
      return SESSIONS.setdefault(user_id, str(uuid.uuid4()))
  ```

  ```typescript TypeScript theme={null}
  const SESSIONS = new Map<string, string>();

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

### 2. System prompt

The prompt is most of the work here: it shapes how memory gets used.

```text System prompt theme={null}
You are a personal companion. The user is talking to you in confidence.

- Use what you remember about them (preferences, ongoing situations, communication style) to respond like someone who's been listening.
- Mirror their tone. If they use emoji, use emoji. If they're terse, be terse. If they're warm, be warm.
- Never volunteer past information unprompted unless it's directly relevant. They told you in trust, not so you could quiz them.
- Respect anything they've marked as off-limits, sensitive, or "don't bring up."
- When they share something meaningful (a goal, a boundary, a person, a change), remember it.
- If they ask what you remember, summarize honestly. Offer to forget on request.
```

### 3. Wire memory + LLM

<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()

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

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

      agent = Agent(
          name="companion",
          instructions=SYSTEM,
          tools=[
              FunctionTool(synap_search, name_override="synap_search"),
              FunctionTool(synap_store,  name_override="synap_store"),
          ],
      )

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

      asyncio.create_task(sdk.memories.create(
          document=f"User: {text}\nCompanion: {reply}",
          document_type="ai-chat-conversation",
          user_id=user_id,
          customer_id="companion",
          metadata={"conversation_id": conv_id},
      ))
      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! });

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

    const model = synap.wrap(openai("gpt-4o"), {
      userId,
      customerId: "companion",
      conversationId,
    });

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

    return reply;
  }
  ```
</CodeGroup>

### 4. The "forget" hook

A trust-grade companion needs a way to forget. Expose this from your app.

<CodeGroup>
  ```python Python theme={null}
  async def forget(user_id: str, query: str) -> int:
      """Delete memories matching a natural-language query for a user."""
      matches = await sdk.memories.search(
          user_id=user_id,
          customer_id="companion",
          query=query,
          max_results=20,
      )
      for m in matches:
          await sdk.memories.delete(m.id)
      return len(matches)
  ```

  ```typescript TypeScript theme={null}
  export async function forget(userId: string, query: string): Promise<number> {
    const matches = await synap.sdk.memories.search({
      userId,
      customerId: "companion",
      query,
      maxResults: 20,
    });
    for (const m of matches) await synap.sdk.memories.delete(m.id);
    return matches.length;
  }
  ```
</CodeGroup>

Wire this to a user-facing "forget what we talked about Mondays" or "delete everything about my ex" affordance. Trust in a companion app is mostly about giving users this control.

## Run & verify

```text Day 1 theme={null}
User:      Hey, I'm Sara. Heads up: I'm vegetarian and please don't ask about my family,
           it's a sore spot.
Companion: Got it, Sara. Vegetarian noted, family off-limits. What's on your mind today?
```

```text Day 8 (fresh session, same user) theme={null}
User:      Recommend something for dinner.
Companion: Vegetarian, so, easy: roasted chickpea bowl, takes 25 mins.
           Want the recipe or just the idea?
```

```text Day 14 theme={null}
User:      What do you remember about me?
Companion: You're Sara, vegetarian, and you've asked me to keep family off the table.
           That's what I keep front-of-mind. Want me to forget anything?
```

## Customize / extend

* **Voice channel** → adapt this for phone using [Voice Concierge](/cookbook/voice-concierge). Tone matters even more in voice.
* **Wellness flavor** → if the companion is goal-driven (workouts, sleep, journaling), see [AI Coach](/cookbook/personal-ai-coach) for the goal-tracking shape.
* **Multi-modal** → image generation, voice notes, photo recognition: all stack on top; memory is unchanged.
* **End-to-end encryption** → memories at rest aren't encrypted per-user by default. If your trust model requires it, encrypt the document field client-side before calling `memories.create`.

## Troubleshooting

**Companion forgets things the user told it last week**

* Confirm ingestion is firing. Log inside the `asyncio.create_task` block during development: the fire-and-forget pattern swallows errors silently.
* Increase `maxResults` in the search adapter so important memories aren't crowded out.

**Companion volunteers sensitive memories unprompted**

* Sharpen the system prompt's "never volunteer unprompted" line.
* Consider tagging sensitive memories with `metadata.sensitivity = "high"` and filtering them out of default search.

**Tone drift over a long session**

* Long histories can let the model regress to LLM-default voice. Re-inject the user's tone preference into the system prompt at the start of each session, pulled from a "communication-style" memory.

## Related

* **Integrations:** [OpenAI Agents SDK](/integrations/openai-agents) · [Vercel AI SDK](/integrations/vercel-ai-sdk)
* **Concepts:** [Customer Context](/concepts/context-end-to-end#customer-context) · [Memory Types](/concepts/memories-and-context#memory-types) · [Memory Scopes](/concepts/memory-scopes)
* **Patterns:** [Graceful Degradation](/patterns/graceful-degradation) · [RAG over User History](/patterns/rag-user-history)
* **Other recipes:** [AI Coach](/cookbook/personal-ai-coach)
