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

# ManifoldState

> The axes a person's thinking actually varies along, discovered from their own content rather than chosen from a list.

Personality APIs hand you a fixed vocabulary and score people against it. That works until the vocabulary is wrong for the person, and it is wrong for most people, because the interesting variation in how someone thinks is rarely the variation somebody else pre-named.

Exo takes the opposite approach. It reads the axes out of the subject's own content and reports what it found, including how much of their variation each axis explains and how confident the clustering is.

Read it from [`GET /v1/recall/identity`](/api-reference/recall/get-recall).

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  curl "https://api.get-exo.com/v1/recall/identity" \
    -H "X-Exo-API-Key: $EXO_KEY" \
    -H "X-Exo-Subject: customer_6412"
  ```

  ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  import os, requests

  r = requests.get(
      "https://api.get-exo.com/v1/recall/identity",
      headers={
          "X-Exo-API-Key": os.environ["EXO_KEY"],
          "X-Exo-Subject": "customer_6412",
      },
  )
  r.raise_for_status()
  state = r.json()["manifoldState"]
  if state is None:
      print("no discovery pass yet")
  else:
      for dim in state["dimensions"]:
          print(dim["name"], dim["positive_label"], "vs", dim["negative_label"])
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  const r = await fetch("https://api.get-exo.com/v1/recall/identity", {
    headers: {
      "X-Exo-API-Key": process.env.EXO_KEY!,
      "X-Exo-Subject": "customer_6412",
    },
  });
  if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
  const { manifoldState } = await r.json();
  if (manifoldState === null) {
    console.log("no discovery pass yet");
  } else {
    for (const dim of manifoldState.dimensions) {
      console.log(dim.name, dim.positive_label, "vs", dim.negative_label);
    }
  }
  ```
</CodeGroup>

```json 200 application/json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "type": "identity",
  "manifoldState": {
    "version": 2,
    "discovered_at": "2026-07-14T02:41:55Z",
    "n_signals": 412,
    "n_dimensions": 4,
    "dimensions": [
      {
        "name": "systems_thinking_vs_component_focus",
        "positive_label": "whole-system framing",
        "negative_label": "single-component focus",
        "description": "Whether a problem is framed as an interaction between parts or as one part in isolation."
      },
      {
        "name": "ship_now_vs_design_first",
        "positive_label": "ship and iterate",
        "negative_label": "design before building",
        "description": "How much certainty is required before committing to an implementation."
      }
    ],
    "variance_ratios": [0.31, 0.19, 0.12, 0.08],
    "cumulative_variance": 0.7,
    "n_basins": 3,
    "basins": [
      {
        "name": "rapid_implementation",
        "description": "Whole-system framing paired with a bias to ship and correct in flight.",
        "center": [0.62, 0.41, -0.08, 0.13],
        "size": 187
      },
      {
        "name": "architecture_review",
        "description": "Systems framing with a strong preference for settling the design first.",
        "center": [0.55, -0.49, 0.21, -0.04],
        "size": 143
      }
    ],
    "basin_centers": [[0.62, 0.41, -0.08, 0.13], [0.55, -0.49, 0.21, -0.04]],
    "silhouette_score": 0.41,
    "refresh_count": 6
  },
  "voiceProfile": { "formalityLevel": 0.24, "avgMessageLengthWords": 38.6, "technicalDepth": 0.71, "evidenceCount": 412 },
  "recentPatterns": [],
  "basinHistory": [],
  "contradictions": []
}
```

## What it is

Start with the four ideas, in the order they build.

**Dimension**, one axis along which this person's thinking varies (in practice, a named spectrum with two poles, like *whole-system framing* against *single-component focus*).

**Position**, where they sit on those axes at a moment in time (in practice, one number per dimension).

**Basin**, a stable mode of thinking the subject keeps returning to (in practice, the cluster their positions fall into, with a name and a member count).

**Trajectory**, how the position has moved over time (in practice, an ordered series of past positions and the basin each one fell in).

The metaphor is a landscape. Dimensions are the directions you can walk. Basins are the valleys you tend to end up in. A trajectory is the path someone actually took across it.

### The axes are discovered, not chosen

Exo does not start from a personality inventory. It takes the subject's identity signals, finds the directions in which those signals differ most, and names each direction from the content that loads at either end of it. So the axes are specific to the person: an infrastructure engineer and a novelist get different dimensions, because different things vary in their writing.

Two consequences that matter when you consume this:

* **`dimensions` is not a fixed list.** Its length, its names and its order are all discovered. Do not index into it by position and assume you know what index 0 means.
* **Names can change between passes.** A dimension's label is regenerated when the axis it describes has genuinely moved. `refresh_count` tells you how many passes have run.

## Field reference

This object is **snake\_case**, unlike the rest of the API. Those are the stored discovery keys, passed through as written, and the object is deliberately open: a pass records diagnostic keys beyond the ones named here and every one is passed through unchanged. Read what you need and ignore the rest.

| Field                 | Type            | What it means                                                                                   |
| --------------------- | --------------- | ----------------------------------------------------------------------------------------------- |
| `dimensions`          | array or null   | The discovered axes. Each carries `name`, `positive_label`, `negative_label` and `description`. |
| `variance_ratios`     | array or null   | Share of the subject's variation each axis explains, strongest first.                           |
| `cumulative_variance` | number or null  | Total share the kept axes explain together.                                                     |
| `n_dimensions`        | integer or null | How many axes the pass kept.                                                                    |
| `basins`              | array or null   | The stable modes. Each carries `name`, `description`, `center` and `size`.                      |
| `basin_centers`       | array or null   | Basin coordinates along the discovered axes.                                                    |
| `n_basins`            | integer or null | How many basins were found.                                                                     |
| `silhouette_score`    | number or null  | How cleanly separated the basins are, from -1 to 1.                                             |
| `trajectory`          | array or null   | Where the subject has sat over time, oldest first.                                              |
| `n_signals`           | integer or null | How many identity signals the pass decomposed.                                                  |
| `refresh_count`       | integer or null | How many discovery passes have run.                                                             |
| `discovered_at`       | string or null  | When this pass ran.                                                                             |
| `version`             | integer or null | Schema version of the stored record.                                                            |

### Reading the two numbers that tell you whether to trust it

**`variance_ratios`** says how much of the person the axes actually capture. If the first ratio is 0.31, that axis explains about a third of the variation in their signals. If `cumulative_variance` is 0.7, the kept axes explain about seventy percent and the rest is noise or structure too fine to name. Low cumulative variance means the axes are real but partial.

**`silhouette_score`** says how separated the basins are. Near 1 means tight, well-separated clusters. Near 0 means the basins overlap enough that the boundary between them is not meaningful, and you should present the basin name as a tendency rather than a label. Negative means the clustering did not find structure.

Neither number is a pass or fail. They are how you decide how strongly to phrase what you show a user.

## What a basin shift means for you

`basinHistory` is the recorded series of positions, oldest first. Each snapshot names the basin the subject was in at that moment:

```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
[
  { "timestamp": 1751328000.0, "basin": "architecture_review", "activations": [0.55, -0.49, 0.21, -0.04], "energy": 0.31, "trigger": "how should we structure the ingest pipeline" },
  { "timestamp": 1752537600.0, "basin": "rapid_implementation", "activations": [0.62, 0.41, -0.08, 0.13], "energy": 0.22, "trigger": "just get the endpoint up, we can refactor" }
]
```

When the basin changes between snapshots, the subject has moved into a different working mode. That is the moment a conditioned agent should change how it behaves: someone in an architecture-review basin wants the tradeoffs laid out, and the same person in a rapid-implementation basin wants the code.

You do not have to poll for this. `basin.shifted` is one of the event types on [`GET /v1/events`](/api-reference/events/stream-events) and on webhooks, so you can react to the move rather than diffing snapshots yourself. See [Reacting to change](/guides/reacting-to-change).

The most direct use is not reading the manifold at all: [`POST /v1/condition/session`](/api-reference/condition/post-condition-session) already folds the current basin into a system prompt for you. Read the raw state when you are building your own UI over it, and use the [ConditioningPack](/concepts/conditioning-pack) when you want the behaviour.

<AccordionGroup>
  <Accordion title="Why is manifoldState null?" icon="circle-question">
    Because no discovery pass has run for this subject yet. Two ordinary causes: the background cognition cycle has not reached them since their content landed, or there are too few identity signals to decompose. A handful of signals has no stable axes to find, and reporting invented ones would be worse than reporting nothing.

    Null is the honest answer, not an error. Everything else in the identity view fills in independently, so `voiceProfile` or `recentPatterns` can be populated while `manifoldState` is still null.
  </Accordion>

  <Accordion title="Why do the dimension names read like they were written by a model?" icon="circle-question">
    Because they were. The axes are found numerically and then named from the content that loads most strongly at each pole. The numbers are the discovery; the names are a readable gloss on them. If a name looks wrong, `description` and the two pole labels give you enough to judge it, and `variance_ratios` tells you how much weight that axis deserves in the first place.
  </Accordion>

  <Accordion title="Why did the axes change between two reads?" icon="circle-question">
    A refresh re-runs discovery over the accumulated signals. Exo aligns each new pass to the previous one so an axis keeps its identity where it can, and nudges basin centres rather than replacing them, so the surface evolves instead of resetting. But when a subject's content genuinely shifts, axes are relabelled and basins can be born or retired. Treat `dimensions` as a snapshot with a `discovered_at`, not as a stable schema.
  </Accordion>

  <Accordion title="Is basinHistory the same as trajectory?" icon="circle-question">
    They describe the same movement. `trajectory` is the field named on the `ManifoldState` object itself; `basinHistory` is the recorded series returned alongside it on the identity view. Both are ordered oldest first and both give you position plus basin per snapshot. If you only need "which mode are they in now", read the last entry, or read `basins` and take the dominant one.
  </Accordion>
</AccordionGroup>

## Limits

* `manifoldState` is null until the first discovery pass. There is no route that triggers one on demand.
* The object is snake\_case while the rest of the API is camelCase. This is deliberate, since the field names are the stored discovery keys.
* Extra diagnostic keys appear beyond those documented here. Treat unknown keys as forward-compatible rather than an error.
* Dimension count, names and order are all discovered. None of them is a contract.
* There is no route that returns a position for an arbitrary piece of text. The manifold describes a subject, not a message.

## Next

<Columns cols={2}>
  <Card title="ConditioningPack and VoiceProfile" icon="message-square-quote" href="/concepts/conditioning-pack">
    The basin turned into a prompt your own model can use.
  </Card>

  <Card title="Recall cognitive state" icon="code" href="/api-reference/recall/get-recall">
    The endpoint behind all four views, including `identity`.
  </Card>
</Columns>
