RememberStackremember.dev/docs

Graph

The live graph is the set of current relations seen as edges between entities (the nodes). Three routes walk it: the neighbourhood of one entity, the shortest paths between two entities, and citation chains between two documents. They read PostgreSQL directly, inside one read-only snapshot, so every answer is consistent with itself.

All three use POST because their arguments do not fit a query string. They only read and need the read scope. Base URL, authentication and error shapes are described in HTTP API conventions.

Bounds every traversal shares

Each traversal runs under fixed budgets. When one is reached, the result says so in truncation.reason instead of stopping silently.

BudgetValuetruncation.reason when reached
Edges examined2,000expansion_budget
Frontier size1,000frontier_budget
Traversal time1,000 mstime_budget
Resultsthe route's limitresult_budget
Statement timeout5 snone: the request fails with 503 live graph timed out

Two traversals run at a time by default (REMEMBERSTACK_SELFHOST_GRAPH_MAX_CONCURRENCY, default 2). A request that cannot get a slot within the pool wait (default 1 second) is refused with 503 live graph is busy.

Time

valid_at and believed_at are the two clocks of a bitemporal read: the instant in the world you ask about, and the instant of the memory's knowledge you ask from. Send both or neither. With neither, both are the time the request runs. The result's temporal_scope always has mode as_of and names both clocks. Send them in UTC (Z or +00:00); a timestamp with no offset or another offset is refused with 422. See Time.

POST /graph/neighborhood

Return the entities within a number of hops of one entity, nearest first, optionally with the path to each.

Request body

FieldTypeRequiredDefaultConstraints
entity_idUUIDyes
hopsintegerno21 to 4.
predicatesarray of stringno[] (all)At most 100 items, each 1 to 200 characters. Only edges with these predicates are followed.
valid_atdate-timenonowSend with believed_at.
believed_atdate-timenonowSend with valid_at.
limitintegerno5001 to 500 entities per page.
continuationstringnoThe truncation.continuation from the previous page. At most 200 characters.
include_pathsbooleannofalseAlso return one path to each entity.

Unknown fields are rejected.

Response

200 with an Envelope of grain fact:

  • nodes: the entities reached (GraphNode), each with its hop distance.
  • paths and edges: with include_paths, one GraphPath per entity and the distinct GraphEdge entries they use. Empty otherwise.
  • truncation: always present. When more entities exist, truncated is true and continuation holds the cursor for the next page.
SituationResult
entity_id not in the live graphnegative.kind unknown_entity
The entity exists but no neighbour matchesnegative.kind known_empty
continuation is not a cursor this route issuednegative.kind boundary

Errors

StatusdetailCause
422validation listOut-of-range hops or limit, too many or too long predicates, only one of valid_at and believed_at, a clock without a zero UTC offset, or an unknown field.
503live graph is busyNo traversal slot was free in time. Retry.
503live graph result unavailableThe traversal and the rows it pointed at disagreed. Retry.
503live graph timed outThe traversal ran past its statement timeout, or its database connection failed. Retry with back-off; narrow the request if it persists.

Example

curl -s -X POST "$REMEMBER_API_URL/graph/neighborhood" \
  -H "Authorization: Bearer $REMEMBER_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"entity_id\": \"$BILLING_MIGRATION_ID\", \"hops\": 2, \"limit\": 100, \"include_paths\": true}"
from remember import Client
 
memory = Client()
page = memory.graph_neighborhood(entity_id=billing_migration_id, hops=2, limit=100)
while True:
    for node in page.nodes:
        print(node.hops, node.name)
    if page.truncation is None or page.truncation.continuation is None:
        break
    page = memory.graph_neighborhood(
        entity_id=billing_migration_id,
        hops=2,
        limit=100,
        continuation=page.truncation.continuation,
    )

POST /graph/path

Return the shortest paths between two entities. All returned paths have the same, shortest length.

Request body

FieldTypeRequiredDefaultConstraints
from_entity_idUUIDyes
to_entity_idUUIDyes
max_hopsintegerno41 to 6.
predicatesarray of stringno[] (all)At most 100 items, each 1 to 200 characters.
valid_atdate-timenonowSend with believed_at.
believed_atdate-timenonowSend with valid_at.

Unknown fields are rejected. At most 10 paths are returned.

Response

200 with an Envelope of grain fact. paths holds the paths, each whole: if any edge of a path no longer holds, the whole path is dropped rather than shortened. nodes and edges list the distinct entities and relations the paths use. truncation is always present.

SituationResult
Either entity not in the live graphnegative.kind unknown_entity
No path within max_hopsnegative.kind known_empty

Errors

As for POST /graph/neighborhood.

Example

curl -s -X POST "$REMEMBER_API_URL/graph/path" \
  -H "Authorization: Bearer $REMEMBER_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"from_entity_id\": \"$DANA_ID\", \"to_entity_id\": \"$RAVI_ID\", \"max_hops\": 3}"
envelope = memory.graph_path(from_entity_id=dana_id, to_entity_id=ravi_id, max_hops=3)

POST /graph/citation-path

Return directed citation chains from one document to another: document A cites B, B cites C, and so on.

Request body

FieldTypeRequiredDefaultConstraints
from_doc_idUUIDyes
to_doc_idUUIDyes
max_hopsintegerno61 to 6.

Unknown fields are rejected. At most 10 paths are returned. This route takes no clocks; it reads the current document graph.

Response

200 with an Envelope of grain fact, shaped like a path result but over documents:

  • each GraphNode is a document: entity_id holds the document id and name its title;
  • each GraphEdge is a citation: relation_id holds the cross-reference id, subject_id and object_id the citing and cited documents, predicate the kind of reference and fact its context text. evidence_count is 0 and the validity fields are null.

temporal_scope.mode is current when chains are found.

SituationResult
Either document not livenegative.kind unknown_entity
No chain within max_hopsnegative.kind known_empty

Errors

As for POST /graph/neighborhood.

Example

curl -s -X POST "$REMEMBER_API_URL/graph/citation-path" \
  -H "Authorization: Bearer $REMEMBER_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"from_doc_id\": \"$SPEC_DOC_ID\", \"to_doc_id\": \"$RFC_DOC_ID\"}"
envelope = memory.graph_citation_path(from_doc_id=spec_doc_id, to_doc_id=rfc_doc_id)