Long-term memory system for AI agents with hybrid search (BM25 + vector + knowledge graph), memory consolidation, and MCP integration
Loomem demonstrates strong definition quality with well-structured tool schemas and comprehensive descriptions. All three tools (memory_store, memory_search, memory_context) have detailed, LLM-optimized descriptions (150-400 chars) that explain WHAT the tool does, WHEN to use it vs siblings, and WHAT it returns. Input schemas are complete with proper JSON Schema types and descriptions for all parameters. However, output schemas are documented only in prose descriptions rather than formal JSON Schema definitions, and tool annotations (readOnlyHint, destructiveHint) are absent despite memory_store being a WRITE operation. Error handling guidance is present but could be more structured (e.g., explicit error classification). Parameter naming is clear and consistent (query, top_k, budget_tokens, stream_id). The 'supersedes' parameter in memory_store demonstrates sophisticated composition thinking. No security issues detected in parameter design, no secrets exposed.
Build a token-budgeted context block from memory, formatted as markdown ready for prompt injection. When to use vs siblings: use memory_context at the start of a complex task or new session to load relevant background without iterating over individual chunks. Use memory_search instead when you want per-chunk scoring, time-filtered queries, or to surface superseded versions. memory_context is faster and returns a single formatted block; memory_search gives per-chunk scores. Note: memory_context returns a single formatted block; for per-chunk ids, use memory_search. Returns: plain markdown text — a single formatted block combining profile summary, relevant memories, and recent memories (controlled by the sections parameter). Returns an empty string or minimal block when no relevant memories exist.
Search long-term memory using hybrid BM25 + vector + knowledge-graph retrieval with reranking. When to use vs siblings: use memory_search when you need specific facts matching a query. Use memory_context instead at the start of a session or complex task to get a single pre-formatted context block. Use memory_graph when you need to explore entity relationships rather than retrieve text facts. Use memory_associate to surface non-obvious connections. The response includes a chunk_id for each hit (`(id: <uuid>)`), usable directly with memory_history, memory_delete, or memory_feedback. Returns: numbered plain-text list — each entry: "N. [YYYY-MM-DD] <content> (score: X.XX) (id: <uuid>)". Items superseding older versions are annotated "[UPDATED — supersedes older version]". Returns "No relevant memories found." when empty. Note: supports time_filter (ISO date lower bound), valid_at (unix timestamp for bitemporal time-travel), and include_superseded to see historical versions. Set max_context_tokens to cap the returned context by a token budget (keeps the highest-scoring results that fit).
Output schemas not formally defined in JSON Schema. Descriptions document return format in prose (e.g., 'numbered plain-text list') but tools lack explicit outputSchema definitions. LLMs cannot reliably parse unstructured prose output or plan downstream tool calls without formal schema.
Tool annotations missing. memory_store is a WRITE operation (creates/updates/deletes memories) but lacks destructiveHint annotation. memory_search and memory_context are READ_ONLY but lack readOnlyHint. Annotations help LLMs reason about side effects and retry safety.
Error handling lacks structured classification. Descriptions mention error conditions (e.g., 'Errors if the superseded memory is not found') but do not categorize them as retryable, user-fixable, or fatal. No recovery guidance provided (e.g., 'Try memory_search() to find the correct chunk_id').
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 69 | <=2025-11-25 | v2 |
Store a single fact, decision, or preference in long-term memory. When to use vs siblings: use memory_store for one explicit fact the user just stated (name, preference, decision). Use memory_ingest instead when you have a full conversation transcript — it extracts multiple typed facts at once with contradiction detection. Do not use memory_store to dump raw conversation text. When the fact replaces an existing memory (a correction, a newer state of the same thing), pass its chunk_id as `supersedes` instead of writing a "replaces chunk X" note into the content — that links the versions for memory_history and hides the old one from memory_search. Returns: plain text confirmation — "Stored: \"<first 80 chars>...\" (id: <uuid>)"; with `supersedes`: "... (id: <uuid>, supersedes: <old uuid>)". Errors if the superseded memory is not found in your stream or already has a newer version (supersede that one instead).
memory_context lacks pagination/limit enforcement. budget_tokens parameter caps output size, but no mention of max_context_tokens behavior when results exceed budget. Does it truncate, return empty, or error? Ambiguity invites LLM misuse.
Parameter 'stream' is optional across all tools but default behavior is undocumented. Description says 'Omit to use your default stream' but does not explain how the default is determined (user config, session context, first-created stream?). LLMs may assume wrong defaults.