> ## Documentation Index
> Fetch the complete documentation index at: https://docs.memory.anonalabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Memory models

> Standing questions a space keeps a current answer to: a running model of the user, their preferences, and how they work.

Retrieve answers a question from individual memories. A **memory model** answers a
standing question from *all* of them, and keeps that answer current as new memories
arrive.

Give a model a question ("how does this user like to be communicated with?", "what is
this customer's account history?") and the memory layer synthesizes an answer across
everything the space knows, then rewrites it as the space learns more. Read it back as
markdown and drop it straight into a system prompt.

> [!NOTE]
>
> A model's content is generated in the background. Creating or refreshing one returns a
> `job_id`; poll `GET /v1/spaces/{space_id}/jobs/{job_id}`, then read the model to get its
> content.

> [!NOTE]
>
> A memory model is a `reason` answer the space keeps, so it is written by the
> space's **reason model** — see
> [choosing a model](https://docs.memory.anonalabs.com/api-reference/model-selection.md). A stronger model is
> noticeably better at the thing these are for: holding the *current* value of a
> fact that has changed. Change the setting and the next refresh picks it up.

> [!TIP]
>
> Everything on this page is also on the **Models** tab of a space in the
> [dashboard](https://memory.anonalabs.com): the profile, each model's rendered content,
> and create, edit, refresh, clear and delete. Useful for reading what a space has
> concluded without writing a line of code.

## Create a model

```http theme={null}
POST /v1/spaces/{space_id}/models
Authorization: Bearer anona_live_YOUR_KEY
Content-Type: application/json
```

```json theme={null}
{
  "name": "Communication style",
  "query": "How does this user prefer to be communicated with?",
  "model_id": "comms",
  "tags": ["prefs"],
  "max_tokens": 2048,
  "trigger": { "mode": "delta", "refresh_on_new_memories": true }
}
```

| Field | Type | Default | Description |
| - | - | - | - |
| `name` | string | required | Human-readable name. |
| `query` | string | required | The question this model answers from the space's memories. |
| `model_id` | string | generated | Optional stable id. Lowercase alphanumeric and hyphens. |
| `tags` | string\[] | none | Only build the model from memories carrying these tags. |
| `max_tokens` | integer | unset (engine default, currently 2048) | Ceiling on generated content length, 256 to 8192. |
| `trigger.mode` | `full` \| `delta` | `full` | How a refresh rewrites the content. See [refresh modes](#refresh-modes). |
| `trigger.refresh_on_new_memories` | boolean | `false` | Refresh automatically once newly recorded memories are consolidated. |
| `trigger.memory_type` | string\[] | all | Restrict to `fact`, `experience` and/or `note`. |

```bash cURL theme={null}
curl -X POST https://api.anonalabs.com/v1/spaces/customer-support-bot/models \
  -H "Authorization: Bearer anona_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Communication style",
    "query": "How does this user prefer to be communicated with?",
    "model_id": "comms",
    "tags": ["prefs"],
    "trigger": { "mode": "delta", "refresh_on_new_memories": true }
  }'
```

```python Python theme={null}
job = client.create_memory_model(
    space_id="customer-support-bot",
    name="Communication style",
    query="How does this user prefer to be communicated with?",
    model_id="comms",
    tags=["prefs"],
    trigger={"mode": "delta", "refresh_on_new_memories": True},
)
```

```typescript TypeScript theme={null}
const job = await anona.createMemoryModel({
  spaceId: "customer-support-bot",
  name: "Communication style",
  query: "How does this user prefer to be communicated with?",
  modelId: "comms",
  tags: ["prefs"],
  trigger: { mode: "delta", refresh_on_new_memories: true },
});
```

**Response** `202 Accepted`

```json theme={null}
{ "model_id": "comms", "job_id": "job_9f2a", "status": "processing" }
```

## List models

```http theme={null}
GET /v1/spaces/{space_id}/models?limit=50&offset=0&tags=prefs
Authorization: Bearer anona_live_YOUR_KEY
```

```bash cURL theme={null}
curl "https://api.anonalabs.com/v1/spaces/customer-support-bot/models?limit=50&tags=prefs" \
  -H "Authorization: Bearer anona_live_YOUR_KEY"
```

```python Python theme={null}
models = client.list_memory_models(space_id="customer-support-bot", tags=["prefs"])
```

```typescript TypeScript theme={null}
const models = await anona.listMemoryModels({
  spaceId: "customer-support-bot",
  tags: ["prefs"],
});
```

**Response** `200 OK`

```json theme={null}
{
  "items": [
    {
      "id": "comms",
      "space_id": "customer-support-bot",
      "name": "Communication style",
      "query": "How does this user prefer to be communicated with?",
      "content": "Prefers short, direct answers. Has asked for brevity in several sessions and disengages past about three paragraphs.",
      "tags": ["prefs"],
      "max_tokens": 2048,
      "trigger": {
        "mode": "delta",
        "refresh_on_new_memories": true,
        "memory_type": null
      },
      "last_refreshed_at": "2026-08-01T10:00:00Z",
      "created_at": "2026-07-01T10:00:00Z",
      "is_stale": false
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}
```

| Field | Type | Description |
| - | - | - |
| `content` | string or null | The current answer, as markdown. Null until the first refresh finishes, or after a clear. A model that has never been refreshed reports `null`, never a partial or placeholder answer. |
| `is_stale` | boolean or null | True when memories have been recorded since the last refresh, so the content is behind. |
| `last_refreshed_at` | string or null | ISO timestamp of the last rewrite. |

## Get a model

```http theme={null}
GET /v1/spaces/{space_id}/models/{model_id}
Authorization: Bearer anona_live_YOUR_KEY
```

```bash cURL theme={null}
curl https://api.anonalabs.com/v1/spaces/customer-support-bot/models/comms \
  -H "Authorization: Bearer anona_live_YOUR_KEY"
```

```python Python theme={null}
model = client.get_memory_model(space_id="customer-support-bot", model_id="comms")
```

```typescript TypeScript theme={null}
const model = await anona.getMemoryModel({
  spaceId: "customer-support-bot",
  modelId: "comms",
});
```

Returns the same object as a list item, **plus `sources`** — the memories, notes and
other models the current content was written from.

```json theme={null}
{
  "id": "comms",
  "space_id": "support",
  "content": "Prefers short, direct answers.",
  "sources": [
    { "id": "3f2a…", "type": "fact", "text": "Alice asked for shorter replies" },
    { "id": "9c81…", "type": "note", "text": "Alice: communication preferences" }
  ]
}
```

| Field | Type | Description |
| - | - | - |
| `sources[].id` | string | The memory, note or model id. |
| `sources[].type` | string | `fact` or `experience` for a raw memory, `note` for a consolidated one, `summary` for another memory model. |
| `sources[].text` | string or null | What that source says, as stored. |

These are the memories the refresh **declared it used**, not everything it retrieved.
That is what makes a wrong answer diagnosable: if the memory you expected is absent from
the list, it was never reached — record it, or check the model's `tags` and `query`. If
it is present and the answer still contradicts it, the memory was read and mishandled,
which is a different problem and worth reporting.

An empty list means the content rests on nothing in this space. For a model that has
never refreshed that is expected; for one with content, it is a finding.

> [!NOTE]
>
> `sources` is served on this route only. The [list](#list-models) omits it
> — a page of models each carrying the full text of everything behind it is a large
> response for a view that only names them.

## Update a model

Changes the definition. Existing content is left alone. Change the query and the answer
only catches up on the next refresh.

```http theme={null}
PATCH /v1/spaces/{space_id}/models/{model_id}
Authorization: Bearer anona_live_YOUR_KEY
```

```bash cURL theme={null}
curl -X PATCH https://api.anonalabs.com/v1/spaces/customer-support-bot/models/comms \
  -H "Authorization: Bearer anona_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Communication preferences", "max_tokens": 4096 }'
```

```python Python theme={null}
model = client.update_memory_model(
    space_id="customer-support-bot",
    model_id="comms",
    name="Communication preferences",
    max_tokens=4096,
)
```

```typescript TypeScript theme={null}
const model = await anona.updateMemoryModel({
  spaceId: "customer-support-bot",
  modelId: "comms",
  name: "Communication preferences",
  maxTokens: 4096,
});
```

**Response** `200 OK`, the updated model.

## Refresh a model

Re-answers the model's question from everything the space knows now.

```http theme={null}
POST /v1/spaces/{space_id}/models/{model_id}/refresh
Authorization: Bearer anona_live_YOUR_KEY
```

```bash cURL theme={null}
curl -X POST \
  https://api.anonalabs.com/v1/spaces/customer-support-bot/models/comms/refresh \
  -H "Authorization: Bearer anona_live_YOUR_KEY"
```

```python Python theme={null}
job = client.refresh_memory_model(space_id="customer-support-bot", model_id="comms")
```

```typescript TypeScript theme={null}
const job = await anona.refreshMemoryModel({
  spaceId: "customer-support-bot",
  modelId: "comms",
});
```

**Response** `202 Accepted`

```json theme={null}
{ "model_id": "comms", "job_id": "job_be71", "status": "queued" }
```

### Refresh modes

| Mode | Behaviour |
| - | - |
| `full` | Regenerates the content from scratch every time. Predictable, and the right default. |
| `delta` | Edits in place: untouched sections are preserved verbatim, stale parts are dropped, new material is added. Cheaper on long documents. A model with no content yet, or whose `query` changed, falls back to a full regeneration automatically. |

A `delta` model that has drifted over many incremental edits can be reset: clear it, then
refresh for a clean rebuild.

## Clear a model's content

Drops the content, keeps the definition.

```http theme={null}
POST /v1/spaces/{space_id}/models/{model_id}/clear
Authorization: Bearer anona_live_YOUR_KEY
```

```bash cURL theme={null}
curl -X POST \
  https://api.anonalabs.com/v1/spaces/customer-support-bot/models/comms/clear \
  -H "Authorization: Bearer anona_live_YOUR_KEY"
```

```python Python theme={null}
model = client.clear_memory_model(space_id="customer-support-bot", model_id="comms")
```

```typescript TypeScript theme={null}
const model = await anona.clearMemoryModel({
  spaceId: "customer-support-bot",
  modelId: "comms",
});
```

**Response** `200 OK`, the model, with `content` now null.

## Delete a model

Removes the model. The memories it was built from are untouched.

```http theme={null}
DELETE /v1/spaces/{space_id}/models/{model_id}
Authorization: Bearer anona_live_YOUR_KEY
```

```bash cURL theme={null}
curl -X DELETE https://api.anonalabs.com/v1/spaces/customer-support-bot/models/comms \
  -H "Authorization: Bearer anona_live_YOUR_KEY"
```

```python Python theme={null}
client.delete_memory_model(space_id="customer-support-bot", model_id="comms")
```

```typescript TypeScript theme={null}
await anona.deleteMemoryModel({ spaceId: "customer-support-bot", modelId: "comms" });
```

**Response** `204 No Content`

## Model history

Every refresh archives the content it replaced, most recent first, showing how the space's view
of the question changed as it learned more.

```http theme={null}
GET /v1/spaces/{space_id}/models/{model_id}/history
Authorization: Bearer anona_live_YOUR_KEY
```

```bash cURL theme={null}
curl https://api.anonalabs.com/v1/spaces/customer-support-bot/models/comms/history \
  -H "Authorization: Bearer anona_live_YOUR_KEY"
```

```python Python theme={null}
history = client.get_memory_model_history(
    space_id="customer-support-bot",
    model_id="comms",
)
```

```typescript TypeScript theme={null}
const history = await anona.getMemoryModelHistory({
  spaceId: "customer-support-bot",
  modelId: "comms",
});
```

**Response** `200 OK`

```json theme={null}
{
  "entries": [
    {
      "previous_content": "Prefers detailed explanations.",
      "changed_at": "2026-07-15T09:00:00Z"
    }
  ],
  "total": 1
}
```

## Space profile

The space's standing description: what it is for, and how it weighs what it is told.

```http theme={null}
GET /v1/spaces/{space_id}/profile
Authorization: Bearer anona_live_YOUR_KEY
```

```bash cURL theme={null}
curl https://api.anonalabs.com/v1/spaces/customer-support-bot/profile \
  -H "Authorization: Bearer anona_live_YOUR_KEY"
```

```python Python theme={null}
profile = client.get_space_profile("customer-support-bot")
```

```typescript TypeScript theme={null}
const profile = await anona.getSpaceProfile("customer-support-bot");
```

**Response** `200 OK`

```json theme={null}
{
  "space_id": "customer-support-bot",
  "name": "Alice",
  "mission": "Keep Alice's team organized and shipping.",
  "disposition": { "skepticism": 3, "literalism": 4, "empathy": 5 }
}
```

| Field | Type | Description |
| - | - | - |
| `mission` | string | What this space is for. Empty if never set. |
| `disposition.skepticism` | integer | 1 takes statements at face value, 5 questions them. |
| `disposition.literalism` | integer | 1 reads intent loosely, 5 reads literally. |
| `disposition.empathy` | integer | 1 ignores emotional context, 5 weighs it heavily. |

Disposition shapes how the space interprets what it is told when it synthesizes. It does
not affect plain retrieval.

### Setting the profile

```http theme={null}
PUT /v1/spaces/{space_id}/profile
Authorization: Bearer anona_live_YOUR_KEY
Content-Type: application/json

{
  "mission": "This space tracks the Halcyon account for the customer-success team; answers are read by non-technical account managers.",
  "disposition": { "skepticism": 2, "literalism": 2, "empathy": 5 }
}
```

```bash cURL theme={null}
curl -X PUT https://api.anonalabs.com/v1/spaces/customer-support-bot/profile \
  -H "Authorization: Bearer anona_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mission": "This space tracks the Halcyon account for the customer-success team.",
    "disposition": { "skepticism": 2, "literalism": 2, "empathy": 5 }
  }'
```

```python Python theme={null}
client.set_space_profile(
    space_id="customer-support-bot",
    mission="This space tracks the Halcyon account for the customer-success team.",
    disposition={"skepticism": 2, "literalism": 2, "empathy": 5},
)
```

```typescript TypeScript theme={null}
await anona.setSpaceProfile({
  spaceId: "customer-support-bot",
  mission: "This space tracks the Halcyon account for the customer-success team.",
  disposition: { skepticism: 2, literalism: 2, empathy: 5 },
});
```

Both SDK methods take `mission` and `disposition` as **required** arguments, for the
same reason the endpoint replaces rather than patches: an argument you leave out is an
argument that clears. Read the current pair back with `get_space_profile` /
`getSpaceProfile` when you only mean to change one of them.

Responds `200 OK` with the profile as it now stands.

| Field | Type | Description |
| - | - | - |
| `mission` | string \| null | Up to 4,000 characters. Null or blank clears it. |
| `disposition.skepticism` | integer \| null | 1-5. Null restores the default of 3. |
| `disposition.literalism` | integer \| null | 1-5. Null restores the default of 3. |
| `disposition.empathy` | integer \| null | 1-5. Null restores the default of 3. |

Three things worth knowing before you call it.

**It is a full replace.** A body naming only `mission` clears the dials back to the
default, exactly as [chat defaults](https://docs.memory.anonalabs.com/api-reference/chat.md#space-defaults) and
[extraction settings](https://docs.memory.anonalabs.com/api-reference/extraction-settings.md) do. Send both halves every
time, or send only what you want the space to end up with.

**The response is what the space now has, not what you sent.** A dial cleared to `null`
comes back as `3`, because that is the value the space will actually reason with.

**Owner-only.** A space shared with you can be read and written to, but not
reconfigured — the mission reframes every answer the space gives to everyone who can
read it. A non-owner gets `403 space_owner_only`.

`DELETE /v1/spaces/{space_id}/profile` returns `204` and puts the space back to no
mission and 3/3/3. It is idempotent, and it is `reset_space_profile(space_id)` /
`deleteSpaceProfile(spaceId)` in the SDKs.

The mission is prepended to the reasoning prompt on every [reason](https://docs.memory.anonalabs.com/api-reference/reason.md)
call and every model refresh, so it costs a few tokens per call rather than once — worth
keeping to a paragraph rather than a page. Setting the profile is itself free.

## Cost

Creating and refreshing a model each run a synthesis pass over the space's memories and
are billed like [reason](https://docs.memory.anonalabs.com/api-reference/reason.md). Reading models, history and the profile
is free.

## Error responses

| Status | Code | Cause |
| - | - | - |
| 401 | `missing_auth` / `invalid_api_key` / `key_expired` | No `Authorization` header, a key Anona did not issue or has revoked, or a key past its expiry date. |
| 403 | `space_access_denied` | You are not a member of this space. |
| 404 | `model_error` | No memory model with that id, or no such space. This surface reports every engine-side failure as `model_error` with the engine's own status, so the same code also appears on `4xx` and `5xx`. Nothing to do with `unsupported_model` or `invalid_model`, which are about the LLM a space thinks with. |
| 422 | `validation_error` | Unknown or malformed field in the request body. |
| 429 | `rate_limited` | Credit quota or rate limit exceeded. |

See the full [error reference](https://docs.memory.anonalabs.com/api-reference/errors.md).

## Next steps

> **[Reason API](https://docs.memory.anonalabs.com/api-reference/reason.md)**
>
> Answer a one-off question across memories.

> **[MCP integration](https://docs.memory.anonalabs.com/mcp-integration.md)**
>
> Let an agent read a space's profile and models directly.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.