The state endpoints give you a structured, queryable view of everything SurrealDB Agent Memory has learned within the caller's read region. Rather than replaying the conversation history, you query the current knowledge graph directly - grouped by category - and receive a clean, structured representation of what the agent knows.
The /state endpoint
GET /api/v1/{context_id}/state?limit=100limit is the only parameter. It bounds each of the six underlying tables (entities, attributes, relations, actions, instructions, and unknowns), defaults to 100, and is capped at 500. The response covers everything the calling key can read; there is no scope filter on this endpoint.
Response:
{
"identity": {
"entities": [
{
"id": "['person', 'alice']",
"name": "Alice",
"entityType": "person",
"memoryCategory": "identity",
"importance": 1.0,
"createdAt": "2026-05-12T16:24:00Z",
"updatedAt": "2026-05-12T16:24:00Z"
}
],
"attributes": [
{
"id": "attr_01hy2c",
"entity": "entity:person/alice",
"key": "job_title",
"value": "CTO",
"memoryCategory": "identity",
"importance": 1.0,
"validFrom": "2026-05-12T16:24:00Z",
"validUntil": null,
"supersedes": "attr_01hy1b",
"supersededBy": null,
"source": { "kind": "turn", "ref": "turn:01hy2…", "sessionId": "01hx7…" },
"createdAt": "2026-05-12T16:24:00Z"
}
],
"relations": [],
"actions": []
},
"knowledge": { "entities": [], "attributes": [], "relations": [], "actions": [] },
"context": { "entities": [], "attributes": [], "relations": [], "actions": [] },
"instructions": [
{
"id": "instr_01hy4d",
"label": "language",
"description": "Always respond in German when the user writes in German."
}
],
"unknowns": [
{
"about": "Whether Alice holds any equity in Acme.",
"reason": "Equity was never mentioned in the conversation."
}
],
"truncated": {
"entities": false,
"attributes": false,
"relations": false,
"actions": false,
"instructions": false,
"unknowns": false
}
}Response structure
| Field | Description |
|---|---|
identity | Biographical facts, roles, and names - as entities, attributes, relations, and actions. |
knowledge | Preferences, opinions, and skills, in the same four-part shape. |
context | Situational and recent-event memory, in the same four-part shape. |
instructions | Standing behavioural directives: id, label, description. |
unknowns | Things extraction could not resolve: about and reason. |
truncated | For each table, true when it had more rows than limit. |
The three memory categories share one shape - each holds entities, attributes, relations, and actions.
/state is a bounded snapshot, not an export. To read a table completely, page through /entities, /attributes, /relations, or /actions with page.nextCursor instead.
The endpoint always returns the current, canonical view: superseded attributes are not included. Each returned attribute carries supersedes and supersededBy links, so you can walk backwards from the current value. Corrections are not part of this response - they come back on the write that made them, described in Showing what changed after a turn.
Showing what changed after a turn
There is no /state/diff endpoint, and /state takes no since parameter - but you rarely need one, because every write returns its own delta.
POST /facts responds with an extraction object describing exactly what that write produced:
| Field | What it holds |
|---|---|
turnId | The turn the extraction belongs to |
entities | Entities created or matched |
attributes | Attributes asserted |
relations | Relations asserted |
actions | Events the turn described |
instructions | Standing directives picked up |
uncertainties | Things the extractor could not resolve |
corrections | Values this write superseded, with both sides |
A supersession is reported with the value it replaced, so no second request and no comparison work are needed. Read /state for the full picture at a point in time, and GET /entities/{type}/{name}/history/{key} to follow one attribute's ordered supersession chain.
Python SDK
state = await memory.state()
print(state.identity["attributes"]) # Current attribute list
print(state.instructions) # Active behavioural instructions
print(state.unknowns) # Flagged uncertaintiesGET /state takes only limit - no scope, no time filter, no since. It returns everything the calling key can read, up to limit rows per table. Narrow the result yourself, or read a single entity with entities.get(…). The JavaScript SDK passes limit as memory.state({ limit: 200 }).
To read as another principal, send the X-Spectron-On-Behalf-Of header. That is a request header available on every end-user route rather than a state() argument, and the effective authority is the intersection of your grants and the target's - delegation never widens access.
JavaScript SDK
const state = await memory.state();
console.log(state.identity.attributes);
console.log(state.instructions);
console.log(state.unknowns);Profile endpoint
The profile endpoint returns a richer, opinionated view of a single user's memory - pre-formatted for prompt injection or display in account or preferences UIs.
Python SDK
profile = await memory.profile()JavaScript SDK
const profile = await memory.profile();Response shape:
| Field | Description |
|---|---|
selfFacts | Facts about the calling principal's own entity, kept apart from facts about other people. |
static | Stable identity facts: name, role, organisation, contact details. |
dynamic | Frequently-updated attributes: location, status, current project, mood. |
preferences | Extracted preferences and stated likes or dislikes. |
instructions | Active behavioural directives applicable to this user. |
Each entry in selfFacts, static, dynamic, and preferences carries entity and entityType (the entity the fact is about) alongside key and value, so a fact about a person the user mentioned is not read as a fact about the user. The profile view does not include raw turn references. It is a synthesised snapshot intended for injection into system prompts or display in a "what does the AI know about me?" UI.
Driving real-time UI updates
Apply the extraction payload from each write straight to the UI - new attributes as additions, corrections as in-place updates:
result = await memory.remember(
"I moved to Berlin last month",
session_id=session_id,
)
for attr in result.extraction.attributes:
ui.add_fact(attr)
for c in result.extraction.corrections:
ui.animate_update(c["key"], old=c["oldValue"], new=c["newValue"])const result = await memory.remember("I moved to Berlin last month", { sessionId });
for (const attr of result.extraction.attributes) {
ui.addFact(attr);
}
for (const c of result.extraction.corrections) {
ui.animateUpdate(c.key, { old: c.oldValue, new: c.newValue });
}The corrections format in detail
Corrections are produced during reconciliation whenever a newly extracted attribute contradicts an existing one. Each correction record contains both sides of the change and the turn references that establish provenance:
{
"entityId": "entity:person/alice",
"key": "location",
"oldValue": "London",
"newValue": "Berlin"
}The correction is a summary of the change, not the full temporal record. The timestamps live on the attribute records themselves: the superseded record gets a valid_until and a superseded_by link, the new record a valid_from and a supersedes link. That is what makes historical queries - "what did the agent know about Alice's location in March 2026?" - return the right answer. Read the chain with GET /entities/{type}/{name}/history/{key}.
Corrections do not delete the superseded record. The full supersession chain for any attribute stays queryable, so the audit trail for a fact is complete unless it is removed on purpose: forget with purge: true also removes the superseded history, and deleting a session removes its turns. See Temporal validity and Reconciliation and supersession for the detailed model.