mnemory exposes a full REST API alongside the MCP server. The FastAPI sub-app is mounted at /api/ with auto-generated OpenAPI spec at /api/openapi.json and Swagger UI at /api/docs.
Both MCP and REST share the same MemoryService backend and authentication middleware. Authentication may use API keys (Authorization: Bearer <key> / X-API-Key) or Cognis-issued ES256 JWTs (Authorization: Bearer <jwt> with aud=mnemory). The OpenAPI document and Swagger UI are public metadata for client import; documented API operations still require authentication.
POST /api/user-events/remember/v1 and POST /api/evidence/remember/v1
share semantic ownership for new authenticated event roots. Each route still
requires its own route-bound signed request. Ordinary API keys cannot use either
route or supply trusted provenance.
The service extracts atomic facts, retrieves shared-user Qdrant candidates, and
uses the existing deduplication LLM. Similarity alone does not authorize a write.
The immutable journal plan contains ADD, CONFIRM, UPDATE, or SKIP actions.
ADD establishes provenance with zero confirmations. CONFIRM requires complete
semantic equivalence and an independent root. UPDATE creates a successor revision.
Partial support cannot confirm an entire multi-fact consolidated memory.
Both routes return the same canonical result for one root. A repeated route call
returns replayed without new semantic effects. Historical terminal journals
remain terminal and are not processed again by the new pipeline.
A lease in the existing operations collection serializes trusted semantic decisions within the exact shared user/owner scope. Other users remain independent. This lease does not serialize ordinary tool writes. Target revision checks remain required. Persisted actions and deterministic ADD identities support recovery after a partial write.
The message limit is separate from the extracted-memory limit. Oversized input or extraction output fails explicitly before memory writes; the service does not truncate the signed source or store it unconditionally as one fact.
Cognis can retain paired route delivery, but must sign each request for its actual route. Prefer one ingest call per event to avoid a redundant replay request. The evidence route now shares ADD/UPDATE semantics for new authenticated roots, not only CONFIRM semantics. No credential scope changes are required.
Both trusted routes return HTTP 422 with a structured detail object for a
durable budget rejection. Its fields are:
| Field | Value |
|---|---|
status |
rejected |
outcome |
rejected_before_write |
operation_id |
Canonical event operation UUID |
reason |
One reason from the list below |
terminal |
true |
retryable |
false |
fallback_allowed |
false |
semantic_effects |
none |
source_retention |
caller_queue |
Reasons: input_budget_exceeded, extraction_fact_budget_exceeded,
action_limit_exceeded, or plan_budget_exceeded.
The canonical journal atomically commits an empty rejection record before any semantic actions for this root exist. Its committed state means durable failure, not successful extraction. Both routes replay the identical HTTP failure without another LLM call, even after configuration changes. Existing sealed actions cannot be replaced by a rejection. Historical journal recovery remains unchanged.
Client handling:
- Match HTTP
422anddetail.outcome == "rejected_before_write", not HTTP status alone. - Mark the existing queue work as terminal failed and retain its complete original signed payload, provenance, operation ID, and reason.
- Surface the failure. Do not acknowledge successful memory extraction.
- Do not invoke ordinary remember as fallback. Its input truncation is not lossless.
- On a timeout, HTTP
503, or any unknown outcome, resume the same authenticated root. Never infer that no effects occurred.
source_retention names the existing caller queue's responsibility. Mnemory keeps
the canonical fingerprint/root binding, not another copy of the rejected source
as a memory. The Cognis failure-retention change must accompany deployment.
Schema-level HTTP 422 validation errors do not carry this durable outcome.
Arbitrarily large messages are not guaranteed successful extraction.
| Endpoint | Method | Description |
|---|---|---|
/api/memories |
POST | Add a memory |
/api/memories/batch |
POST | Batch add memories |
/api/memories/search |
POST | Semantic search |
/api/memories/find |
POST | AI-powered multi-query search |
/api/memories/core |
GET | Core memories (pinned + recent) |
/api/memories/recent |
GET | Recent memories |
/api/memories |
GET | List memories |
/api/memories/by-ids |
POST | Fetch memories by ID list (batch) |
/api/memories/{id} |
PUT | Create a successor revision |
/api/memories/{id} |
DELETE | Retract without erasing history |
/api/memories/{id}/privacy |
DELETE | Explicitly erase a lineage and unreferenced artifacts |
/api/memories/{id}/history |
GET | Get immutable revisions and audit records |
/api/memories/{id}/links |
GET | Get exact supersession and derivation links |
/api/memories/{id}/artifacts |
POST | Save artifact |
/api/memories/{id}/artifacts |
GET | List artifacts |
/api/memories/{id}/artifacts/{aid} |
GET | Get artifact (JSON with base64 for binary) |
/api/memories/{id}/artifacts/{aid}/download-token |
POST | Generate a signed download token |
/api/memories/{id}/artifacts/{aid}/raw |
GET | Download raw artifact bytes (token or API key auth) |
/api/memories/{id}/artifacts/{aid} |
DELETE | Delete artifact |
/api/categories |
GET | List categories |
Artifact deletion removes the reference from the selected revision. Immutable history can retain the artifact bytes. Use lineage privacy erasure when the artifact content must be physically removed from all revisions.
The four REST mutation classes accept an optional If-Match header:
PUT /api/memories/{id}DELETE /api/memories/{id}POST /api/memories/{id}/artifactsDELETE /api/memories/{id}/artifacts/{aid}
The value is the positive integer revision returned by memory and mutation
responses. A bare value (If-Match: 3) or strong quoted value
(If-Match: "3") is valid. Weak entity tags, lists, wildcards, snapshot
hashes, zero, negative values, and other strings return HTTP 400.
Memory update bodies and memory delete queries retain expected_revision.
When a request supplies both forms, their integer values must match. A
conflict returns HTTP 409 before mutation. A valid but stale revision also
returns HTTP 409. Artifact mutation responses retain lineage_id, revision,
artifact_revision, and replay state. Signed artifact download URLs are not
affected by this contract.
POST /api/fsck/audit starts a bounded audit for the exact memory IDs and
revisions of one existing fsck operation. The request accepts at most 20
targets. The audit does not search a neighbor corpus, stamp checked_at, or
change memory metadata. Its completed journal record contains fingerprints and
counts, but no memory content.
POST /api/fsck/operations/{operation_id}/re-evaluate compares an old fsck
operation with a newer completed exact audit bound to that operation. A normal
or historically rehydrated check is rejected. The default
terminalize=false is mutation-free and does not apply actions. With explicit
terminalize=true, the endpoint marks the old journal entry superseded.
Applying the fresh issue remains a separate explicit request.
GET /api/sessions/failed/diagnostics returns aggregate failure metadata.
GET /api/sessions/failed/retry-eligibility returns authorized, content-free
candidate metadata. Eligibility validates every linked raw-memory revision for
existence, exact scope, raw layer, active state, and supersession. Diagnostics
include stable ineligibility reason counts.
POST /api/sessions/failed/retry defaults to
dry_run=true, which creates no operation record and changes no session.
Mutation requires LEGACY_FAILED_RETRY_ENABLED=true, an idempotency key, and
an explicit list of no more than ten eligible sessions. Retries run
sequentially and stop on configured failure, revision, or timeout conditions.
The execution path repeats linked-input validation immediately before it
creates or claims the retry journal.
GET /api/memories/core supports:
recent_days— how many days of recent context to includeinclude_stats— whentrue, also return structured stats for the assembled core context
Example response with include_stats=true:
{
"text": "## User Facts\n- User lives in Prague\n...",
"stats": {
"memory_count": 4,
"char_count": 1840,
"estimated_tokens": 460,
"by_type": {"fact": 2, "preference": 1, "episodic": 1},
"by_role": {"user": 4},
"by_section": {"user_facts": 2, "user_preferences": 1, "recent_user_activity": 1},
"section_labels": {"user_facts": "User Facts", "user_preferences": "User Preferences", "recent_user_activity": "User Activity"},
"sections": {"user_facts": ["mem_1", "mem_2"], "user_preferences": ["mem_3"], "recent_user_activity": ["mem_4"]},
"memory_ids": ["mem_1", "mem_2", "mem_3", "mem_4"]
}
}Core memories exclude memory_layer=raw by default, so only consolidated (and legacy no-layer) memories are included in the default injected context.
Persistent session summaries from the remember endpoint, used by the consolidation service.
| Endpoint | Method | Description |
|---|---|---|
/api/sessions |
GET | List session summaries with pagination, search, and sorting (offset, limit, consolidation_state, q, sort_by, sort_dir) |
/api/sessions/{id} |
GET | Get a single session summary |
/api/sessions/{id} |
DELETE | Delete a session summary (optional delete_memories=true to also delete linked raw memories) |
/api/sessions/{id}/consolidate |
POST | Trigger consolidation for a specific session (returns result) |
GET /api/sessions returns:
{
"sessions": [
{
"session_id": "ses_...",
"summary": "...",
"created_at": "2026-03-28T10:00:00+00:00",
"updated_at": "2026-03-28T10:05:00+00:00",
"consolidation_state": "idle"
}
],
"total": 123,
"offset": 0,
"limit": 25,
"has_more": true,
"total_truncated": false
}q: case-insensitive substring match against the stored session summary text and session IDsort_by:updated_at(default) orcreated_atsort_dir:desc(default) orasctotal: count after all filters/search are applied, before pagingtotal_truncated: true if the server hit its internal safety cap while scanning very large session sets
Built-in memory consistency checker. Runs a three-phase pipeline to detect quality issues and suggest fixes:
| Endpoint | Method | Description |
|---|---|---|
/api/fsck |
POST | Start a memory check (runs in background) |
/api/fsck/audit |
POST | Start an exact-target, audit-only check |
/api/fsck/auto-run |
POST | Trigger an immediate auto-fsck run for the current user |
/api/fsck/{id} |
GET | Poll check status, progress, and results |
/api/fsck/{id}/apply |
POST | Apply selected fixes from a completed check |
- Security scan (instant) — regex-based detection of prompt injection patterns, confirmed via LLM re-evaluation
- Duplicate detection — vector similarity clustering + LLM evaluation of each cluster for duplicates and contradictions
- Content quality — LLM batch evaluation for broken, meaningless, or unsalvageable memories
- Metadata normalization — LLM batch evaluation for wrong memory_type, categories, importance, pinned, or role
Phases 2–4 run LLM calls in parallel (FSCK_LLM_CONCURRENCY, default 4 workers) for ~4x speedup on large memory sets. Results are cached with configurable TTL (FSCK_CACHE_TTL, default 24 hours) so you can review issues in the UI and apply fixes without re-running the check.
By default, fsck focuses on durable memories (consolidated memories plus legacy memories without a memory_layer field). Raw provisional memories are excluded unless the caller explicitly opts in with include_raw=true.
Incremental mode: Auto-fsck defaults to incremental processing — only memories changed since the last maintenance run are checked. Manual fsck runs a full scan. The checked_at metadata field tracks when each memory was last checked. LLM budget is bounded by FSCK_MAX_LLM_CALLS (default 200 per run); phases execute in priority order (security → duplicates → content quality → metadata normalization), so under budget pressure earlier phases take precedence and later phases catch up in subsequent runs.
Issue types: duplicate, contradiction, quality, reclassify, security
Workflow: Start check → poll until completed → review issues in UI → select and apply fixes. Each fix is a set of actions (update content, update metadata, delete memory). Auto-fsck applies qualifying fixes automatically based on confidence and severity thresholds.
Two high-level endpoints designed for plugin-driven automatic memory management:
Combined initialize + search. Call on each user message.
{
"session_id": null,
"query": "Should I buy a dog?",
"include_instructions": true,
"managed": true,
"score_threshold": 0.5,
"labels": {"project": "myapp"}
}- First call (no
session_id): creates session, returns instructions + core memories +find_memoriesresults - Subsequent calls: returns only NEW relevant memories via fast
search_memories(no LLM), filtering out already-returned IDs score_threshold: optional per-request minimum score (0.0-1.0) for search results, applied on top of server'sSEARCH_SCORE_THRESHOLD. Prevents context bloat from weak matches on follow-up messages.labels: optional label filter — only return memories matching these key-value pairs. See Memory Model — Labels.- Graceful degradation:
find_memoriesfails ->search_memories-> core memories only -> empty response
Fire-and-forget memory storage. Call after each exchange.
{
"session_id": "sess_abc123",
"messages": [
{"role": "user", "content": "I just moved to Berlin"},
{"role": "assistant", "content": "That's exciting!"}
],
"role": null,
"labels": {"conversation_id": "conv-123"}
}The role parameter controls the extraction point of view:
| Value | Behavior |
|---|---|
null (default) |
Auto mode — extracts facts from ALL participants. Each fact is attributed to the correct role (user or assistant). Assistant facts are silently dropped if no agent_id is set in the session. |
"user" |
Extracts only user facts, suppresses assistant content. |
"assistant" |
Extracts only assistant facts (identity, recommendations, conclusions). Requires agent_id via X-Agent-Id header. |
- Returns
{"accepted": true}immediately, processes in background - Reuses the same extraction pipeline as
add_memory(infer=True)— no extra LLM calls labels: optional key-value metadata attached to all extracted memories. Bypasses LLM — stored as-is. See Memory Model — Labels.- Stored memory IDs are added to the session to prevent echo on next recall
- Rate limited per user (configurable via
REMEMBER_RATE_LIMIT)
The recall/remember endpoints use server-side sessions (MemorySession) to track which memories the client already has. This prevents context bloat by only returning new memories on subsequent calls.
- Sessions are created on first recall and expire after idle timeout (default 24 hours, configurable via
MEMORY_SESSION_TTL) - Both recall and remember calls reset the idle timer (auto-prolong), so active conversations never expire
- Sessions are persisted to a pluggable backend (SQLite by default, Redis for clustered deployments) and survive server restarts
- Losing a session is harmless — next recall creates a new one
- Periodic background sweep cleans up expired sessions from both the in-memory cache and the backend
Sessions use a write-through cache: an in-memory dict for fast reads, with all mutations written to the backend for durability. On cache miss (e.g., after restart), sessions are loaded lazily from the backend.
| Backend | Use case | Config |
|---|---|---|
sqlite (default) |
Single-node, local development, uvx mnemory |
SESSION_PATH (default ~/.mnemory/sessions.db) |
redis |
Clustered deployments, multiple replicas | REDIS_URL (e.g., redis://host:6379/0) |
memory |
Tests, no persistence needed | No additional config |
Auto-detection: if REDIS_URL is set and SESSION_BACKEND is not explicitly set, defaults to redis. See Configuration for all session env vars.
mnemory includes a built-in background maintenance service that automatically runs memory consistency checks and applies fixes on a configurable schedule.
Enable it by setting FSCK_AUTO_INTERVAL to a non-zero number of hours:
FSCK_AUTO_INTERVAL=24 # Run every 24 hours
FSCK_AUTO_MIN_CONFIDENCE=0.95 # Only apply fixes with >=95% confidence
FSCK_AUTO_MIN_SEVERITY=medium # Only apply medium or high severity fixesHow it works:
- The maintenance loop sleeps for the configured interval, then wakes up
- All users in the collection are enumerated via a Qdrant scroll
- For each user, a full fsck check is run (security scan -> duplicate detection -> quality check)
- Issues that meet both the confidence and severity thresholds are automatically applied
- Results are recorded in Prometheus metrics (
mnemory_autofsck_*counters + last-run timestamp) - Per-user errors are isolated — one failing user does not stop the run
The loop is sleep-first: it waits the full interval before the first run, so startup is not impacted. Auto-applied fixes are visible in the management UI (Check tab status banner) and in the Grafana dashboard (Auto-fsck row).