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

# ExoBrain

> The model Exo trains on one organization's own graph, when it serves a retrieval, and what happens when the graph outgrows it.

Retrieval-augmented systems usually bolt a general model onto a private index. The model has never seen your graph, so it can rank passages but it cannot tell you which nodes it leaned on or how sure it was. Exo trains a small model per organization, on that organization's own graph, and the model reports its own working state.

Ask what is loaded with [`GET /v1/brain/info`](/api-reference/brain/get-brain-info).

```json 200 application/json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "tenant_id": "0d5b7c92-3a41-4e88-9b02-6f7c1d4a5e30",
  "alias": "base",
  "backend": "onnx",
  "blob_path": "brains/0d5b7c92-3a41-4e88-9b02-6f7c1d4a5e30/base.onnx",
  "loaded": true,
  "model_params": 2841600,
  "graph_nodes": 846,
  "graph_edges": 5127,
  "training": {
    "status": "ready",
    "blobExists": true,
    "exportedNodes": 846,
    "exportedEdges": 5127,
    "modelParams": 2841600,
    "parityMaxDelta": 0.00021,
    "trainedAt": "2026-07-14T02:41:55Z",
    "trainSeconds": 903,
    "lastError": null,
    "lastJobId": "7c1e4a90-2f83-4d16-b5c7-0a94e1d3f286"
  }
}
```

The top-level keys are snake\_case and the nested `training` block is camelCase. That is how the route actually answers, and it is worth noticing before you write a parser.

## What it is

**ExoBrain**, a small graph neural model trained on one organization's knowledge graph (in practice, a file at `brains/<org>/base.onnx` that turns a query into a set of focal nodes plus a confidence).

**Focal nodes**, the nodes the model settled on as the centre of an answer (in practice, the ids that the reasoning converged to, which are usually not the same as the ids plain cosine similarity would have returned).

**Parity**, the check that the exported model gives the same numbers as the model that was trained (in practice, a hard gate: an export that drifts past a tiny tolerance is rejected and never published).

One brain per organization, not one per subject. It trains on the whole org's graph, and retrieval filters its output down to the nodes the effective subject actually owns. That is why deleting a subject marks the org's brain stale rather than deleting it.

## Where it comes from

Training is an admin-scope job, not a synchronous call. [`POST /v1/brain/train`](/api-reference/brain/post-brain-train) enqueues it and returns immediately.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  curl -X POST https://api.get-exo.com/v1/brain/train \
    -H "X-Exo-API-Key: $EXO_ADMIN_KEY" \
    -H "Content-Type: application/json" \
    -d '{"reason": "quarterly refresh", "epochsScale": 1.0}'
  ```

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

  r = requests.post(
      "https://api.get-exo.com/v1/brain/train",
      headers={"X-Exo-API-Key": os.environ["EXO_ADMIN_KEY"]},
      json={"reason": "quarterly refresh", "epochsScale": 1.0},
  )
  r.raise_for_status()
  job = r.json()
  print(job["jobId"], job["status"])
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  const r = await fetch("https://api.get-exo.com/v1/brain/train", {
    method: "POST",
    headers: {
      "X-Exo-API-Key": process.env.EXO_ADMIN_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ reason: "quarterly refresh", epochsScale: 1.0 }),
  });
  if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
  const job = await r.json();
  console.log(job.jobId, job.status);
  ```
</CodeGroup>

```json 202 application/json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "jobId": "7c1e4a90-2f83-4d16-b5c7-0a94e1d3f286",
  "orgId": "0d5b7c92-3a41-4e88-9b02-6f7c1d4a5e30",
  "status": "pending"
}
```

One training run per organization at a time. A second request while one is in flight returns 409 rather than queueing a duplicate. Watch progress in the `training` block of [`GET /v1/brain/info`](/api-reference/brain/get-brain-info), which reports `status: "none"` on an org that has never trained instead of failing.

The published artifact is only replaced when the new one passes parity. A run that fails, or that trains a model whose export does not match, leaves the previous good brain serving. You never get a worse brain by trying to retrain.

## Field reference

| Field                                              | Type              | What it tells you                                                                                                |
| -------------------------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------- |
| `loaded`                                           | boolean           | Whether the serving layer has the artifact in memory right now. False is normal on a cold process, not an error. |
| `backend`                                          | string            | Which runtime is serving.                                                                                        |
| `blob_path`                                        | string            | Where the artifact lives.                                                                                        |
| `model_params`                                     | integer or null   | Size of the loaded model.                                                                                        |
| `graph_nodes`, `graph_edges`                       | integer or null   | The graph the loaded model covers.                                                                               |
| `training.status`                                  | string            | `none` when the org never trained, otherwise the state of the most recent or in-flight run.                      |
| `training.exportedNodes`, `training.exportedEdges` | integer or null   | The graph size the artifact was exported at. Compare against `graph_nodes` and `graph_edges` to see drift.       |
| `training.parityMaxDelta`                          | number or null    | Largest divergence found by the parity check. Small is good.                                                     |
| `training.trainedAt`                               | date-time or null | When the current artifact was trained.                                                                           |
| `training.trainSeconds`                            | integer or null   | How long the run took.                                                                                           |
| `training.lastError`                               | string or null    | Why the last run failed, when it did.                                                                            |
| `training.lastJobId`                               | string or null    | The job to look up in [`GET /v1/jobs/{job_id}`](/api-reference/jobs/get-job).                                    |

## When the brain serves, and when it does not

A retrieval takes the brain path when the org has a trained artifact that still matches the graph. Otherwise it falls back to hybrid vector plus keyword search and says so:

| `degradationReason` | What happened                                                                  |
| ------------------- | ------------------------------------------------------------------------------ |
| `no_brain`          | The org has never trained one, or the artifact is missing.                     |
| `brain_stale`       | The graph moved under the brain.                                               |
| `cold_start`        | The fallback ran and matched nothing, because there is not enough content yet. |

Staleness has two triggers, and they behave differently:

* **Shape drift.** The artifact is exported at a fixed node and edge count. If the graph has grown or shrunk since, the brain refuses to run and retrieval falls back with `brain_stale`.
* **Content drift.** Editing a node's text or forgetting one does not change the counts, so the shape check cannot see it. Every mutation you make through the API stamps the org as dirty instead. Here the brain **still answers**, because a slightly stale brain is a better answer than no brain, and the response carries `degraded: true` with `degradationReason: "brain_stale"`.

The second case is the one that surprises people: `path: "brain"` and `degraded: true` together are a valid, expected combination. See [Brain path and hybrid path](/concepts/retrieval-paths).

## Reading the glass box directly

[`POST /v1/brain/query`](/api-reference/brain/post-brain-query) runs one forward pass and returns the reasoning state without wrapping it in a retrieval. It is the route behind an attention heat map or a "what did the model look at" view.

```json 200 application/json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "question": "Where did we land on the pricing model?",
  "tenant_id": "0d5b7c92-3a41-4e88-9b02-6f7c1d4a5e30",
  "alias": "base",
  "focal": [
    { "node_id": "ingest:4a1e88f0-9c2d-4f31-b7a0-5e6c1d820b93:7d3c9b1e04a2", "snippet": "Usage-based only. Seats punish the teams who adopt fastest.", "cosine_in_seed": true },
    { "node_id": "ingest:4a1e88f0-9c2d-4f31-b7a0-5e6c1d820b93:8e15b6c3a70d", "snippet": "Revisit after the June call.", "cosine_in_seed": false }
  ],
  "cosine_top": [
    { "node_id": "ingest:4a1e88f0-9c2d-4f31-b7a0-5e6c1d820b93:7d3c9b1e04a2", "index": 12, "score": 0.913 }
  ],
  "confidence": 0.83,
  "drift_rate": 0.02,
  "tier": "growing",
  "use_system2": false,
  "system_used": "system1",
  "subgraph": { "nodes": 846, "edges": 5127 },
  "negative_edges_in_subgraph": 37,
  "model_params": 2841600,
  "latency_ms": 118.4,
  "contradictions": []
}
```

`cosine_top` is the plain nearest-neighbour list, included so you can compare it against `focal`. A focal node with `cosine_in_seed: false` is one the model reached through the graph that similarity alone would have missed. That gap is the clearest single demonstration of what the brain adds.

<AccordionGroup>
  <Accordion title="Why does the brain family ignore X-Exo-Subject?" icon="circle-question">
    [`POST /v1/brain/query`](/api-reference/brain/post-brain-query) and [`POST /v1/brain/answer`](/api-reference/brain/post-brain-answer) read the API key owner's own content, so the subject selector is not accepted on them. If you need per-subject reasoning output, use [`POST /v1/retrieve`](/api-reference/retrieve/post-retrieve) with `X-Exo-Subject`, which returns the same reasoning state in its `cognition` block already filtered to that subject's nodes.
  </Accordion>

  <Accordion title="Why 404 and 409 on a brain query" icon="circle-question">
    404 means no artifact exists for the org yet: train one. 409 means an artifact was just published and the serving layer has not picked it up. The serving layer reloads when the training timestamp changes, so a 409 clears on its own shortly after a train; retry rather than retraining.
  </Accordion>

  <Accordion title="What makes a training run fail permanently" icon="circle-question">
    Two failures are terminal on the first attempt rather than retried. A parity failure means the exported artifact did not reproduce the trained model, so republishing it would serve numbers nobody verified. Too few nodes means the graph is too small to train anything meaningful. Neither is worth burning GPU time on three times, so both stop immediately and report through `training.lastError`.
  </Accordion>

  <Accordion title="Does deleting a subject destroy the brain?" icon="circle-question">
    No. [`DELETE /v1/subjects/{subject_id}`](/api-reference/subjects/delete-subject) erases that subject's rows and marks the org's brain stale, because the brain was trained across every subject. Retrieval keeps working on the fallback path until you retrain. The erased subject's content is gone from the graph either way, and retrieval filters brain output to nodes that still exist.
  </Accordion>
</AccordionGroup>

## Limits

* One brain per organization. There is no per-subject brain and no route to request one.
* One training run in flight per org. A concurrent request is refused with 409, not queued.
* `GET /v1/brain/info` has no typed response schema in the contract. The `training` block is the stable, documented part; treat unknown top-level keys as additive.
* The brain's `tier` reports graph size, not reasoning depth. See [Cognition](/concepts/cognition).
* Training cost is real GPU time and is metered. It is not something to run on every write.

## Next

<Columns cols={2}>
  <Card title="Brain path and hybrid path" icon="split" href="/concepts/retrieval-paths">
    What `path`, `degraded` and `degradationReason` mean on a retrieval.
  </Card>

  <Card title="Get ExoBrain status" icon="code" href="/api-reference/brain/get-brain-info">
    The full info response and the training block.
  </Card>
</Columns>
