Documentation menu
Entity graph

Every synthesis pass extracts entities and relationships into a bi-temporal knowledge graph — who a user knows, what they use, where they work, and when each of those was true.

What gets extracted

The synthesis worker identifies five entity types:

TypeExamples
person"Alex", "my manager Sarah"
org"Stripe", "Acme Corp", "Y Combinator"
tech"Next.js", "PostgreSQL", "BullMQ"
project"Project Atlas", "the payments rewrite"
location"San Francisco", "HQ"

And six relationship types:

RelationshipMeaning
works_atperson → org
usesperson/project → tech
knowsperson → person
member_ofperson → org/project
reports_toperson → person
part_ofproject → org

Bi-temporal edges

Each relationship is stored as an edge with validity bounds:

json
{ "relationship": "works_at", "target": { "id": "...", "type": "org", "name": "Stripe" }, "validFrom": "2024-01-01T00: 00: 00Z", "validUntil": null, "current": true }
  • validFrom — when this relationship became true (derived from chunk timestamps)
  • validUntil — when it stopped being true; null means currently active
  • current — convenience boolean, true when validUntil is null

If synthesis later learns "Alex left Stripe and joined Anthropic", the Stripe edge gets validUntil set and a new Anthropic edge is created. Both are preserved — you get the full history, not a snapshot. Edges also carry a second, independent knowledge-time axis (when Anansi learned each boundary) — see temporal memory for asOf and asOfKnowledge point queries.

How to read the graph

Every GET /v1/context response includes entities[]:

typescript
"kw">const ctx = "kw">await memory.context({ userId: 'user_123' }); for ("kw">const entity of ctx.entities) { console.log(entity.name, entity."kw">type); for ("kw">const rel of entity.relationships) { "kw">if (rel.current) { console.log(` → ${rel.relationship} ${rel.target.name}`); } } }

Response shape (same as GET /v1/entities):

json
{ "entities": [ { "id": "a1b2c3...", "type": "person", "name": "Alex", "relationships": [ { "relationship": "works_at", "target": { "id": "d4e5f6...", "type": "org", "name": "Stripe" }, "validFrom": "2024-01-01T00: 00: 00Z", "validUntil": null, "current": true }, { "relationship": "works_at", "target": { "id": "g7h8i9...", "type": "org", "name": "Google" }, "validFrom": "2022-01-01T00: 00: 00Z", "validUntil": "2024-01-01T00: 00: 00Z", "current": false } ], "firstSeen": "2026-06-01T00: 00: 00Z", "lastSeen": "2026-06-13T12: 00: 00Z" } ] }

For graphs you want to render or query independently, use the dedicated endpoint:

shell
"kw">curl "https://anansimemory.com/v1/entities?userId=user_123" \ -H "Authorization: Bearer ans_..."

Temporal facts in context

Alongside the entity graph, GET /v1/context returns temporal[] — a flat list of time-bounded facts extracted in the same synthesis pass:

json
{ "temporal": [ { "fact": "Works at Stripe", "validFrom": "2024-01", "validUntil": null, "current": true }, { "fact": "Worked at Google", "validFrom": "2022-01", "validUntil": "2024-01", "current": false }, { "fact": "Led the payments team", "validFrom": "2023-06", "validUntil": "2024-01", "current": false } ] }

This complements the entity graph: entities[] is structured and queryable; temporal[] is for prompt injection where you want to give the LLM a clean timeline. Both support point-in-time queries via asOf — see Temporal memory & point-in-time queries.

How to use entities in a prompt

typescript
"kw">const ctx = "kw">await memory.context({ userId: 'user_123' }); "kw">const currentOrg = ctx.entities .find(e => e."kw">type === 'person' && e.name === 'Alex') ?.relationships.find(r => r.relationship === 'works_at' && r.current) ?.target.name; "kw">const techStack = ctx.entities .filter(e => e."kw">type === 'tech') .map(e => e.name); "kw">const systemPrompt = ` ${memory.formatForPrompt(ctx)} Current employer: ${currentOrg ?? 'unknown'} Tech stack: ${techStack.join(', ')} `.trim();

When entities are populated

The entity graph is populated after the first synthesis run for a user. Synthesis is triggered automatically after every POST /v1/ingest call, but runs asynchronously — typically within a few seconds. A user who has never been synthesized returns entities: [].

To confirm synthesis has run, check that static[] is non-empty in GET /v1/context. Entities and temporal facts are extracted in the same pass as the static/dynamic profile.

Note
On the hosted service, the entity graph and GET /v1/entities are Pro+ features.