RAM-A
RAM-A is a standalone long-term memory module for add/search experiments and benchmark baselines. The repository currently focuses on a local Rust workspace, a command-line benchmark runner, and reproducible evaluation scripts.
Scope
memory-core: core long-term memory API and storage implementations.memory-bench: CLI runner for ingesting benchmark records and searching top-k memories.memory-cases: embeddable case-library engine for storing, indexing, and retrieving case documents.- Default local storage: SQLite.
- Default retrieval mode: hybrid dense embedding retrieval plus BM25 text retrieval.
- Embedding providers: OpenAI-compatible embeddings, including self-hosted
/v1/embeddingsservices, for real runs and a deterministic hash embedding provider for offline smoke tests.
Repository Layout
crates/
memory-core/ # core memory API, record model, stores, retrieval
memory-bench/ # benchmark CLI
memory-cases/ # embeddable case API, ingestion worker, chunk store, and search index
evaluation/
common/ # shared evaluation helpers, metrics, reports, backends
backends/ # optional benchmark comparison backends
clients/ # optional third-party benchmark clients
fixtures/ # small checked-in smoke datasets
datasets/ # dataset acquisition and local placement notes
baselines/ # Git-friendly baseline index format
longmemeval/ # LongMemEval adapter
personalmem/ # PersonaMem adapter
scripts/ # dataset-specific orchestration scripts
docs/
benchmarks/ # benchmark schema and result conventions
design/ # design notes and implemented architecture decisions
guides/ # task-oriented usage guides
Large raw datasets, generated stores, HTML reports, and full run artifacts are not part of
the source tree. Keep them under data/, outputs/, artifacts/, or external object
storage and record reproducible summaries in evaluation/baselines/.
Dataset Sources
Full benchmark datasets are not committed to this repository. Use the upstream links
below and keep local downloads under data/:
| Dataset | Source / download | Local placement |
|---|---|---|
| PersonaMem | GitHub, HuggingFace | data/personalmem/raw/ and data/personalmem/prepared/ |
| LongMemEval | GitHub, cleaned oracle JSON | data/longmemeval/longmemeval_oracle.json |
| LoCoMo | GitHub, locomo10.json | data/locomo/locomo10.json |
The repository only keeps small synthetic or truncated fixtures under
evaluation/fixtures/ for smoke tests.
Core API
memory-core exposes the minimal long-term memory interface:
#[async_trait::async_trait]
pub trait LongTermMemory: Send + Sync {
async fn add(&self, request: AddMemoryRequest) -> MemoryResult<AddMemoryResponse>;
async fn search(&self, request: SearchMemoryRequest) -> MemoryResult<Vec<ScoredMemory>>;
}
Add request:
pub struct AddMemoryRequest {
pub id: Option<String>,
pub text: String,
pub metadata: serde_json::Value,
}
Search request:
pub struct SearchMemoryRequest {
pub query: String,
pub top_k: usize,
pub filter: Option<serde_json::Value>,
pub graph_memory_space_id: Option<String>,
}
memory-cases Storage
memory-cases keeps durable case library data separate from the derived search index:
- Case data store: SQLite tables for datasets, uploaded documents, ingestion tasks, and chunks.
- Search index store:
memory-coreSQLite index for indexed text, FTS rows, and embeddings. Document index records are scoped withmemory_index_namespace = "memory-cases"so update and delete flows do not remove other memories in the same DB. - Chunk sizing uses tiktoken
cl100k_basetoken counting.
See docs/design/memory-cases-storage-split.md for table ownership and index semantics.
memory-cases defaults to local hash embeddings for offline and demo use. Configure
case_library.embedding_provider as openai_compatible together with its base URL, model,
API key environment name, and dimensions when a real embedding service is available. The
endpoint must be OpenAI-compatible and expose /v1/embeddings; this includes self-hosted
embedding services.
Keep the case-library search index in a separate SQLite file from RAM-A personal long-term
memory for demos and deployments. Sharing one SQLite index is only suitable for narrow smoke
tests because independent storage operations can contend on SQLite writer locks and case
reindex/reset flows should not touch user memories. If a shared memory-core SQLite index is intentionally used,
use one embedding profile per search scope. RAM-A stores embedding profile metadata on new
records and rejects same-scope profile mismatches, because two providers can have the same
vector dimensions but incompatible semantic spaces.
Quick Start
Use the checked-in fixture for an offline smoke test:
cargo run -p memory-bench -- `
--store data/sample.sqlite `
--embedding hash `
add `
--dataset evaluation/fixtures/sample.json
cargo run -p memory-bench -- `
--store data/sample.sqlite `
--embedding hash `
search `
--dataset evaluation/fixtures/sample.json `
--output outputs/sample_results.json
Use OpenRouter embeddings for a real local retrieval run:
$env:OPENROUTER_API_KEY="your_openrouter_key"
cargo run -p memory-bench -- `
--store data/memory.sqlite `
--embedding openrouter `
--model baai/bge-m3 `
--dimensions 1024 `
add `
--dataset data/locomo/locomo10.json `
--text-fields text,content,message,memory
cargo run -p memory-bench -- `
--store data/memory.sqlite `
--embedding openrouter `
--model baai/bge-m3 `
--dimensions 1024 `
search `
--dataset data/locomo/locomo10.json `
--query-fields question,query `
--top-k 10 `
--output outputs/bge_m3_top10.json
Graph memory benchmark mode is opt-in. --graph-build builds the graph during add;
--graph enables the graph retrieval channel during search. Graph extraction uses an
OpenAI-compatible chat-completions endpoint; by default it reads the same
OPENROUTER_API_KEY environment variable and uses OpenRouter.
$env:OPENROUTER_API_KEY="your_openrouter_key"
cargo run -p memory-bench -- `
--store data/locomo_graph.sqlite `
--embedding openrouter `
--model baai/bge-m3 `
--dimensions 1024 `
--graph-build `
--graph-llm-model openai/gpt-4o-mini `
add `
--dataset data/locomo/locomo10.json `
--text-fields text,content,message,memory
cargo run -p memory-bench -- `
--store data/locomo_graph.sqlite `
--embedding openrouter `
--model baai/bge-m3 `
--dimensions 1024 `
--graph `
--graph-weight 0.2 `
search `
--dataset data/locomo/locomo10.json `
--query-fields question,query `
--top-k 10 `
--output outputs/locomo_graph_top10.json
In graph auto memory-space mode, prepared-schema datasets use scope_id, while raw
top-level-array datasets use the top-level JSON path such as path:$[0]. Keep graph and
baseline runs in separate SQLite files when comparing scores.
For ad-hoc graph search with --query, pass the memory space through --filter, for example
--filter '{"scope_id":"scope-a"}'; otherwise no graph memory space can be inferred from the
single query path. With --resume --graph-build, existing MemoryRecords are still checked for
graph build: completed graph runs are skipped, missing graph runs are built, and failed/running
graph runs fail explicitly instead of being treated as successful.
Do not commit API keys, local stores, downloaded datasets, or generated reports.
RAM-A memory MCP service
memory-mcp provides ram-a-mem, a single Streamable HTTP MCP service for
personal long-term memory and tenant-authorized case-library retrieval. It exposes
/mcp, /healthy, and /ready on one configured HTTP port. The service name is
ram-a-mem so it can be distinguished from future services such as ram-a-kv.
Minimal RAM-A + MCP client deployment
The runtime has three configuration surfaces:
- RAM-A memory service config: controls auth, HTTP limits, personal memory storage, model providers, and optional embedded case-library storage.
- MCP client config, commonly
.mcp.json: tells an MCP client how to connect to RAM-A. - Client application config, for example xiaoO
config.toml: controls the client's own LLM provider and optional automatic memory behavior.
By default, ram-a-mem looks for config in this order:
--config <path>RAM_A_MEM_CONFIG./config/ram-a-mem.json~/.config/ram-a/ram-a-mem.json/etc/ram-a/ram-a-mem.json
Create config/ram-a-mem.json:
Copyable examples are also available under plugins/mcp/, including
plugins/mcp/ram-a-mem.json,
plugins/mcp/xiaoo.mcp.json, and
plugins/mcp/xiaoo-config.toml. For client-side prompt
guidance, see plugins/mcp/case-tool-instruction.md.
xiaoO + RAM-A knowledge base configuration can start from those files and then apply the
field changes listed below.
{
"auth": {
"tokens": [
{
"token_env": "RAM_A_XIAOO_TOKEN",
"tenant_id": "tenant-local",
"user_id": "alice",
"agent_id": "xiaoo",
"permissions": ["memory:read", "memory:write", "cases:read"]
}
]
},
"features": {
"memory": {
"enabled": true
},
"case_library": {
"enabled": true
},
"graph_memory": {
"enabled": false
}
},
"http": {
"bind_address": "127.0.0.1",
"port": 18081,
"allowed_origins": ["http://127.0.0.1:18080"],
"allowed_hosts": ["127.0.0.1:18081"]
},
"limits": {
"max_body_bytes": 1048576,
"requests_per_second": 20,
"rate_burst": 40,
"max_in_flight_per_principal_tool": 4,
"initialize_requests_per_second": 4,
"initialize_rate_burst": 8,
"max_active_sessions_per_principal": 8,
"max_active_sessions_global": 256,
"session_idle_timeout_seconds": 1800
},
"storage": {
"database_path": "data/ram-a-memory.sqlite"
},
"providers": {
"api_key_env": "LLM_API_KEY",
"base_url": "http://127.0.0.1:8000/v1",
"embedding_provider": "hash",
"embedding_model": "hash",
"embedding_dimensions": 1024,
"extractor_model": "GLM-5.2",
"verifier_model": "GLM-5.2",
"timeout_seconds": 120,
"max_retries": 3
},
"case_library": {
"rag_store": "data/memory-cases.sqlite",
"index_store": "data/memory-cases-index.sqlite",
"source_dir": "crates/memory-cases/test/accuracy_docs",
"api_token_env": "RAM_A_CASES_ADMIN_TOKEN",
"ingestion_poll_ms": 1000,
"embedding_provider": "hash",
"embedding_model": "hash",
"embedding_dimensions": 1024,
"chunk_size": 160,
"default_library": "ops",
"libraries": [
{
"name": "ops",
"dataset_id": "openeuler-ops-cases",
"tenant_ids": ["tenant-local"]
}
]
},
"graph_memory": {
"llm_api_key_env": "GRAPH_LLM_API_KEY",
"llm_base_url": "http://127.0.0.1:8000/v1",
"llm_model": "GLM-5.2",
"llm_timeout_ms": 60000,
"build_concurrency": 1,
"retrieval": {
"weight": 0.2,
"rerank_with_graph": false,
"allow_graph_only": false,
"max_graph_only_results": null,
"seed_limit": null,
"max_evidence_records_per_fact": null,
"fail_open": false
}
}
}
Change these fields before deployment:
RAM_A_XIAOO_TOKEN: environment variable name that contains the RAM-A bearer token.tenant_id,user_id,agent_id: identity scope for memory isolation.permissions: includememory:read,memory:write, and optionallycases:read. Grantcases:writeonly to trusted MCP principals allowed to prepare and confirm case writes.features.memory.enabled: expose or hide RAM-A personal long-term memory tools (memory_search,memory_ingest).features.case_library.enabled: expose or hide the case-library tools (memory_case_search, allmemory_case_prepare_*tools, and the upload, update, and delete confirmation tools). If this istrue,case_librarymust also be configured.features.graph_memory.enabled: augmentmemory_ingestandmemory_searchwith graph construction and retrieval. Keep thisfalseunless the top-levelgraph_memorysettings andGRAPH_LLM_API_KEYare configured.allowed_hosts: host and port used by clients to reach RAM-A.storage.database_path: RAM-A personal long-term memory SQLite path.providers.api_key_env,providers.base_url,extractor_model,verifier_model: OpenAI-compatible chat/completions provider used by extraction and verification.providers.embedding_provider: usehashfor local demos; useopenai_compatiblefor real semantic memory retrieval.case_library.rag_store: case-library business SQLite path for datasets, documents, tasks, chunks, and uploaded source files.case_library.index_store: case-library retrieval index SQLite path. Keep it separate fromstorage.database_path.case_library.source_dir: optional local directory of.md/.txtdocuments. When set,ram-a-memimports new files into the default case-library dataset on startup.case_library.api_token_env: optional environment variable containing the dedicated administrator token for the case-management REST API. Omit it to keep that API disabled.case_library.ingestion_poll_ms: polling interval for the ingestion worker embedded inram-a-mem. The worker continuously processes document create/update tasks.case_library.embedding_provider: case-library retrieval embedding provider. It can behashfor demos oropenai_compatiblefor a real/self-hosted embedding service.
For real embedding retrieval, replace the hash embedding fields with an OpenAI-compatible
embedding provider in providers or case_library as needed:
{
"embedding_provider": "openai_compatible",
"embedding_api_key_env": "EMBEDDING_API_KEY",
"embedding_base_url": "http://127.0.0.1:8001/v1",
"embedding_model": "local-embedding-model",
"embedding_dimensions": 1024
}
Start RAM-A memory service:
export RAM_A_XIAOO_TOKEN='replace-with-long-random-token'
export RAM_A_CASES_ADMIN_TOKEN='replace-with-a-separate-admin-token'
export LLM_API_KEY='replace-with-llm-provider-key'
# Required only when features.graph_memory.enabled is true.
export GRAPH_LLM_API_KEY='replace-with-graph-provider-key'
cargo run -p memory-mcp --bin ram-a-mem
The command above reads config/ram-a-mem.json by default. To use another path:
RAM_A_MEM_CONFIG=/etc/ram-a/ram-a-mem.json ram-a-mem
Do not point case_library.index_store at storage.database_path; case index rebuilds and
personal long-term memories must remain isolated even though both capabilities are served
from the same ram-a-mem process and HTTP port.
When case_library.api_token_env is configured, ram-a-mem also serves the authenticated
case-management API under /api/v1. Creating or updating a document enqueues an ingestion
task; the background worker in the same process parses, chunks, and indexes it. Do not start
a separate memory-cases --api or memory-cases --ingestor process.
MCP client config
For xiaoO, create .mcp.json; see
plugins/mcp/xiaoo.mcp.json for a copyable example. Other MCP
clients may use a different config file shape, but they need the same connection values:
Streamable HTTP transport, /mcp URL, bearer token, agent ID if supported, timeout, and MCP
protocol version 2025-11-25.
{
"mcpServers": {
"ram-a": {
"transport": "streamable_http",
"url": "http://127.0.0.1:18081/mcp",
"bearer_token_env": "RAM_A_XIAOO_TOKEN",
"agent_id": "xiaoo",
"timeout_ms": 30000
}
}
}
Change these fields:
url: RAM-A MCP endpoint. RAM-A currently exposes MCP only at/mcp.bearer_token_env: client-side environment variable containing the same token configured in RAM-Aauth.tokens[*].token_env.agent_id: must match RAM-Aauth.tokens[*].agent_idwhenX-Agent-IDis sent.timeout_ms: increase this if memory extraction or provider calls are slow.
Do not put Authorization, Origin, X-Agent-ID, mcp-session-id, or
mcp-protocol-version into static MCP headers. A compliant Streamable HTTP MCP client should
manage them per request/session. Non-xiaoO clients must still send bearer auth and negotiate
MCP protocol version 2025-11-25.
After initialization, clients can discover RAM-A tools with tools/list. The expected tools
are:
memory_search: search authenticated personal long-term memory.memory_ingest: extract, ground, and persist authenticated conversation memory.memory_case_search: first-use tool for troubleshooting, incident diagnosis, root-cause analysis, remediation steps, operational case lookup, or similar historical case questions. Also use it when the user asks whether there were similar past cases, previous incidents, known fixes, or examples for a troubleshooting symptom, even if they do not explicitly say "case library" or name the tool. It searches an authorized case library whencase_libraryis configured and the token hascases:read.memory_case_prepare_upload: after diagnosis, stage a proposed UTF-8 Markdown/text case without writing it, and return a proposal plus a one-time confirmation token.memory_case_prepare_update: stage a proposed replacement for an existing case without writing it, and return the same confirmation information.memory_case_prepare_delete: stage the proposed deletion of an existing case, including a required deletion reason, without removing anything.memory_case_upload: consume a prepared upload token after explicit user confirmation and return a generated ingestion task ID.memory_case_update: consume a prepared update token after explicit user confirmation; the replacement is indexed asynchronously by the in-process ingestion worker.memory_case_delete: consume a prepared delete token after explicit user confirmation and immediately remove the document, source file, tasks, chunks, and search records.
All six mutation tools require cases:write. Preparation accepts library rather than
dataset_id, so the server keeps dataset selection and tenant authorization under configuration
control. After diagnosis, prepare an upload with arguments such as:
{
"library": "ops",
"document_id": "dns-case-001",
"file_name": "dns-failure.md",
"name": "DNS failure recovery",
"diagnosis_summary": "The local resolver held a stale record; flushing it restored DNS.",
"content": "# DNS failure\n\nFlush the resolver cache and verify the upstream DNS server."
}
Prepare a deletion with the exact document ID and a reason that xiaoO can show to the user:
{
"library": "ops",
"document_id": "dns-case-001",
"deletion_reason": "This case is obsolete and its remediation is no longer safe."
}
The preparation call does not modify the case library. The client must display its proposal and
ask the user, then end the turn. Only after a later explicit confirmation may it call the matching
final tool with {"confirmation_token":"...","user_confirmed":true}. Tokens expire after ten
minutes, are bound to tenant/user/agent and operation, are single-use, and are lost on service
restart. Upload and update return ingestion_status: "pending", and the background worker in
ram-a-mem processes those tasks automatically. Delete completes synchronously and returns
deleted: true. case_library.api_token_env is not required for these MCP tools—it only
controls the separate REST management API.
If the client model does not reliably choose the case-library tool by itself, add the prompt
snippet in plugins/mcp/case-tool-instruction.md to
the client's system prompt, role prompt, or demo prompt. This is a client-side guidance issue;
RAM-A still exposes the tool through standard tools/list and does not force a call.
For xiaoO automatic memory, add this to xiaoO config.toml:
[memory_automation]
enabled = true
server = "ram-a"
recall_top_k = 5
recall_token_budget = 512
context_messages = 4
queue_path = "memory-automation-queue.jsonl"
queue_capacity = 256
max_retries = 5
retry_backoff_ms = 250
allowed_agent_roles = ["main", "defaultagent"]
This setting enables pre-turn recall and post-turn durable ingest queueing. It does not modify the user's original prompt. If RAM-A is unavailable, xiaoO should degrade memory behavior and continue normal chat.
Check the services:
curl -i http://127.0.0.1:18081/healthy
curl -i http://127.0.0.1:18081/ready
See docs/guides/http-mcp-deployment.md
for secure local deployment, xiaoO .mcp.json, automatic memory settings,
health checks, and the current single-instance boundary.
See
docs/guides/xiaoo-case-library-integration.md
for the memory_case_search contract and the xiaoO integration boundary.
Evaluation
Start from evaluation/README.md for the benchmark overview.
The current evaluation entry points are:
- PersonaMem: evaluation/personalmem/README.md
- LongMemEval: evaluation/longmemeval/README.md
- LoCoMo: evaluation/locomo/README.md
Benchmark-prepared schema guidance lives in docs/benchmarks/prepared-schema-v1.md. Baseline result storage guidance lives in evaluation/baselines/README.md.
Verification
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
PYTHONPATH=evaluation python -m pytest evaluation