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

> Wellness coach with goal tracking, plan adherence memory, and session continuity.

<Info>
  **Status:** In Development · Playground demo coming soon.
  The recipe below is complete and runnable today; only the hosted playground showcase is pending.
</Info>

A wellness coach that holds the user's goals, current plan, what they've actually done, and what's been hard. It celebrates streaks, flexes plans when life gets in the way, and never starts from zero on Monday morning.

## What you'll build

A coaching agent that:

* **Tracks goals and plans**: primary goal, weekly plan, constraints
* **Logs adherence**: workouts, meals, sleep, sessions (whatever your domain is)
* **Adapts plans**: if the user skipped three sessions, the next plan reflects that
* **Holds the narrative**: last week's slump, the injury that's still healing, the trip coming up

**Est. build time:** 45 minutes (more if your logging schema is rich).

## When to use this recipe

Build this if:

* Your product has a notion of a *plan* the user is following over weeks
* Adherence (what got done vs what was planned) matters as much as what's currently planned
* You want the coach to feel continuous, not session-bound
* The user is the only client; per-user isolation is strict

## 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/>goals, current plan, recent adherence, constraints)]
    Synap1 --> LLM[LLM with tools]
    LLM --> Tools["set_goal<br/>get_current_plan<br/>log_session<br/>get_streak<br/>suggest_plan_adjustment"]
    Tools --> Reply[Reply to user]
    Reply -.->|fire-and-forget| Synap2[(Synap ingest turn)]
```

## 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) |
| **Storage for structured logs** | Your own DB (Postgres / SQLite). Synap holds the narrative; the DB holds the rows.                                    |
| **LLM**                         | OpenAI `gpt-4o`                                                                                                       |

## Prerequisites

* A Synap API key. See [Authentication](/setup/authentication)
* A DB for structured session logs (Synap is not your activity log; it's the memory that wraps around it)
* **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 zod
  ```
</CodeGroup>

### Configure

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

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

## Build it

### 1. The Synap-vs-DB split

This is the key call: **structured data lives in your DB; narrative lives in Synap.**

| What                                | Where | Why                                                         |
| ----------------------------------- | ----- | ----------------------------------------------------------- |
| "Ran 5km on Tuesday, 8:21 pace"     | DB    | Queryable, aggregatable, historical                         |
| "Hates running in the rain"         | Synap | Soft preference; surfaces when planning                     |
| "Knee felt off after Tuesday's run" | Synap | Narrative signal the next plan should respect               |
| "Goal: half-marathon by October"    | Both  | Structured target in DB, plus motivational context in Synap |

Tools read both. The system prompt teaches the agent which is which.

### 2. Identity & scoping

* `customer_id = "coach"`: single tenant
* `user_id = <stable user ID>`: strict per-user isolation
* `conversation_id`: one continuous conversation per user works well here (this is a long-running relationship)

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

### 3. Business tools

<CodeGroup>
  ```python Python theme={null}
  from agents import function_tool

  @function_tool
  async def set_goal(user_id: str, goal: str, target_date: str) -> dict:
      """Set or update the user's primary training goal."""
      return await db.goals.upsert(user_id, goal, target_date)

  @function_tool
  async def get_current_plan(user_id: str) -> dict:
      """Return the user's current week plan with session list and status."""
      return await db.plans.current(user_id)

  @function_tool
  async def log_session(user_id: str, kind: str, payload: dict) -> dict:
      """Log a completed session. kind: 'run' | 'lift' | 'sleep' | 'meal' | 'mood'."""
      return await db.sessions.create(user_id, kind, payload)

  @function_tool
  async def get_streak(user_id: str, kind: str) -> dict:
      """Return current and best streak for a session kind."""
      return await db.sessions.streak(user_id, kind)

  @function_tool
  async def suggest_plan_adjustment(user_id: str, reason: str) -> dict:
      """Generate a tentative plan adjustment, save it as pending for user approval."""
      return await planner.adjust(user_id, reason)
  ```

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

  const coachTools = {
    set_goal: tool({
      description: "Set or update the user's primary training goal.",
      parameters: z.object({
        userId: z.string(), goal: z.string(), targetDate: z.string(),
      }),
      execute: async ({ userId, goal, targetDate }) => db.goals.upsert(userId, goal, targetDate),
    }),
    get_current_plan: tool({
      description: "Return the user's current week plan with session list and status.",
      parameters: z.object({ userId: z.string() }),
      execute: async ({ userId }) => db.plans.current(userId),
    }),
    log_session: tool({
      description: "Log a completed session.",
      parameters: z.object({
        userId: z.string(),
        kind: z.enum(["run", "lift", "sleep", "meal", "mood"]),
        payload: z.record(z.any()),
      }),
      execute: async ({ userId, kind, payload }) => db.sessions.create(userId, kind, payload),
    }),
    get_streak: tool({
      description: "Return current and best streak for a session kind.",
      parameters: z.object({ userId: z.string(), kind: z.string() }),
      execute: async ({ userId, kind }) => db.sessions.streak(userId, kind),
    }),
    suggest_plan_adjustment: tool({
      description: "Generate a tentative plan adjustment for user approval.",
      parameters: z.object({ userId: z.string(), reason: z.string() }),
      execute: async ({ userId, reason }) => planner.adjust(userId, reason),
    }),
  };
  ```
</CodeGroup>

### 4. System prompt

```text System prompt theme={null}
You are a wellness coach. The user is following a plan you helped design.

- Their plan and logged sessions live in tools. Always check them before suggesting anything new.
- Their constraints, preferences, history, and current "season of life" live in your memory. Use them.
- Celebrate consistency more than intensity. Acknowledge skipped sessions without judgment.
- If they've skipped 3+ planned sessions or surfaced an injury / illness / life event, propose a plan adjustment via the tool. Don't push through.
- When they share something coach-relevant (an injury, a trip, a constraint), remember it.
- Keep replies under 4 sentences unless walking through a plan. Be direct, be warm.
```

### 5. Wire it together

<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="coach")
      synap_store  = create_store_tool(sdk=sdk, user_id=user_id, customer_id="coach")

      agent = Agent(
          name="coach",
          instructions=SYSTEM,
          tools=[
              FunctionTool(synap_search, name_override="synap_search"),
              FunctionTool(synap_store,  name_override="synap_store"),
              set_goal, get_current_plan, log_session, get_streak, suggest_plan_adjustment,
          ],
      )

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

      asyncio.create_task(sdk.memories.create(
          document=f"User: {text}\nCoach: {reply}",
          document_type="ai-chat-conversation",
          user_id=user_id,
          customer_id="coach",
          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: "coach",
      conversationId,
    });

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

    return reply;
  }
  ```
</CodeGroup>

## Run & verify

```text Week 1 theme={null}
User:  Goal is to run a half-marathon by October.
Coach: Locked in: half-marathon by October. I'll build a 12-week base + build plan.
       Two questions: any injuries to work around, and how many days a week can you train?

User:  Probably 4. Old knee thing on the right, hurts on hills.
Coach: 4 days, flat routes prioritized, hills reintroduced gradually. Plan up tomorrow.
```

```text Week 4 (fresh session) theme={null}
User:  Missed Tuesday and Wednesday. Travel.
Coach: Got it. Logging both as skipped, reason: travel. You've still got 2/4 this week,
       which is fine for a base block. Want me to slide Thursday's tempo to Saturday?
```

```text Week 8 theme={null}
User:  Knee is acting up again.
Coach: Noted. Knee flaring same right side as before. Pulling the hill repeats and dropping
       to 3 days this week. Half-marathon date is still safe with this adjustment.
       Want me to suggest a PT check-in?
```

The coach remembers the knee history (Synap), checks current plan (tool), proposes an adjustment (tool), and stays warm.

## Customize / extend

* **Companion flavor** → if you want less goal-driven and more open-ended, see [AI Companion](/cookbook/personal-ai-companion).
* **Voice journaling** → swap the chat handler for [Voice Concierge](/cookbook/voice-concierge). Coaching by voice is natural.
* **Apple Health / Wearable ingestion** → write structured rows to your DB; let Synap pick up the narrative when the user mentions it.
* **Cohort coaching** → set `customer_id = <cohort_id>` to share light context (community PRs, group challenges) across users within a cohort while keeping personal context per-`user_id`.

## Troubleshooting

**Coach asks the same intake questions every session**

* Confirm goal-setting facts are being ingested. After `set_goal`, also call `sdk.memories.create` with a narrative summary ("Goal: half-marathon by October, training 4 days/week, knee-aware").

**Coach doesn't adapt when user misses sessions**

* The model isn't pulling DB state. Sharpen the system prompt: "Before any plan advice, call `get_current_plan` and `get_streak`." If you're using the TS wrapper, ensure tools fire by giving the model an explicit instruction to check state.

**Plan adjustments feel generic**

* The `suggest_plan_adjustment` tool needs the *reason* to come from Synap memory (injury context, life events). Without it, you get generic deload weeks.

## Related

* **Integrations:** [OpenAI Agents SDK](/integrations/openai-agents) · [Vercel AI SDK](/integrations/vercel-ai-sdk)
* **Concepts:** [Memory Types](/concepts/memories-and-context#memory-types) · [Memory Scopes](/concepts/memory-scopes) · [Long-term Context](/concepts/context-end-to-end#long-term-context)
* **Other recipes:** [AI Companion](/cookbook/personal-ai-companion) · [Voice Concierge](/cookbook/voice-concierge)
