Memory System
What is the Memory System?
The memory system is xbrain's central invariant. Every piece of data — whether it comes
from a chat, an agent, a Drive sync, or the Chrome extension — must pass through
memory-api and carry exactly 7 mandatory tags. A write without any of
these tags returns HTTP 422.
This is not a convention — it is enforced at the API level. The tagging contract is what makes scoped retrieval, truth-level promotion, team isolation, and audit logging possible. Remove the contract and xbrain becomes an ordinary vector database.
The Tagging Contract (7 Fields)
Enforced at the API level
Any POST to /v1/memory/upsert missing one of these 7 fields
returns 422 Unprocessable Entity. This is enforced at the API level, not
by convention. No exceptions.
| Field | Type | Description | Example |
|---|---|---|---|
team_scope |
string | Team identifier — hard isolation wall between teams. Team A can never see Team B's memories. | "excalibur" |
project_scope |
string | null | Sub-project within the team. null means team-wide scope. |
"fundraising" |
visibility |
enum | Who can see this item within the team: team (all members), project (project members only), private (author only). |
"team" |
confidence |
float | Confidence score from 0.0 to 1.0. Set by the source (agent, user, or extraction pipeline). | 0.85 |
truth_level |
enum | Epistemic status of this data point. Progresses from EPHEMERAL to PUBLIC via the promotions workflow. | "WORKING" |
source |
string | Who or what wrote this. Format: prefix:id. Used for attribution and audit. |
"librechat:conv_abc123" |
validation_status |
enum | Current validation state: pending (not yet reviewed), approved, rejected. |
"pending" |
The 5 Truth Levels
Truth levels represent the epistemic status of a memory item. They form a one-way
progression: you can promote a fact forward (e.g., WORKING → VALIDATED), but you
cannot demote it. Promotion requires an explicit workflow — it cannot be done by patching
the truth_level field directly.
Raw output: chat messages, web clips, draft notes. Unverified. Expires or gets promoted. The default level for anything that enters xbrain without explicit classification.
In-progress knowledge: actively used but not yet validated. Most team knowledge lives here. Facts the team is working with daily but has not yet formally reviewed.
Peer-reviewed: at least one team member has explicitly approved this fact. Safe to reference in decisions and to surface in agent context.
Team truth: the definitive answer your team has agreed on.
CANONICAL facts are injected automatically in LLM system prompts during agent runs.
Querying memory with truth_level_min=CANONICAL retrieves only authoritative facts.
Published externally: shareable outside the team. Requires explicit promotion from CANONICAL.
Only PUBLIC facts can be queried without a team_scope header.
Direct PATCH is blocked
truth_level can ONLY be promoted via the
/v1/promotions workflow. A direct PATCH on
truth_level returns HTTP 405 Method Not Allowed.
This prevents bypassing the approval chain.
Promotion Workflow
To change a memory item's truth_level, a team member requests a promotion. An admin (or automated policy for lower levels) approves or rejects it.
bash — request a promotion# Any team member can request a promotion
POST /v1/promotions
{
"item_id": "mem_abc123",
"target_level": "VALIDATED",
"justification": "Confirmed with the client on 2026-05-01"
}
# Admin approves the promotion
PATCH /v1/promotions/{promotion_id}
{
"decision": "approved",
"note": "Verified against signed contract doc"
}
Memory Upsert API
The primary write endpoint. Accepts a memory item with all 7 required fields.
On success, returns the canonical id of the stored item. If the item
already exists (same source + team_scope combination),
it is updated in-place.
bashcurl -X POST https://api.grooveos.app/v1/memory/upsert \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{
"item": {
"content": "The Q2 fundraising target is 2M€",
"team_scope": "excalibur",
"project_scope": "fundraising",
"visibility": "team",
"confidence": 0.9,
"truth_level": "WORKING",
"source": "librechat:conv_abc123",
"validation_status": "pending"
}
}'
# Response
{"id": "mem_a8f3c2d1..."}
Upsert with metadata and entities
Include an entities array in metadata to trigger
automatic Neo4j graph enrichment:
json{
"item": {
"content": "Alice leads the fundraising project",
"team_scope": "excalibur",
"project_scope": "fundraising",
"visibility": "team",
"confidence": 0.85,
"truth_level": "WORKING",
"source": "agent-runtime:run_xyz789",
"validation_status": "pending",
"metadata": {
"entities": [
{"name": "Alice", "type": "person"},
{"name": "fundraising", "type": "project"}
]
}
}
}
Semantic Search
Search memory items by natural language query. Results are filtered by
team_scope automatically — Team A can never see Team B's memories.
Use truth_level_min to restrict results to a minimum epistemic level.
bashcurl "https://api.grooveos.app/v1/memory/search?q=fundraising+target&truth_level_min=WORKING&limit=5" \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
Search parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
q |
string | required | Natural language search query. Converted to embedding by memory-api. |
truth_level_min |
enum | EPHEMERAL | Minimum truth level filter. CANONICAL returns only authoritative facts. |
project_scope |
string | null | Filter by project. Omit for all projects within the team. |
limit |
integer | 10 | Maximum number of results. Max 100. |
visibility |
enum | team | Filter by visibility level. |
Neo4j Graph Enrichment
When a memory item includes an entities field in its metadata,
memory-api asynchronously creates Entity nodes and
MENTIONS edges in Neo4j. This builds a knowledge graph of
who mentions what, across all conversations and agents.
Query relationships after they've been built:
bashcurl "https://api.grooveos.app/v1/graph/neighbors?entity=Alice" \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
# Returns: Alice's connected entities, co-mentioned projects, relationships
The Neo4j enrichment is async and fail-soft: if the Neo4j container is down or the worker is backed up, the memory upsert still returns 200. The entity processing is retried when Neo4j recovers.
Graphiti Enrichment
Every memory upsert triggers an async call to graphiti-service, which extracts temporal facts and updates the knowledge graph with time-aware relationships. Graphiti understands sentences like "Alice was the lead until Q2" and creates time-bounded edges rather than static ones.
Fail-soft by design
If graphiti-service is down, the memory upsert still succeeds. The graphiti enrichment
is queued and retried when the service recovers. You can track enrichment backlog via
GET /v1/graph/queue-status.
Why temporal facts matter
Standard knowledge graphs store facts as static triples: Alice → WORKS_ON → fundraising. Graphiti adds temporal context: Alice → WORKS_ON → fundraising [2026-01-01 to 2026-04-30]. When an agent retrieves context for a question about Q1, it gets the correct team structure for that period — not the current one.
See Graphiti documentation for the full extraction pipeline, configuration options, and query examples.
Error Reference
| HTTP Status | When | Resolution |
|---|---|---|
422 |
One or more of the 7 required fields is missing from the request body | Check that all 7 fields are present: team_scope, project_scope, visibility, confidence, truth_level, source, validation_status |
405 |
Attempted direct PATCH on truth_level field | Use the /v1/promotions workflow instead |
401 |
Missing or expired JWT in Authorization header | Re-authenticate and obtain a fresh JWT |
403 |
team_scope in request does not match X-Team-Scope header | Ensure the JWT's team claim and the request body's team_scope match |
404 |
item_id not found (for promotions or graph queries) | Verify the memory item ID exists in the correct team_scope |