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

# User Profile

> Configure a structured, per-user profile that Synap builds and maintains from conversations — defined by your own attribute schema.

## Overview

The **User Profile** is a structured document Synap maintains for every end user of your
instance: a set of **critical attributes you define** (typed fields like a customer's
budget, preferred language, or purchase timeline), a short narrative **overview**, and an
**extras** bucket for stable facts that fall outside your schema.

Profiles are built exclusively from what users actually said in ingested conversations —
**extracted, never inferred**. If a conversation never touched an attribute, that
attribute stays blank. Every populated attribute carries its own confidence, timestamp,
and the ID of the conversation it came from.

The profile is designed for **conversation-start injection**: one low-latency fetch
returns the profile (plus recent conversation summaries) ready to paste into your
agent's system prompt — see [Context Fetch](/sdk/context-fetch) and
[`user.get_profile`](/sdk-reference/user/get-profile).

## Availability and enabling

The feature ships platform-wide but is **off by default** for every instance
(zero-regression: enabling it never changes existing ingestion or retrieval behavior).
It is switched on per instance by adding a `user_profile` block to the instance's
memory-architecture configuration — contact your Maximem team or use your dashboard's
instance configuration to enable it and define your attributes.

```json theme={null}
{
  "user_profile": {
    "enabled": true,
    "overview_enabled": true,
    "max_profile_tokens": 1000,
    "critical_attributes": [ ... ]
  }
}
```

Your attribute definitions are treated as client-owned state: they survive
memory-architecture regeneration and instance promotion unchanged.

## Defining critical attributes

Each attribute is a typed field:

| Field            | Type                                                  | Notes                                                                                        |
| ---------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `name`           | string                                                | snake\_case identifier, e.g. `budget_range`                                                  |
| `type`           | `string` \| `number` \| `boolean` \| `enum` \| `list` | `enum` requires `allowed_values`                                                             |
| `description`    | string                                                | Guides extraction — state exactly what qualifies                                             |
| `allowed_values` | string\[]                                             | Closed set for `enum` types                                                                  |
| `required`       | boolean                                               | Flags the attribute as expected (used by dashboards; extraction still never invents a value) |
| `examples`       | string\[]                                             | Optional hints for open-ended fields                                                         |

A realistic schema for a sales assistant:

```json theme={null}
"critical_attributes": [
  {"name": "customer_name", "type": "string",
   "description": "Only if explicitly stated by the customer — never inferred. Preferred name."},
  {"name": "preferred_language", "type": "string",
   "description": "Preferred conversation language.", "examples": ["English", "Hindi"]},
  {"name": "product_interest", "type": "enum",
   "description": "Product line the customer asked about.",
   "allowed_values": ["Starter", "Professional", "Enterprise"]},
  {"name": "budget_range", "type": "string",
   "description": "Budget exactly as stated by the customer."},
  {"name": "decision_timeline", "type": "enum",
   "description": "Stated purchase timeline.",
   "allowed_values": ["Immediate", "3 months", "6+ months"]},
  {"name": "main_objection", "type": "string",
   "description": "Biggest objection raised.", "examples": ["Price", "Integration effort"]}
]
```

<Tip>
  Write descriptions as extraction instructions. A phrase like *"only if explicitly
  stated by the customer"* in the description is honored by the extraction pass — fields
  stay blank until a conversation genuinely provides the value.
</Tip>

## How the profile is built

The profile updates as a by-product of normal [ingestion](/sdk/ingestion) — there is no
separate API call to maintain it:

1. Your application pushes a conversation (for example with
   [`conversation.ingest_transcript`](/sdk-reference/conversation/ingest-transcript))
   and moves on — everything below is asynchronous and off your hot path.
2. Synap's ingestion pipeline extracts long-term memories from the transcript.
3. At the end of the pipeline, the profile step loads your instance's attribute schema
   and merges newly extracted, conversation-grounded values into the user's profile
   document — updating only attributes the conversation actually evidenced, respecting
   `allowed_values`, and rewriting the narrative overview.
4. Each write bumps the profile's version and records, per attribute, the source
   conversation ID and timestamp. Unrelated attributes are never touched, and concurrent
   ingestions for the same user merge safely.

Stable facts that don't match any configured attribute are kept in the profile's
`extras` object rather than discarded. Client-supplied analysis passed with the
transcript is used as extraction *hints* only — the transcript remains the source of
truth.

<Note>
  Attribute-schema edits take effect on new ingestions within a few minutes (configuration
  is cached briefly). Existing profile values are preserved; new and changed attributes
  populate as subsequent conversations provide evidence.
</Note>

## Reading the profile

At conversation start, fetch the profile together with recent conversation summaries in
one call:

```python theme={null}
ctx = await sdk.fetch(
    user_id="user-123",
    context_mode="conversation-summary",
    include_profile=True,
    last_n_conversations=1,
)
prompt_block = ctx.formatted_context   # "## Caller Profile" + "## Previous Conversations"
profile = ctx.profile                  # typed document, or None if not yet built
```

Or read it directly:

```python theme={null}
profile = await sdk.user.get_profile(user_id="user-123")
```

The document shape:

```json theme={null}
{
  "attributes": {
    "budget_range": {
      "value": "about 20k per year",
      "confidence": 1.0,
      "updated_at": "2026-08-01T10:14:03Z",
      "source_conversation_id": "call-0042"
    }
  },
  "overview": "Short narrative summary of who this user is and what they want.",
  "extras": {"team_size": 40},
  "_meta": {"schema": 1, "version": 3, "updated_at": "2026-08-01T10:14:03Z"}
}
```

See [`user.get_profile`](/sdk-reference/user/get-profile) for response details and
[Response Shapes](/sdk/response-shapes) for the typed models.
