Skip to main content
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.
200 application/json

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.

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:
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 and on webhooks, so you can react to the move rather than diffing snapshots yourself. See Reacting to change. The most direct use is not reading the manifold at all: POST /v1/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 when you want the behaviour.
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.
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.
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.
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.

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

ConditioningPack and VoiceProfile

The basin turned into a prompt your own model can use.

Recall cognitive state

The endpoint behind all four views, including identity.