agentic-box/memora

★ 727⑂ 0

Give your AI agents persistent, collective memory — with deduplicating absorb, supersession lineage, semantic search, and a graph UI. Speaks MCP.

About agentic-box/memora

agentic-box/memora is an open-source project on GitHub, mainly written in Python. Give your AI agents persistent, collective memory — with deduplicating absorb, supersession lineage, semantic search, and a graph UI. Speaks MCP. It currently holds 727 stars and 0 forks with 0 open issues, and was last pushed on an unknown date (repository created unknown).

Project Overview

AI Homed tracks it on the AI Agent Memory board.

GitHub Repository Details

Repository agentic-box/memora · default branch - · size 0 KB · watchers 0 · source: GitHub REST API and repository README

README

https://github.com/agentic-box/memora/blob/HEAD/Memora Logo Memora

"You never truly know the value of a moment until it becomes a memory."

Give your AI agents persistent collective memory
An MCP memory layer for agents: structured storage, semantic retrieval, graph relations, and source-backed cross-session context.

https://github.com/agentic-box/memora/blob/HEAD/Version https://github.com/agentic-box/memora/blob/HEAD/License https://github.com/agentic-box/memora/blob/HEAD/Mentioned in Awesome Claude Code

https://github.com/agentic-box/memora/blob/HEAD/Memora absorb and digest flow

Absorb agent work into durable graph memory, then use memory_digest(topic) to retrieve relevant memories, TODOs/issues, related edges, and source IDs.

Features · Preview · Install · Usage · Config · Multi-DB · Containers · Live Graph · Cloud Graph · Chat · Semantic Search · Documents · LLM Dedup · Linking · Neovim

Features

Core Storage

Absorb & Lineage Search & Intelligence Document Storage Tools & Visualization

Preview

https://github.com/agentic-box/memora/blob/HEAD/Memora memory graph demo https://github.com/agentic-box/memora/blob/HEAD/Memora memory interaction demo

Install

Two paths. pip is a local stdio child the client spawns. A container is a detached HTTP service you start with up; with MEMORA_DATABASES it serves multiple stores from one process. The LaunchAgent supervises the proxy, not the container — after a host restart the listener can come back while its upstream is still stopped. If you are running memora as a service, the container path is the install.

pip (local / stdio)

pip install memora-mcp

The PyPI package is memora-mcp (bare memora on PyPI is an unrelated project). Includes cloud storage (S3/R2) and OpenAI embeddings out of the box.

# Optional: local embeddings (offline, ~2GB for PyTorch)
pip install "memora-mcp[local]"

Latest development version straight from git

pip install "git+https://github.com/agentic-box/memora.git"

Then spawn it from .mcp.json with "command": "memora-server" (see Configuration).

Container (HTTP service)

Default runtime is Apple's container CLI. Every container operation scripts/memora-instance.sh performs (build, up, status, logs, down) uses $MEMORA_CONTAINER_BIN (default container). The generated proxy process does not; it hardcodes container list.

Before the first build:

1. Install Apple's container CLI (signed pkg from its GitHub releases). It needs a Mac with Apple silicon running macOS 26 — Apple does not support older macOS versions for container. 2. Start the runtime — Apple's documented first command, which also installs a kernel if none is configured:

   container system start
   

3. Clone this repo and cd into it:

   git clone https://github.com/agentic-box/memora.git
   cd memora
   

4. Copy the instance template. It ships with INSTANCE=myinstance so the later build/up/proxy lines match without renaming. Edit PORT and a backend (STORAGE_URI, VOLUME, or MEMORA_DATABASES):

   cp instances/example.env instances/myinstance.env
   

5. Create the credential file and install the proxy the LaunchAgent will run. cred_args() requires a .mcp.json whose mcpServers.memora.env holds CLOUDFLARE_API_TOKEN (D1 access) and the embedding/LLM keys — up dies if that file is missing. The script looks for ~/.config/memora/credentials.mcp.json if that file exists, otherwise ~/repos/agentic-box/.mcp.json. Set CRED_SOURCE in the instance file to pick a path. Separately, proxy renders a plist whose executable is $MEMORA_PROXY_BIN (default ~/.local/libexec/memora/memora_proxy.py) and whose logs live in $MEMORA_LOG_DIR (default ~/.local/var/log) — nothing creates either on a fresh clone.

   mkdir -p ~/.config/memora ~/.local/libexec/memora ~/.local/var/log
   cp scripts/memora_proxy.py ~/.local/libexec/memora/
   # real values; any key is fine, an absent file is not
   # the default umask is permissive -- chmod 600 keeps other local accounts out
   cat > ~/.config/memora/credentials.mcp.json <<'JSON'
   {"mcpServers":{"memora":{"env":{"CLOUDFLARE_API_TOKEN":"REPLACE","OPENAI_API_KEY":"REPLACE"}}}}
   JSON
   chmod 600 ~/.config/memora/credentials.mcp.json
   

That JSON is the minimal correct config: both the LLM and embeddings use the default OpenAI host with a real OpenAI key. Do not add OPENAI_BASE_URL pointing at OpenRouter without the embedding pair from Embeddings — OpenRouter has no embeddings endpoint, every embed call 404s, and memora silently falls back to TF-IDF keyword bags while looking healthy.

Then:

./scripts/memora-instance.sh build myinstance   # tags IMAGE from myinstance.env (memora-pilot if IMAGE is unset)
./scripts/memora-instance.sh up      myinstance # runs that same IMAGE
./scripts/memora-instance.sh proxy   myinstance # render the LaunchAgent; run the printed launchctl

up does not publish a host port. The listener the workspace connects to is the proxy. proxy only renders a macOS LaunchAgent and prints the launchctl commands — it does not load the service. Run those printed commands.

The printed workspace URL is always http://127.0.0.1:/mcp (the registry default). For a non-default store, append / yourself — a bare /mcp on a registry silently binds MEMORA_DEFAULT_DB:

{"mcpServers": {"memora": {"type": "http", "url": "http://127.0.0.1:/mcp/"}}}

Proxy rationale, credentials, instance files, and MEMORA_CONTAINER_BIN: Container Deployment.

Usage

The server runs automatically when configured in Claude Code. Manual invocation:

# Default (stdio mode for MCP)
memora-server

With graph visualization server

memora-server --graph-port 8765

HTTP transport (alternative to stdio)

memora-server --transport streamable-http --host 127.0.0.1 --port 8080

Configuration

Claude Code

Add to .mcp.json in your project root:

Local DB:

{
  "mcpServers": {
    "memora": {
      "command": "memora-server",
      "args": [],
      "env": {
        "MEMORA_DB_PATH": "~/.local/share/memora/memories.db",
        "MEMORA_ALLOW_ANY_TAG": "1",
        "MEMORA_GRAPH_PORT": "8765"
      }
    }
  }
}

Cloud DB (Cloudflare D1) - Recommended:

{
  "mcpServers": {
    "memora": {
      "command": "memora-server",
      "args": ["--no-graph"],
      "env": {
        "MEMORA_STORAGE_URI": "d1:///",
        "CLOUDFLARE_API_TOKEN": "",
        "MEMORA_ALLOW_ANY_TAG": "1"
      }
    }
  }
}

With D1, use --no-graph to disable the local visualization server. Instead, use the hosted graph at your Cloudflare Pages URL (see Cloud Graph).

Cloud DB (S3/R2) - Sync mode:

{
  "mcpServers": {
    "memora": {
      "command": "memora-server",
      "args": [],
      "env": {
        "AWS_PROFILE": "memora",
        "AWS_ENDPOINT_URL": "https://.r2.cloudflarestorage.com",
        "MEMORA_STORAGE_URI": "s3://memories/memories.db",
        "MEMORA_CLOUD_ENCRYPT": "true",
        "MEMORA_ALLOW_ANY_TAG": "1",
        "MEMORA_GRAPH_PORT": "8765"
      }
    }
  }
}

Codex CLI

Add to ~/.codex/config.toml:

[mcp_servers.memora]
  command = "memora-server"  # or full path: /path/to/bin/memora-server
  args = ["--no-graph"]
  env = {
    AWS_PROFILE = "memora",
    AWS_ENDPOINT_URL = "https://.r2.cloudflarestorage.com",
    MEMORA_STORAGE_URI = "s3://memories/memories.db",
    MEMORA_CLOUD_ENCRYPT = "true",
    MEMORA_ALLOW_ANY_TAG = "1",
  }

Environment Variables

| Variable | Description | |------------------------|-----------------------------------------------------------------------------| | MEMORA_DB_PATH | Local SQLite database path (default: ~/.local/share/memora/memories.db) | | MEMORA_STORAGE_URI | Storage URI: d1:/// (D1) or s3://bucket/memories.db (S3/R2). Used when MEMORA_DATABASES is unset. | | MEMORA_DATABASES | JSON object {name: uri} mapping each store this process serves. Names are one URL path segment (/mcp/): letters, digits, -, _, . only. Duplicate keys, empty values, unsafe names, or non-objects refuse to start rather than silently picking a store. Unset = single-store (legacy). See Multi-database routing. | | MEMORA_DEFAULT_DB | Registry name a bare /mcp uses. Required when the registry has more than one database; with exactly one name, that name is the default. A value not in the registry refuses to start. | | CLOUDFLARE_API_TOKEN | API token for D1 (d1:// URI). CF_API_TOKEN is accepted as an alias. | | MEMORA_CLOUD_ENCRYPT | Encrypt the local file before uploading to S3/R2. Unset/false = off; 1/true/yes = on. | | MEMORA_CLOUD_COMPRESS| Compress the local file before uploading to S3/R2. Unset/false = off; 1/true/yes = on. | | MEMORA_CACHE_DIR | Local cache directory for an S3/R2-synced database. Unset: the backend picks a cache path. | | MEMORA_ALLOW_ANY_TAG | Allow any tag without validation against allowlist (1 to enable) | | MEMORA_TAG_FILE | Path to a JSON file containing an array of allowed tags, e.g. ["plan", "memora/issues"] | | MEMORA_TAGS | Comma-separated list of allowed tags | | MEMORA_HOST | Bind address for HTTP transports (default 127.0.0.1). Overridable with --host. | | MEMORA_PORT | Bind port for HTTP transports (default 8000). Overridable with --port. | | MEMORA_GRAPH_PORT | Port for the knowledge graph visualization server (default: 8765) | | MEMORA_TRANSPORT | stdio (default), sse, or streamable-http. An unknown env value falls back to stdio; --transport still rejects unknown values. Multi-database routing and the session guard run only on streamable-http. | | MEMORA_TOOL_PROFILE | Tool subset exposed to clients: full (default, all 43), leader (19), agent (12). Unset/empty = full; an unknown value refuses to start. See Tool Profiles. | | MEMORA_MAX_SESSIONS | Hard ceiling on concurrent MCP sessions (default 128). 0 disables. A creation rate plus an idle timeout is not a bound — a client that keeps session ids alive can grow without limit at the creation rate. Invalid values refuse to start. Streamable-HTTP only. | | MEMORA_MAX_INIT_PER_MIN | New sessions admitted per minute (default 120). 0 disables. Invalid values refuse to start. Streamable-HTTP only. | | MEMORA_MAX_INIT_BODY_BYTES | Maximum initialize request body accepted/buffered (default 65536, minimum 1024). Larger requests receive 413. Invalid values refuse to start. Streamable-HTTP only. | | MEMORA_SESSION_IDLE_TIMEOUT | Seconds before an abandoned valid session is reaped (default 1800). 0 disables. Invalid values refuse to start. Streamable-HTTP only. | | MEMORA_HEALTH_TOKEN | Bearer token for detailed /health/db bodies (names, counts, error text). Unset: only a loopback peer sees detail; everyone else gets aggregate status. FastMCP custom_route() is unauthenticated even when MCP auth is configured. HTTP transports only (memora.health is imported for SSE/streamable-http, not stdio). | | MEMORA_HEALTH_TTL | Seconds a readiness snapshot may be served before a refresh is due (default 10, cap 3600). Must be > 0. Invalid values refuse to start. HTTP transports only — a malformed value does not abort stdio. | | MEMORA_HEALTH_TIMEOUT| Bound on one refresh pass and on each store probe (default 15, cap 300). Must be > 0. HTTP transports only. | | MEMORA_HEALTH_REFRESH_INTERVAL | How often the server refreshes readiness on its own (default 15, cap 3600). 0 = poll-only. Without this, a proxy deployment has no loopback caller and the alert surface stays unknown while every database is fine. When periodic refresh is enabled, interval + timeout must be < MEMORA_HEALTH_MAX_STALE. HTTP transports only. | | MEMORA_HEALTH_MAX_STALE | Age after which a cached per-database result may no longer be reported ready (default 60, cap 3600). Must be >= MEMORA_HEALTH_TTL. HTTP transports only. | | MEMORA_STALE_DAYS | Two consumers, two defaults, same name: memory_insights treats an open TODO/issue as stale after 14 days; the graph UI greys closed items after 30 days. Set the variable to override both. | | MEMORA_EMBEDDING_MODEL | Embedding backend: openai (default), sentence-transformers, or tfidf | | SENTENCE_TRANSFORMERS_MODEL | Model for sentence-transformers (default: all-MiniLM-L6-v2) | | MEMORA_EMBEDDING_API_KEY | Embedding provider API key (atomic with base URL — see below) | | MEMORA_EMBEDDING_BASE_URL | Embedding provider base URL (atomic with API key — see below) | | MEMORA_EMBEDDING_STRICT | Recommend 1. Fail hard on embedding errors instead of silent TF-IDF. Without it a broken endpoint keeps answering while every vector becomes a keyword bag (how 756 memories degraded unnoticed). | | OPENAI_API_KEY | LLM only (dedup/chat) when MEMORA_EMBEDDING_* is set. Embeddings fall back to this key only if both MEMORA_EMBEDDING_API_KEY and MEMORA_EMBEDDING_BASE_URL are unset | | OPENAI_BASE_URL | LLM base URL (OpenRouter, Azure, etc.). Same atomic fallback rule as the key — not an embeddings URL when you use a split config | | OPENAI_EMBEDDING_MODEL | Model id for the openai embedding backend. Must exist on the embedding host (default text-embedding-3-small is OpenAI-only; Cloudflare needs e.g. @cf/baai/bge-m3) | | MEMORA_LLM_ENABLED | Enable LLM-powered deduplication comparison (true/1/yes; default: true) | | MEMORA_LLM_MODEL | Model for deduplication comparison and, if unset, for query rewrite and local chat (default: gpt-4o-mini). Pick a plain chat/instruct model, not a reasoning model — a reasoning model was measured at 12-17s per absorb classification call (1000-1700 reasoning tokens it does not let OpenRouter's reasoning.effort/reasoning.max_tokens bound), vs 3-4s for gpt-4o-mini on the same calls. | | MEMORA_ABSORB_CONCURRENCY | Worker count for memory_absorb's concurrent classify phase (default 4; non-numeric or <1 falls back to 4; 1 disables the thread pool and classifies sequentially). Only the per-fact LLM classification call is parallelized — embeds and searches stay sequential (cheap, and conn isn't safe to touch from worker threads: sqlite3 connections are thread-affine by default and D1Connection carries mutable session-token state). | | MEMORA_LLM_TIMEOUT | Seconds the OpenAI client waits (default 60, floored at 1). A non-numeric value falls back to 60. | | MEMORA_REWRITE_MODEL | Model for RAG query rewriting in the graph chat panel. Unset/empty uses MEMORA_LLM_MODEL. | | MEMORA_VECTOR_SCAN_PAGE_SIZE | Rows per page when loading embeddings from D1 (default 1000; non-numeric or <1 falls back to 1000; hard ceiling 10000). At the default, a store under 1000 rows returns the entire corpus plus every embedding in one D1 response, which raced Cloudflare's 30s per-request ceiling and made memory_absorb fail outright. Use 100 on D1 (the instance script already injects that). Paging is a mitigation, not the fix: absorb reads the corpus once per call and reuses a process-local cache keyed on the DB's monotonic embedding_change_epoch. | | MEMORA_REBUILD_CHUNK_SIZE | Rows per batch in memory_rebuild_embeddings (default 32; non-numeric or <1 falls back to 32). Each batch is one compute_embeddings_batch call and one conn.commit(), instead of one embed call and one commit per row — on D1 this collapses the lease-heartbeat round trips from two per row to two per batch, which dominated wall time far more than the per-row commits did (D1's commit() is already a no-op). | | CHAT_MODEL | Model for the local graph chat panel. Unset/empty falls back to MEMORA_LLM_MODEL. (The deepseek/deepseek-chat default is Cloudflare Pages wrangler.toml, not this process.) | | MEMORA_CLOUD_GRAPH_ENABLED | true/1/yes to notify the hosted graph of writes (default off). | | MEMORA_CLOUD_GRAPH_WORKER_URL | Worker base URL for those broadcasts (POST /broadcast). Unset: broadcasts are skipped. | | MEMORA_CLOUD_GRAPH_DEBOUNCE | Seconds to batch rapid writes before broadcasting (default 1.0). | | MEMORA_CLOUD_GRAPH_SYNC_SCRIPT | Path captured at startup (default: memora-graph/scripts/sync.sh if that file exists). The current write path does not execute this script — D1 is the source of truth and only the worker broadcast runs. | | AWS_PROFILE | AWS credentials profile from ~/.aws/credentials (useful for R2) | | AWS_ENDPOINT_URL | S3-compatible endpoint for R2/MinIO | | R2_PUBLIC_DOMAIN | Public domain for R2 image URLs |

Tool Profiles (MEMORA_TOOL_PROFILE)

All 43 MCP tools register unconditionally, so every agent session is injected with the full ~12,700-token tool schema even when most tools are never called. MEMORA_TOOL_PROFILE exposes a subset per deployment so a gated tool is genuinely absent — missing from tools/list AND undispatchable (call_tool returns unknown-tool, not a hidden execution). The profile is applied and attested at startup; the active profile and exposed tool count are logged to stderr.

| Value | Tools | Use | |-------|-------|-----| | full (default) | all 43 | Direct stdio use; every existing deployment is byte-for-byte unchanged | | leader | 19 | The agent set plus memory_create_section, memory_store_document, memory_get_document, memory_tags, memory_delete, memory_digest, memory_list | | agent | 12 | The read/create surface a worker agent needs: memory_absorb, memory_semantic_search, memory_hybrid_search, memory_list_compact, memory_get, memory_related, memory_link, memory_stats, memory_create, memory_create_issue, memory_create_todo, memory_update |

  • Unset / empty = full. No existing deployment changes behaviour.
  • An unknown value aborts startup with a message naming the valid values. It never silently falls back to full — a typo must not re-expose destructive maintenance tools (memory_rebuild_embeddings, memory_delete_batch) to every worker. Fail closed.
  • memory_list is in leader but not agent. It was excluded from both while it cost 163-174s on a D1 store against memory_list_compact's 0.22s; #973 fixed that (now ~1.1s). It stays out of agent because a worker's read surface is deliberately narrow, not for speed.
  • The leader/agent boundary is data in memora/tool_profile.py (two frozensets). Editing it is one line, not a sweep of 43 decorators.
  • The prune deletes from FastMCP's private _tool_manager._tools dict, so memora pins mcp>=1.27,<1.28 (the audited minor) and runs a startup attestation through the low-level registered MCP request handlers (_mcp_server.request_handlers[ListToolsRequest] / [CallToolRequest] — the actual dispatch callable real client requests use, not the FastMCP.list_tools / call_tool Python helpers) that refuses to start if the installed SDK routes listing/dispatch elsewhere (private-implementation drift). The pin is the static guard; the attestation is the runtime backstop. Bumping the upper bound requires

GitHub Stars & Activity

727Stars
0Forks
0Open issues
PythonLanguage

GitHub Popularity

GitHub stars727
Forks0
Open issues0
Primary languagePython
License-
Stars gained today0
Created-
Last pushed-

Trending History

Trending statusnot on today's boards

Related AI Projects

1

666ghj / MiroFish

Python★ 74,064⑂ 0
2

mem0ai / mem0

Python★ 65,695⑂ 0
3

bojieli / ai-agent-book

Python★ 48,844⑂ 0
4

volcengine / OpenViking

Python★ 38,148⑂ 0
5

topoteretes / cognee

Python★ 30,855⑂ 0
6

MemoriLabs / Memori

Python★ 16,849⑂ 0
7

NevaMind-AI / memU

Python★ 14,418⑂ 0
8

semantica-agi / semantica

Python★ 13,301⑂ 0

More AI Rankings