Skip to content

Sessions

Adding turns

A turn is a single message in a conversational thread. After a session is open, every message - whether from the user, the assistant, the system, or a tool - is recorded as a turn. SurrealDB Agent Memory processes each turn synchronously through an extraction pipeline and returns a structured diff of everything that was learned or changed.

A turn is written with the /facts endpoint. Pass the session id as session_id and the speaker as role:

POST /api/v1/{context_id}/facts
Content-Type: application/json

{
  "text": "I just got promoted to CTO",
  "session_id": "01hx7…",
  "role": "user"
}

role defaults to user. When session_id is left out, SurrealDB Agent Memory creates a new session for the write and returns its id as sessionId in the response.

The request blocks until extraction is complete, then returns the structured result. Extraction is synchronous by design: the caller can use the returned diff to update application state, drive real-time UI, or trigger downstream logic without polling.

RoleWhen to use
userMessages from the human participant. Extraction is most active on user turns.
assistantResponses generated by the LLM. Recording assistant turns preserves the full conversational context and allows SurrealDB Agent Memory to track what the assistant has stated or committed to.
systemSystem prompt or instruction turns. SurrealDB Agent Memory extracts behavioural instructions and constraints from system turns.
toolResults returned from tool calls. SurrealDB Agent Memory extracts factual content from tool results and attributes it to the tool source.

All four roles participate in provenance. Each turn is stored with its role, and every fact extracted from it carries a source_turn reference regardless of role.

When a turn arrives, SurrealDB Agent Memory runs the following steps synchronously before returning:

  1. Named entity recognition - identifies people, organisations, locations, products and the other entity types in the fixed set. A type outside that set is stored as other.

  2. Attribute extraction - extracts scalar properties of entities (job title, location, preference, status, and so on).

  3. Relation extraction - identifies directed relationships between entities.

  4. Instruction detection - recognises statements that express user preferences, behavioural instructions, or explicit requests about how the agent should behave.

  5. Uncertainty flagging - marks statements that are speculative, negated, or otherwise of low confidence.

  6. Reconciliation - compares newly extracted facts against existing memory. Facts that contradict prior records are resolved and a correction record is produced. See Reconciliation and supersession for the full correction model.

result = await memory.remember("I just got promoted to CTO", session_id=session.id, role="user")
const result = await memory.remember("I just got promoted to CTO", { sessionId: session.id, role: "user" });

The response to a write contains the session and turn ids, and an extraction object with the full diff for that turn.

{
  "mode": "full",
  "sessionId": "01hx7…",
  "turnId": "01hy2…",
  "extraction": {
    "turnId": "01hy2…",
    "entities": [
      {
        "id": "['person', 'alice']",
        "name": "Alice",
        "entityType": "person",
        "memoryCategory": "identity",
        "isNew": false
      }
    ],
    "attributes": [
      {
        "id": "attr_01hy2b",
        "entityId": "['person', 'alice']",
        "key": "job_title",
        "value": "CTO",
        "memoryCategory": "identity"
      }
    ],
    "relations": [],
    "actions": [],
    "instructions": [],
    "uncertainties": [],
    "corrections": [
      {
        "entityId": "['person', 'alice']",
        "key": "job_title",
        "oldValue": "Head of Platform",
        "newValue": "CTO"
      }
    ]
  },
  "preview": false
}

The stable identifier for this turn. Store it to correlate later reads - for example to label a /state snapshot taken immediately after the turn.

Entities named or implied in the turn content. Each entity carries:

  • id - stable record identifier

  • name - the canonical display name

  • entityType - entity type (e.g. person, organisation, project)

  • memoryCategory - identity, knowledge, or context

  • isNew - true when this turn created the entity, false when it matched an existing one

An entity record is created once and then referenced by subsequent attributes and relations. If an entity was already known, the existing record is returned rather than a duplicate.

Scalar facts about entities. Each attribute carries:

  • id - the attribute record identifier

  • entityId - the entity the attribute belongs to

  • key - the attribute name (e.g. job_title, location, preference)

  • value - the extracted value

  • memoryCategory - the category the attribute was filed under

Attributes are versioned. If the same key already exists for an entity and the new value differs, the prior attribute is superseded rather than overwritten. The supersession chain is preserved so the history of a fact is always queryable. Validity times and provenance are stored on the attribute record and returned by /state and GET /entities/{type}/{name}/history/{key}.

Directed relationships between two entities. Each relation carries:

  • subject - source entity

  • label - relationship type (e.g. works_at, manages, owns)

  • object - target entity

  • memoryCategory - the category the relation was filed under

Events the turn describes: who did what, and when. Each action carries actor, verb, object (the thing acted on, as the extraction named it, or null), a one-line summary, occurredAt when the turn gave a time, and memoryCategory.

Behavioural directives extracted from the turn. These are statements where the user or system is expressing a persistent preference or rule, such as "always reply in French" or "never mention competitors by name." Instructions are surfaced in the context payload and injected into prompts automatically when using the managed chat loop.

Each instruction carries:

  • id - the instruction record identifier

  • label - a short name for the directive

  • description - the normalised instruction text

Statements extracted from the turn that SurrealDB Agent Memory cannot assert with confidence - negated claims, speculative language, conflicting signals, or information that requires human verification. Each carries about and reason. These are stored separately from confirmed attributes and are surfaced in the state under unknowns.

When reconciliation finds that a newly extracted fact contradicts an existing attribute, a correction record is produced. Each correction carries:

  • entityId - the entity whose attribute changed

  • key - the attribute name

  • oldValue - the value that was superseded

  • newValue - the value from this turn

Corrections are the mechanism through which SurrealDB Agent Memory handles conversational drift - for example, when a user corrects a name, updates their role, or revises a stated preference. See Reconciliation and supersession for how the supersession chain works.

Because the extraction result returns synchronously with the turn response, you can use it to drive application state immediately after each message is submitted.

A common pattern is to compare the returned arrays against your local state and highlight changes in the UI:

result = await memory.remember("Actually I moved to Berlin last month", session_id=session.id, role="user")

for attr in result.extraction.attributes:
    ui.render_attribute(attr["entityId"], attr["key"], attr["value"])

for correction in result.extraction.corrections:
    ui.strike_through(correction["oldValue"])
    ui.highlight_new(correction["newValue"])
const result = await memory.remember("Actually I moved to Berlin last month", { sessionId: session.id, role: "user" });

for (const attr of result.extraction?.attributes ?? []) {
    ui.renderAttribute(attr.entityId, attr.key, attr.value);
}

for (const correction of result.extraction?.corrections ?? []) {
    ui.strikeThrough(correction.oldValue);
    ui.highlightNew(correction.newValue);
}

When you are driving the LLM loop yourself (as opposed to using chat()), record the assistant's response as a turn after each generation:

user_result = await memory.remember(user_message, session_id=session.id, role="user")

context = await memory.sessions.context(session.id, user_message)
reply = your_llm.generate(system=context.context, user=user_message)

assistant_result = await memory.remember(reply, session_id=session.id, role="assistant")

Recording assistant turns preserves the full conversational record and allows SurrealDB Agent Memory to extract any facts the assistant stated - commitments, stated knowledge, or preferences expressed on behalf of the agent. Omitting assistant turns is permitted, but the resulting memory will have gaps that could reduce context quality on subsequent retrieval.

To retrieve all turns recorded in a session:

GET /api/v1/{context_id}/sessions/{session_id}/turns
turns = await memory.sessions.turns(session.id)
const turns = await session.turns();

The response is a list of turns, oldest first, with their roles, content, and timestamps. It is paginated: limit defaults to 100 and is capped at 500, and page.nextCursor gives the next page. Extraction results are not re-included in this listing; use the /state endpoint to query the facts that were produced.

Was this page helpful?