Skip to content

Changelog

RSS

New features, improvements, and fixes across Truememory.

Memory graph: one graph, every entity visible

TIE AI Gateway

GET /memories/graph now returns one graph: entity nodes and their RELATES_TO facts. Every entity with a live fact in scope appears, whatever kind of episode produced it. Reading a user’s own graph after an API ingest returns the extracted entities, not a Note bubble with nothing attached.

GET /memories/graph?agent_id=eliza
Authorization: Bearer $USER_TOKEN

What changed

The endpoint used to carry two layers. Any episode whose source_description was not one of two internal stamps became a Note node, and an entity appeared only if a chat exchange or an agent-tool write also mentioned it. Document entities were hidden outright. Together these rules turned every record an application ingested into an isolated Note with its entities missing, in a user graph and in a namespace alike.

All three rules are gone. source_description is a provenance label and never decides visibility. No entity type is hidden. Episodes are not nodes in this response. They stay stored verbatim and are returned by GET /memories/export?include_episodes=true; search and the timeline always returned the extracted facts.

Removed

  • Note nodes and their related edges no longer appear in the response.
  • The source_description_prefix query parameter is removed. Callers that still send it are not rejected; it has no effect. Remove any sentinel prefix you passed to suppress the notes layer.
  • Document entities are no longer filtered from the visualization.

A client that skipped nodes labelled Note, or dropped related edges, keeps working with nothing to skip. A client that grouped Note nodes under a heading of their own now shows that group empty; the same notes’ extracted entities appear under their own types.

Ingest type document

POST /memories/graph accepts type: "document" for a note or document the user brought in whole. note is accepted as its older name and behaves identically. The type selects extraction rules and nothing else; it does not change how the graph is read back.

{
  "data": "Read Atomic Habits this week. The two-minute rule is the part I want to keep.",
  "type": "document",
  "agent_id": "eliza"
}

No re-ingest or backfill is required. The entities were always in the graph; only this endpoint hid them.

Id-aware record resolution for shared graphs

TIE AI Gateway

Record ingestion (type: "json") now resolves entities by the caller’s record identity, not by name alone. Two members who share a name keep two separate nodes. A record re-sent with the same source_description stamp finds its existing node, even after a display-name change.

Three pieces work together. A stable id field on the record (for example tieUserId) lands on the subject’s node as external_id. The source_description stamp carries the same id and drives resolution on corrections. Field values that equal another node’s external_id become real edges — an event’s hostTieUserId produces a HOST edge to that member.

{
  "data": "{\"tieUserId\": \"0f4b9c21-...\", \"name\": \"Dana Ferreira\", \"city\": \"Kuala Lumpur\"}",
  "type": "json",
  "graph_id": "acme__members",
  "source_description": "acme-member-profile:0f4b9c21-..."
}

Extraction also improved for records: every field produces a fact, including list fields such as languages (a new SPEAKS relationship) and free-text intent fields (WANTS_TO_MEET now applies to Person subjects, not only the conversation user).

Callers that ingest third-party records should follow the new conventions: Ingesting Records and Records About People.

Graph namespaces: full entity graph on inspection, service token required

TIE AI Gateway

Inspecting a graph namespace returned only Note nodes and zero entities. The endpoint now returns the namespace’s full deduplicated entity graph, as the documentation always described.

GET /memories/graph?graph_id=acme__product_kb
Authorization: Bearer $SERVICE_TOKEN

Why the entity layer was empty

The entity layer hides entities that no non-note episode mentions. That rule keeps per-note artifacts out of a note-heavy graph. It defined “non-note” as two internal episode tags, written by chat flushes and agent-tool writes.

Namespaces receive neither. API ingest populates them entirely, so no episode ever qualified. The filter blanked the entity layer for every namespace. It now applies to user graphs only.

The entities were always present in the graph. Search and retrieval always blended them in, and only this visualization endpoint hid them. No re-ingest or backfill is required.

Breaking: namespace inspection requires a service token

GET /memories/graph and GET /memories/timeline with a graph_id now return 403 AUTH_SERVICE_REQUIRED for end-user tokens. This matches the existing policy for namespace writes, deletes, and merges. Both endpoints expose the same namespace entities, so gating one alone would leave the other as a bypass.

A namespace holds cross-user data scoped to no caller in particular, so an end-user token has no principled claim on its contents. Returning the unfiltered entity graph widens that gap. Service tokens with no user attached continue to work.

A browser client calling either endpoint directly with an end-user token will break. Proxy the call through your backend with a service token instead.

Reading a user’s own graph, by omitting graph_id, is unchanged and still works with an end-user token.

Also documented

source_description_prefix on GET /memories/graph is now documented. It redefines which episodes count as notes, and therefore which entities pass the filter described above.

Read Memory Graph visualization for the parameter reference and the two-layer response model, or Inspecting a Namespace for the token requirement.

Brains: group knowledge packs into one collection

TIE AI Gateway

A brain is a named collection of knowledge packs. Group packs once under a brain, then ground a chat request in the whole set with a single brain_id. This replaces listing every pack ID on each request.

Manage brains through a new REST surface. Create a brain, then add or remove packs.

# Create a brain
POST /brains
{ "name": "Company Handbook", "description": "HR policies and SOPs" }

# Add a pack to it (idempotent)
POST /brains/{brain_id}/knowledge-packs
{ "pack_id": "7f3c..." }

Attach the brain at chat time with brain_id. TIE resolves it to the packs it holds, then runs the same retrieval as knowledge_pack_ids.

POST /v1/chat/completions
{
  "model": "anthropic/claude-sonnet-4-5",
  "messages": [{ "role": "user", "content": "What is our refund window?" }],
  "brain_id": "b1a2c3..."
}

A brain points at its packs rather than holding copies. Deleting a brain removes only the collection, never the packs. A pack deleted after you add it simply drops out of retrieval. An explicit knowledge_pack_ids on the request wins, and the brain is ignored.

Administrators manage brains across users through /admin/brains. Admin create accepts a user_id; leave it null to create an app-owned or global brain with no owner. Brains require the PostgreSQL memory backend.

See the Brains guide for the full contract.

Graph namespacing — blend shared knowledge graphs into memory search

TIE AI Gateway

Memory retrieval can now draw on knowledge that belongs to no user. A graph namespace is a named knowledge graph — a product catalog, company policies, a coaching methodology — identified by a freeform graph_id slug. Pass up to four of them and TIE blends their facts with the user’s own memory:

POST /memories/retrieve
{
  "query": "what running program fits a beginner",
  "agent_id": "chatbot",
  "graph_ids": ["acme__coaching_kb"]
}

What it does

  • The user’s graph and each namespace are searched concurrently, then fused into one ranked list. When a user fact and a namespace fact are equally relevant, the user fact wins.
  • Every result carries provenance: scope: "user", or scope: "graph" plus the graph_id it came from. Existing callers are unaffected — omit graph_ids and responses are unchanged.
  • The same field works on POST /memories/search for raw ranked fact edges without the context block.
  • A namespace that is empty, unknown, or unreachable contributes nothing; user results always come back. Malformed ids fail fast with 400 VALIDATION_ERROR.

Populate a namespace

POST /memories/graph accepts a graph_id target instead of agent_id — provide exactly one. Namespace writes require a service token; ingestion is asynchronous with the existing job_id polling contract, and writers to one namespace serialize cluster-wide. Namespaces are never written automatically — chat auto-ingest and the memory_write agent tool only touch the user’s own graph.

Use a namespace in chat

POST /v1/chat/completions accepts graph_ids — TIE blends namespace facts into the injected memory context for that turn. Read-only: conversation ingestion still targets the user’s graph.

Inspect and manage a namespace

  • GET /memories/graph?graph_id=… and GET /memories/timeline?graph_id=… show one namespace on its own — no user data, no subject user required, so service-only tokens work. Omit graph_id for the user’s own view; graph_id and agent_id are mutually exclusive. This maps directly onto a dashboard filter: “my memories” versus “organization”.
  • DELETE /memories/{agent_id}/{memory_id}?graph_id=… and merge with a body graph_id operate on namespace facts. Both require a service token — namespace changes affect every reader; end-user tokens receive 403 AUTH_SERVICE_REQUIRED.

Naming rules

graph_ids are lowercase slugs (letters, digits, underscores, hyphens; 3–64 characters; starts with a letter). A bare leading UUID is rejected — that shape is reserved for user memory scopes. For UUID-based uniqueness, prefix it: org_9f8e4b2a-….

Caveats

  • Whole-namespace lifecycle ships separately. Listing all namespaces and exporting or deleting an entire namespace are not in this release; individual fact delete/merge are.
  • Never written automatically. Chat auto-ingest and the memory_write agent tool only touch the user’s own graph.

See Graph Namespacing for naming best practices, the multi-tenant pattern, and full request and response shapes.

Skills: assign one skill to multiple apps

TIE AI Gateway

Skills now carry an app_ids array instead of a single app_id. One skill can serve several apps at once, or every app on the platform.

Set app_ids to the list of apps that should see the skill. Leave it null (or omit it) to share the skill with every app.

{
  "key": "summarize-thread",
  "name": "Summarize Thread",
  "prompt": "Summarize the conversation so far in 3 short bullet points.",
  "app_ids": ["vibrantly", "blinklife"]
}

A null app_ids marks the skill as global — visible to all apps:

{ "key": "translate", "name": "Translate", "prompt": "Translate to formal English.", "app_ids": null }

Why this matters

Before this change, a shared skill had to be duplicated once per app, and each copy drifted on its own. A single record assigned to many apps keeps one source of truth and removes the duplicates.

Backward compatibility

The legacy scalar app_id still works on create and update. TIE folds it into a one-element app_ids list, so existing single-app callers need no changes. Responses now return app_ids.

The app_id query filter on GET /skills is unchanged. It still names the requesting app and returns skills assigned to that app plus global skills.

See the app scoping section of the Skills guide for the full contract.

Add Z.ai (GLM) models

TIE AI Gateway

TIE now routes requests to Zhipu AI (Z.ai) GLM models. Select a model by passing a zai/-prefixed name to /v1/chat/completions, the same way you select any other provider.

{
  "model": "zai/glm-4.6",
  "messages": [{ "role": "user", "content": "Hello" }]
}

Available models

Model Notes
zai/glm-5.2 Latest flagship
zai/glm-5.1 Previous flagship
zai/glm-5-turbo Faster, lower-cost tier
zai/glm-5 General-purpose
zai/glm-4.7 Strong agentic coding and reasoning
zai/glm-4.7-flash Lightweight, low-latency
zai/glm-4.6 200K context window
zai/glm-4.5 Prior generation

GLM models support tool calling and a thinking reasoning mode. They run through Z.ai’s OpenAI-compatible API, so existing request and response handling needs no changes.

Configuration

Set ZAI_API_KEY to enable the provider. When Z.ai is the only configured provider, glm-4.6 becomes the default model. Override the default with DEFAULT_MODEL=zai/glm-5.2.

Query GET /info to confirm which models are active on your instance.

See the chat completions reference for the full provider list.

Personas: soul_document, human_profile, and avatar fields

TIE AI Gateway

Added soul_document, human_profile, and avatar as structured fields on the persona object. Apps no longer need to maintain local copies of personality data, user relationship context, or avatar URLs.

What changed:

  • POST /personas and PUT /personas/{id} accept all three new fields (optional)
  • All persona responses now include soul_document (nullable JSON), human_profile (nullable text), and avatar (nullable text)
  • GET /personas/active returns all three fields so apps can reconstruct the full agent state without a local database

Field semantics:

  • soul_document — The agent’s structured personality document. JSON with keys voiceAndStyle, antiPatterns, learnedAdaptations, boundaries, customInstructions. Each is an array of { "text": string, "source": string } entries. This is persona-level state (same for all sessions) — apps write this when the user edits their AI’s personality.

  • human_profile — Free-form text the AI writes about the specific user it talks to: goals, communication preferences, values, context. Since each user has exactly one non-preset persona, this is effectively user-scoped. Apps write this when the AI learns something meaningful about the user during conversation.

  • avatar — URL of the persona’s avatar image. Shared across all apps using this persona so every app shows the same avatar without maintaining a local copy.

Why: TIE is the single source of truth for a user’s AI agent across all platform apps. Previously, personality data, user profiles, and avatar URLs lived in each app’s local database, causing drift between apps. Now any app that shares a persona sees the same state without a sync step.

Migration: No action required. Existing personas return null for all three fields until updated. Apps can migrate by pushing their local values to TIE on next sync.

Add POST /auth/reset-password to confirm password reset

TIE Auth

A new POST /auth/reset-password endpoint completes the two-step password reset flow. Previously, callers had to call Firebase’s Identity Toolkit REST API directly to exchange an oobCode for a new password. Now TIE Auth handles this as a first-class endpoint.

Request:

POST /auth/reset-password
Content-Type: application/json

{
  "oob_code": "<code from the reset email link>",
  "new_password": "NewSecurePassword123!"
}

Response (200 OK): { "status": "ok" }

New error codes:

Code Status When
AUTH_INVALID_OOB_CODE 400 The code is invalid or has expired
AUTH_PASSWORD_RESET_FAILED 400 Other Firebase-level failure

The existing POST /auth/forgot-password endpoint is unchanged — it still sends the reset email as step 1.

See Password Reset for the full flow.

Merge two entity memories into one

TIE AI Gateway

When the graph ends up with two entity nodes that represent the same real-world thing — Fauzaan and Mohamed Fauzaan, or Acme and Acme Inc. — you can now collapse them in one call:

POST /memories/{agent_id}/merge
{
  "primary_id": "node-uuid-keep",
  "duplicate_id": "node-uuid-fold-in"
}

What it does

  • Every live edge incident to duplicate_id is repointed at primary_id. The original edge is soft-deleted so the timeline is preserved for undo.
  • The duplicate node is marked with merged_into and disappears from List Memories, Search, and the Memory Graph response.
  • An IS_DUPLICATE_OF audit edge is written from duplicate → primary for traceability.
  • The primary’s summary, attributes, and labels are rewritten by the LLM to combine both records into one canonical view. Its name is kept as-is, so pick the more specific or canonical node as primary_id.
  • LLM enrichment is best-effort — on a model outage the merge falls back to a blind rewire and the response sets enriched: false.

Caveats

  • Edge fact text is not rewritten. If an old edge said “Fauzaan likes tea”, that text persists; only the endpoint pointer changes.
  • Duplicate facts are not deduped. If both nodes had similar relationships, you will see two live edges. The next memory-ingestion pass usually collapses them.
  • Same-scope only. Both nodes must share the same user and agent_id.

Why this matters

Before this endpoint, the only way to recover from a misextracted entity was to delete every memory tied to it and let the graph rebuild over time — costly and lossy. Merge gives you a non-destructive fix that preserves history and stays reversible.

See Merge Two Memories for the full reference.

Personas: archetype field for unified agent profile

TIE AI Gateway

Added archetype as a structured field on the persona object. Apps can now store and retrieve the agent’s personality type (e.g. SAGE, EXPLORER) independently of the compiled system_prompt.

What changed:

  • POST /personas and PUT /personas/{id} accept an optional archetype string (max 100 chars)
  • All persona responses now include archetype (nullable)
  • GET /personas/active returns archetype so apps can pattern-match to their own avatar assets without parsing prose

Why: TIE is the single source of truth for a user’s agent across all apps. archetype is the canonical identifier that lets any app read and render the agent correctly after onboarding in a different app. Avatar images are intentionally not stored in TIE — each app renders its own visual for the archetype.

Migration: No action required. Existing personas return archetype: null until updated.

Cache token counts in /v1/chat/completions usage

TIE AI Gateway

The usage object on /v1/chat/completions now includes two extra fields:

{
  "usage": {
    "prompt_tokens": 4532,
    "completion_tokens": 187,
    "total_tokens": 4719,
    "cache_read_tokens": 4200,
    "cache_creation_tokens": 0
  }
}
Field What it tells you
cache_read_tokens How many of your prompt_tokens were reused from the provider’s prompt cache. On Anthropic these are roughly 10× cheaper than regular input tokens.
cache_creation_tokens How many tokens the provider wrote into its cache for next time. Slightly pricier than a normal input token, but pays for itself on the next hit.

Why this matters

Without these fields, you had no way to tell a 10,000-token cached conversation apart from a 10,000-token fresh conversation — so the bill you computed was always the worst-case number. That could overstate input costs by up to 67% on long, repeat conversations (Anthropic’s standard input is $3.00/MTok vs cache reads at $0.30/MTok).

Now you can compute what you’re actually being charged:

fresh_input  = prompt_tokens - cache_read_tokens - cache_creation_tokens
real_cost    = (fresh_input        × standard_rate)
             + (cache_read_tokens  × cache_read_rate)
             + (cache_creation_tokens × cache_write_rate)
             + (completion_tokens  × output_rate)

Supported providers

Provider cache_read_tokens cache_creation_tokens
Anthropic (Claude) Yes Yes
OpenAI (GPT) Yes Always 0 — OpenAI doesn’t report cache writes separately
Vertex AI, Gemini, Groq, Mistral, etc. Always 0 Always 0

If a provider doesn’t support prompt caching, both fields are 0. Nothing breaks — you just don’t see savings where there are none to see.

Backward compatibility

  • Existing callers that ignore the new fields are unaffected — they’re purely additive.
  • Both fields default to 0, so old clients that deserialize into strict schemas continue to work as long as they allow unknown or zero-default fields.
  • Streaming responses (stream_options.include_usage: true) include the new fields in the final usage chunk.

See the full field reference at Understanding the usage object.

Return 409 Conflict for duplicate email registration

TIE Auth

Duplicate email registration now returns 409 Conflict with a dedicated AUTH_EMAIL_EXISTS error code. Previously, all registration failures returned 400 Bad Request with AUTH_REGISTRATION_FAILED, forcing callers to pattern-match on the details string to detect duplicates.

Callers can now match on the code field directly:

{
  "code": "AUTH_EMAIL_EXISTS",
  "message": "A user with this email already exists.",
  "details": null,
  "hint": "Try logging in instead."
}

Other registration failures continue to return 400 with AUTH_REGISTRATION_FAILED.

For the full error code reference, see User Tokens — Error Codes.

Type to search…

↑↓ navigate↵ selectEsc close