Skip to content

Showcase

/

Robin Hood character graph

Build your own character graph

This guide shows how to turn any text that arrives in parts - a novel, a series, a set of reports - into a graph of who is in it at each point, like the Robin Hood character graph. It covers the Agent Memory side in full and leaves the drawing to whatever you use for front ends.

When you finish, you will have a Context that holds your text part by part, and the three reads a graph needs at any reading position: the cast, what is known about each character, and how they are related.

You need:

  • A SurrealDB Agent Memory Context, its id and an API key with memory:read and memory:write. See Hosted quickstart.

  • A text split into parts, in reading order. Public-domain books from Project Gutenberg are a good source.

  • An HTTP client. The examples below are raw HTTP requests, which any language can send.

The Agent Memory features this guide uses are:

FeatureWhat it does here
Document upload with observedAtStamps each part with its place on a narrative clock
asOf on entity readsReads the memory as it was at a reading position
SupersessionKeeps a replaced value, with the time it stopped being current
RelationsLinks characters, each relation dated to the part it was learned in
Scopes and labelsOptional: keep one book separate from another in a shared Context

Give each part a date, so that the order of the dates is the order of the text. The date does not have to mean anything outside the book. The Robin Hood demo dates chapter 1 to 1 January 2000, chapter 2 to 2 January, and so on:

PartobservedAt
Chapter 12000-01-01T00:00:00Z
Chapter 22000-01-02T00:00:00Z
Chapter 222000-01-22T00:00:00Z

A reading position is then just one of these dates, passed later as asOf.

Upload each part as a document, with its date as observedAt in the metadata. Every fact extracted from the part takes that date as its known time.

POST /api/v1/<context-id>/documents
Content-Type: multipart/form-data

file=<chapter-01.txt>
metadata={"title":"Chapter 1","observedAt":"2000-01-01T00:00:00Z","labels":["chapter=1"]}

Upload one part at a time, and wait for each one to finish before the next. Extraction is shown the entities the Context already holds and told to reuse their names, so a part that starts before the previous one is finished can create a second entity for a name it should have matched. Poll the document until its status is ready, then wait a few seconds more for the extracted facts to be written.

Important

Upload one part first and check that GET /api/v1/<context-id>/entities returns entities before you upload the rest. If it returns none, extraction is not running, and every later part would report ready with nothing extracted.

List the entities the memory held at a reading position by passing that part's date as asOf:

GET /api/v1/<context-id>/entities?asOf=2000-01-06T00:00:00Z

That is the cast as of chapter 6. Anything first mentioned after chapter 6 is missing from the list, which is what keeps the graph free of spoilers. To size each character by how much is known about them, count the facts on each entity at the same asOf.

Read one entity at a reading position:

GET /api/v1/<context-id>/entities/person/little_john?asOf=2000-01-06T00:00:00Z

To show how a character changed through the book, read their history. Every value is there, including values a later part replaced:

GET /api/v1/<context-id>/entities/person/little_john/history

A replaced value is superseded rather than overwritten: it keeps the time it became known and gains the time it stopped being current. A graph can use that to strike out a value the reader has moved past, while still showing it for the chapters where it was true. Reconciliation and supersession explains when a new value supersedes an old one.

Read the characters one step out from an entity, with the relations between them, at a reading position:

GET /api/v1/<context-id>/entities/person/robin_hood/neighbourhood?asOf=2000-01-06T00:00:00Z&minFacts=2

minFacts leaves out characters the memory knows almost nothing about. Relations are dated like attributes, so a relation learned in chapter 8 is missing at chapter 6. A graph can draw each relation in at the part it was learned in.

Expect a character to appear under several names. Extraction creates one entity for each name, so Robin and Robin Hood, or Tuck and Friar Tuck, arrive as separate entities. You can:

  • Show them as separate entities, which is what the memory holds.

  • Link them in your graph, and label the links as your own suggestion. The Robin Hood demo links names of the same type where every capitalised word of the shorter name is in the longer one.

  • Map the variants to one name in your own pipeline before you upload, if one entity per character is all you need.

Entities with several names describes the cases, including the ones that must stay separate.

With the reads above, each frame of the graph is:

  1. The entities returned asOf the reading position, sized by their fact count.

  2. The relations between them, asOf the same position.

  3. For the character under the pointer, their facts at that position, with replaced values struck out.

Move the reading position and repeat the three reads. Caching one frame per part keeps the graph responsive, since a part's frame never changes once the text is ingested.

Pick a character who is introduced partway through the text. Confirm that:

  • They are missing from GET /entities asOf the part before their introduction.

  • They are present asOf the part that introduces them.

  • Their history shows at least one value with an end time, if anything about them changes later in the text.

Was this page helpful?