Skip to content

Building memory UIs

Endpoints shaped for user-facing memory features — graph rendering, a memories feed, merge, and delete.

Updated View as Markdown

These endpoints are shaped for building user-facing memory features: an interactive graph view, a “Memories” tab, and the management actions (merge duplicates, delete) that go with them.

Memory Graph (Visualization)

Returns the full knowledge graph — entities (nodes) and their relationships (edges) — for a user. Designed for rendering an interactive graph in your frontend.

GET https://your-tie-host/memories/graph
Authorization: Bearer $TOKEN
Parameter In Required Description
agent_id query No Scope to a specific agent. Omit to get memories across all agents.
graph_id query No Render a graph namespace instead of the user’s graph. Requires a service token. Mutually exclusive with agent_id; no subject user needed.
limit query No Max edges per page (default 200, max 500)
cursor query No Opaque keyset cursor from the previous page’s next_cursor, passed back verbatim (<iso-datetime>|<edge-id>). Malformed values return 400 VALIDATION_ERROR.

Which entities appear

The response is one graph: the deduplicated entity nodes (User / Person / Topic / HealthMetric / Document / …) and the RELATES_TO facts between them. Every entity with a live fact in scope appears, whatever produced it — a chat exchange, an agent-tool write, or an API ingest of any type.

Two things never affect what is shown. source_description is a provenance label for your own debugging and record resolution; it does not classify episodes. And no entity type is filtered out, so a book, article, or report the user brought in shows as a Document node like any other entity.

Episodes themselves are not nodes in this response. They stay stored verbatim; GET /memories/export?include_episodes=true returns them. A UI that grouped nodes labelled Note (for example a “Notes” section) now finds that group empty — the notes’ extracted entities appear under their own types instead.

Response:

{
  "nodes": [
    {
      "id": "node-uuid-1",
      "name": "Fauzaan",
      "summary": "You work as a Staff Engineer at Mindvalley and have a cat named Lucy.",
      "summary_raw": "Fauzaan is a user.\nFauzaan was promoted to Staff Engineer at Mindvalley.\nFauzaan has a cat named Lucy.",
      "labels": ["User"],
      "attributes": {},
      "created_at": "2026-04-07T10:30:00+00:00"
    },
    {
      "id": "node-uuid-2",
      "name": "Mindvalley",
      "summary": "You were promoted to Staff Engineer at Mindvalley.",
      "summary_raw": "Mindvalley is the company where Fauzaan works.\nFauzaan was promoted to Staff Engineer at Mindvalley.",
      "labels": ["Topic"],
      "attributes": {},
      "created_at": "2026-04-07T10:30:00+00:00"
    }
  ],
  "edges": [
    {
      "id": "edge-uuid-1",
      "source": "node-uuid-1",
      "target": "node-uuid-2",
      "label": "works_at",
      "fact": "Fauzaan works at Mindvalley",
      "created_at": "2026-04-07T10:30:00+00:00",
      "valid_at": "2026-04-07T10:30:00+00:00"
    }
  ],
  "next_cursor": null
}

Each node is an entity (person, place, project, concept) with:

  • id — unique identifier, use as the node key in your graph
  • name — display label
  • summary — display-ready prose. Humanized rewrite when the background humanizer has run for this node (addresses the user as “you/your” when relevant), with automatic fallback to the raw graphiti string otherwise. Good for tooltips, popovers, or the entity-detail panel.
  • summary_raw — graphiti’s untouched newline-joined edge facts (third-person, dossier-style). Useful for a “show original” toggle in the UI or for debugging extraction quality.
  • labels — entity types like User, Person, Topic, Goal, Insight
  • attributes — extracted structured attributes (varies per entity type; may be empty)
  • created_at — when the entity was first written to the graph

Each edge is a fact connecting two nodes:

  • source / target — node IDs this edge connects
  • label — relationship type (e.g. works_at, knows, prefers)
  • fact — the full natural-language statement (third-person; edge-fact humanization is on the roadmap)
  • created_at / valid_at — when the fact was recorded / became true

Pagination: When next_cursor is not null, pass it as ?cursor={value} to fetch the next page. Each page returns up to limit edges and all nodes referenced by those edges.

Example with pagination:

# First page
GET /memories/graph?limit=100

# Next page (using next_cursor from previous response)
GET /memories/graph?limit=100&cursor=2026-04-28T08:34:00+00:00|edge-uuid-100

Example scoped to one agent:

GET /memories/graph?agent_id=chatbot&limit=50

Memory Timeline

Recently active entities first — one card per entity, never per fact. Each item is an entity with its humanized summary as the body, and its connected edge facts inlined for an “N facts” disclosure. This is the right shape for any “Memories” tab or memory browser UI.

GET https://your-tie-host/memories/timeline
Authorization: Bearer $TOKEN
Parameter In Required Description
agent_id query No Scope to one agent. Omit for memories across all agents.
graph_id query No Feed from a graph namespace instead of the user’s graph. Mutually exclusive with agent_id; no subject user needed.
limit query No Items (entities) per page (default 20, max 200)
cursor query No Opaque keyset cursor from the previous page’s next_cursor, passed back verbatim (<iso-datetime>|<entity-id>). Malformed values return 400 VALIDATION_ERROR.
sort query No last_updated (default) or created

Response:

{
  "items": [
    {
      "id": "node-uuid-1",
      "name": "Mindvalley",
      "labels": ["Topic"],
      "summary": "You were promoted to Staff Engineer at Mindvalley about five months ago and have been working on AI there.",
      "summary_raw": "Mindvalley is the company where Fauzaan works.\nFauzaan was promoted to Staff Engineer at Mindvalley.\nFauzaan works on AI at Mindvalley.",
      "first_seen_at": "2026-03-12T14:20:00+00:00",
      "last_updated_at": "2026-04-28T08:34:00+00:00",
      "fact_count": 3,
      "facts": [
        {
          "id": "edge-uuid-1",
          "label": "works_at",
          "fact": "Fauzaan works at Mindvalley",
          "created_at": "2026-04-28T08:34:00+00:00",
          "valid_at": "2026-04-28T08:34:00+00:00",
          "direction": "incoming",
          "other": {
            "id": "node-uuid-2",
            "name": "Fauzaan",
            "labels": ["User"]
          }
        }
      ]
    }
  ],
  "next_cursor": "2026-04-28T08:34:00+00:00|node-uuid-1",
  "sort": "last_updated"
}

Each item is one entity with:

  • id / name / labels — entity identity (use labels[0] as the entity-type chip)
  • summary — display-ready prose. Humanized rewrite when the background humanizer has run (addresses the user as “you/your” when the user appears in the entity’s facts), with automatic fallback to the raw graphiti string otherwise. Render this as the card body.
  • summary_raw — graphiti’s untouched newline-joined edge facts. Useful for a “show original” toggle.
  • first_seen_at — when this entity first entered the graph
  • last_updated_at — max(edge.created_at) across the entity’s live edges; the most recent activity touching this entity
  • fact_count — number of live edges. Render as “N facts” before the disclosure expands.
  • facts[] — each connected edge inlined with its other endpoint:
    • label — relationship type (e.g. works_at, prefers)
    • fact — full natural-language statement
    • created_at / valid_at — when the edge was written / is real-world-true
    • direction — "outgoing" if this entity is the source of the edge, "incoming" if the target. Helps render directional UIs (“you → works_at → Mindvalley” vs “Mindvalley ← works_at ← you”).
    • other — the entity on the other side of the edge

Entities with zero live (non-expired) edges are excluded from the response — there’s nothing to render.

Pagination: When next_cursor is not null, pass it as ?cursor={value} to fetch the next page. Treat the value as opaque and send it back byte-for-byte — it carries both a timestamp and the last item’s id, because timestamps are not unique (one ingest batch stamps many facts with the same second) and the id is what keeps tied items from being skipped. Pages never overlap and never drop items. next_cursor is null when you’ve reached the end; a page that happens to fill exactly still returns a cursor, so the final request may come back with an empty items array. Keep requesting until next_cursor is null rather than stopping on a short page.

There is no offset or page parameter — cursor is the only pagination mechanism, and unknown query parameters are ignored rather than rejected.

Picking a sort mode:

  • last_updated (default) — entities ordered by their most recent fact. New conversations push entities to the top of the feed. Good for “what’s been happening” UIs.
  • created — entities ordered by when they first entered the graph. Stable order regardless of recent activity. Good for “show me the things I’ve told you about” archival views.

Example: first page across all agents:

GET /memories/timeline?limit=20

Example: paginating:

# Next page (using next_cursor from previous response)
GET /memories/timeline?limit=20&cursor=2026-04-28T08:34:00+00:00|node-uuid-20

Example: entities in graph-creation order, scoped to one agent:

GET /memories/timeline?agent_id=chatbot&sort=created&limit=50

Merge Two Memories

Sometimes the graph ends up with two entity nodes that represent the same real-world thing — Mohamed Fauzaan and Fauzaan, or Acme Inc. and Acme. Merge collapses them into one: every live relationship that pointed at the duplicate is repointed at the primary, the LLM rewrites the primary’s summary and attributes to combine both records, and the duplicate is hidden from search.

POST https://your-tie-host/memories/{agent_id}/merge
Authorization: Bearer $TOKEN
Content-Type: application/json

Request body:

{
  "primary_id": "node-uuid-keep",
  "duplicate_id": "node-uuid-fold-in"
}
Field Type Required Description
primary_id string Yes UUID of the entity node to keep. Its name, summary, attributes, and embedding are preserved.
duplicate_id string Yes UUID of the entity node to fold into the primary. Must differ from primary_id.
graph_id string No Merge within a graph namespace instead of the user’s memory. Service token required; agent_id is ignored.

Both UUIDs come from the id field of nodes in Memory Graph (Visualization).

Response:

{
  "primary_id": "node-uuid-keep",
  "duplicate_id": "node-uuid-fold-in",
  "rewired_outgoing": 3,
  "rewired_incoming": 2,
  "audit_edge_id": "audit-uuid",
  "merged_at": "2026-05-04T10:30:00+00:00",
  "enriched": true,
  "enriched_summary": "Mohamed Fauzaan is a senior engineer at Mindvalley working on the Truememory platform. Previously known in the graph as 'Fauzaan'.",
  "enriched_labels": ["Person", "Engineer"]
}
  • rewired_outgoing / rewired_incoming — counts of live edges repointed from the duplicate to the primary.
  • audit_edge_id — UUID of the IS_DUPLICATE_OF edge written from duplicate → primary for traceability.
  • merged_at — timestamp the merge committed.
  • enriched — true when the LLM successfully rewrote the primary’s summary and attributes; false if the LLM call failed and the merge fell back to a blind rewire.
  • enriched_summary / enriched_labels — the new summary and unioned labels written onto the primary. null when enriched is false.

What changes after a merge:

  • Every live edge incident to the duplicate is replaced with an equivalent edge incident to the primary. The original edge is soft-deleted (its timeline is preserved for undo).
  • The duplicate node is marked with merged_into and disappears from List Memories, Search, and the Memory Graph response.
  • The primary node’s summary, attributes, and labels are rewritten by the LLM to combine both records into one canonical view. Its name and name_embedding are kept as-is — pick the more specific or canonical node as primary_id. Order matters.

Constraints:

  • Both nodes must live in the same scope — same user, same agent_id. Cross-agent merges are rejected.

Error cases:

Status Code When
400 MEMORY_MERGE_INVALID primary_id equals duplicate_id, or one of the UUIDs is not in the requested scope
501 SERVICE_UNAVAILABLE The memory backend is disabled for this deployment

Delete a Memory

DELETE https://your-tie-host/memories/{agent_id}/{memory_id}
Authorization: Bearer $TOKEN
Parameter In Required Description
graph_id query No Delete from a graph namespace instead of the user’s memory. Service token required; agent_id is ignored.

Response:

{
  "status": "deleted",
  "memory_id": "edge-abc123"
}

Returns 404 if the memory is not found.

List Memories (deprecated)

GET https://your-tie-host/memories/{agent_id}
Authorization: Bearer $TOKEN

Response:

[
  {
    "id": "edge-abc123",
    "title": "Works on Truememory Auth",
    "name": "Truememory Auth integration",
    "content": "User is integrating Truememory Auth with Finerminds",
    "memory_type": "long_term",
    "source": "edge"
  }
]
Navigation

Type to search…

↑↓ navigate↵ selectEsc close