One memory layer. Every agent. Local by default.
Your agent forgot who you are. Again. Dory fixes that.
Every AI agent you use keeps its own half-memory.
- Claude remembers one slice.
- Codex keeps another.
- opencode writes to yet another folder.
- OpenClaw and Hermes park sessions somewhere else entirely.
- The next model still asks what you're building, what you prefer, and what already happened.
You end up re-explaining yourself on loop. Decisions get lost. Project state goes stale. No memory actually follows you across tools.
A local-first memory daemon that gives every agent the same brain.
Markdown is the source of truth. SQLite is a disposable sidecar. Agents read and write through a narrow API — wake, search, get, memory-write, link — so Claude, Codex, opencode, OpenClaw, Hermes, and anything with HTTP or MCP share one memory substrate while keeping their own personality.
Dory isn't trying to make every agent identical. It's giving them the same memory so they can act like they share a brain.
git clone <dory-repo-url>
cd dory
uv sync --frozen
mkdir -p data/corpus
export DORY_CORPUS_ROOT="$PWD/data/corpus"
export DORY_INDEX_ROOT="$PWD/.dory/index"
export DORY_AUTH_TOKENS_PATH="$PWD/.dory/auth-tokens.json"
uv run dory initTry it:
uv run dory memory-write "Atlas is the active focus this week." \
--subject atlas --kind decision --force-inbox
uv run dory search "active focus"
uv run dory wake --profile coding --budget 1200Serve it over HTTP:
uv run dory-http --corpus-root data/corpus --index-root .dory/index \
--host 127.0.0.1 --port 8766Or run it as a durable container:
cp .env.example .env
mkdir -p data/corpus
docker compose up -d --buildDocker binds HTTP to 127.0.0.1:8766 by default. Only set DORY_HTTP_BIND=0.0.0.0 behind a trusted LAN, VPN, reverse proxy, or firewall.
Compose builds with network: host so dependency installs use the host resolver on private DNS setups. If runtime containers cannot resolve external hosts, set DORY_DOCKER_DNS_SERVERS in .env. Raw GEMINI_API_KEY / OPENROUTER_API_KEY values are passed through as compatibility aliases for providers that expect those names.
Full walkthrough → docs/getting-started.md
wake → search → get → memory-write → link
- wake — bounded hot context at session start
- search — hybrid search across durable memory and session evidence
- get — exact markdown, with hashes and metadata
- memory-write — semantic writes (facts, preferences, decisions, project state)
- link — backlinks, neighbors, graph structure
Markdown stays editable by hand. Open it in Obsidian, diff it in git, inspect it in the browser wiki, or let agents update it through guarded write APIs. You always have a human-readable audit trail.
| Surface | What it does |
|---|---|
| CLI | uv run dory — init, search, memory-write, research, ops jobs, migrations |
| HTTP daemon | /v1/wake, /v1/active-memory, /v1/search, /v1/research, /v1/get, /v1/write, /v1/memory-write, /v1/purge, /v1/session-ingest, /v1/link, /v1/status, /v1/stream, /metrics, /wiki |
| Native MCP | uv run dory-mcp --mode stdio or --mode tcp |
| MCP bridge | HTTP-backed bridge for remote daemons |
| Hermes provider | plugins/hermes-dory/ |
| OpenClaw package | packages/openclaw-dory/ |
| Browser wiki | Read/edit the corpus from a browser (auth-gated) |
- Repo-local — development, experiments, throwaway corpora.
- Same-host daemon — one workstation, all local agents hit
127.0.0.1. - Docker service — durable always-on daemon, bind-mounted markdown corpus.
- Private remote host — LAN box, VPN host, or VPS reachable over HTTP by multiple machines.
The corpus, index, auth tokens, public URL, and model provider keys are environment-specific. Keep them out of the public repo.
- Language — Python (uv + pyproject)
- Storage — Markdown source of truth · SQLite (FTS5, graph edges, embedding cache, chunk vectors, session evidence)
- Embeddings — Gemini by default, or an OpenAI-compatible local/LAN embedding endpoint with
DORY_EMBEDDING_PROVIDER=local - Dreaming & maintenance LLM — Gemini 3.1 Flash via OpenRouter
- Active-memory LLM — optional · OpenRouter or any OpenAI-compatible local/LAN endpoint (Ollama, LM Studio, vLLM). The runtime default is OpenRouter when configured;
.env.examplesets it toofffor deterministic retrieval-only installs - Auth — bearer tokens via
.dory/auth-tokens.json;DORY_ALLOW_NO_AUTH=truefor local dev only
Dory is a composite of patterns that already worked:
- Karpathy's LLM Wiki — a persistent markdown layer that compounds instead of forcing rediscovery. Dory keeps that, but generates the wiki from a structured memory core.
- gbrain — human-readable canonical pages, source-backed evidence, entity resolution, backlinks, read-before-write discipline.
- Mem0 — scoped memory APIs, explicit add/update/delete semantics, memory ops as first-class tools instead of hidden chat history.
- MemPalace / memory palace systems — bounded wake-up context, local-first session storage, transcript mining, layered recall.
- Markdown + git — plain files, diffs, reviews, backups, human inspection.
The goal is practical: one memory layer for all agents, with enough structure to stay useful and enough plain text to stay debuggable.
- Core — CLI, HTTP, MCP, search, and semantic writes are in-repo and covered by tests.
- Default runtime — local-first. Server and corpus live wherever you check out.
- Corpus — a fresh checkout ships without
core/user.md,core/soul.md,core/env.md,core/active.md, or thewiki/tree. You populate those. - Locked goals — (1) frozen wake-up block, (2) cross-agent shared memory.
- Public tree — current implementation, synthetic evals, and integration surfaces. Private planning notes stay private.
| Getting started | Install, init, first wake |
| Agent integration | Wire up Claude, Codex, opencode, OpenClaw, Hermes |
| Contributing | Development setup, validation, commit rules, PR rules |
| Agent guide | Shared instructions for coding agents working in this repo |
| Codebase map | Where everything lives |
| Runtime & data flow | How requests move through the system |
| Surfaces & integrations | CLI, HTTP, MCP, providers |
| Operations & validation | Dream, maintain, reindex, migrate |
| Ops runbook | Day-to-day operation |
| Client runbook | For agent integrators |
| Evals | Benchmarks and coverage |
Contributions are welcome, but the public repo has a hard privacy boundary. Use synthetic data in docs, tests, evals, examples, and fixtures. Do not commit private corpora, raw session logs, real personal memories, direct contact details, local absolute paths, private hostnames, tokens, or .env files.
Read CONTRIBUTING.md before opening a PR. The short version: use Conventional Commits, keep changes scoped, run the relevant uv checks, and run scripts/release/check-public-safety.py for public docs or artifacts.
CLI & search
uv run dory # root command
uv run dory init # new corpus
uv run dory search "query" # hybrid search
uv run dory memory-write "..." --subject x --kind decision
uv run dory research "What are we working on?" --kind reportSet DORY_GEMINI_API_KEY or GOOGLE_API_KEY before starting HTTP/MCP or any command that embeds, searches, writes semantic memory, reindexes, or runs evals with the default Gemini provider. To use an OpenAI-compatible local/LAN embedding endpoint instead, set DORY_EMBEDDING_PROVIDER=local with DORY_LOCAL_EMBEDDING_*. Local query embeddings use DORY_LOCAL_EMBEDDING_QUERY_INSTRUCTION for Qwen-style retrieval prompts; set it blank to disable. LLM query planning, expansion, and reranking are opt-in via DORY_QUERY_PLANNER_ENABLED, DORY_QUERY_EXPANSION_ENABLED, DORY_QUERY_RERANKER_ENABLED; local reranking uses DORY_QUERY_RERANKER_PROVIDER=local and DORY_LOCAL_RERANKER_*. DORY_QUERY_RERANKER_CANDIDATE_LIMIT caps how many candidates are sent to the reranker per search.
HTTP & MCP
uv run dory-http --corpus-root <corpus> --index-root <index>
uv run dory-mcp --mode stdio
uv run dory-mcp --mode tcp --host 127.0.0.1 --port 8765Example MCP config: scripts/claude-code/mcp.example.json. HTTP bearer tokens: uv run dory auth new <name>. The browser wiki login also needs DORY_WEB_PASSWORD.
Ops jobs
uv run dory ops dream-once # batch dream pass (+ recall-promotion distillation)
uv run dory ops daily-digest-once # summarize shipped sessions into digests/daily/
uv run dory ops maintain-once # maintenance pass
uv run dory ops wiki-refresh-once # rebuild compiled wiki
uv run dory ops eval-once # eval batch
uv run dory ops watch # foreground corpus watcherInstallers: scripts/ops/install-dory.sh, install-backup-cron.sh, install-ops-launchd.sh.
Legacy corpus migration
uv run dory --corpus-root <corpus> migrate <legacy-corpus>
uv run dory --corpus-root <corpus> migrate --estimate --sample 25 <legacy-corpus>
uv run dory --corpus-root <corpus> migrate --interactive <legacy-corpus>Stages docs, normalizes to markdown evidence, classifies, extracts memory atoms, bootstraps canonical pages, writes a migration report, quarantines edge cases. Afterwards run ops wiki-refresh-once.
Session ingestion
Session evidence is stored separately from durable memory. client and solo installs auto-discover local sessions via scripts/ops/client-session-shipper.py. The shipper keeps a local spool plus checkpoint state and polls known harness stores — no manual --source. Endpoint: POST /v1/session-ingest. search with mode="recall" reads the session evidence plane directly.
Semantic writes
Preferred write surface is semantic, not path-first.
- CLI:
uv run dory memory-write "Atlas prefers concise status notes." --subject atlas --kind preference - HTTP:
POST /v1/memory-write - MCP:
dory_memory_write - OpenClaw:
memory_write - Hermes:
memory_write(...)
Path-first write stays available for compatibility and debug flows.
MIT — see LICENSE.
Not affiliated with, endorsed by, or connected to Disney or Pixar. "Dory" is an affectionate nod to the fish who couldn't hold a thought — a fitting mascot for a memory daemon. The GIF is embedded from Giphy as fan reference under fair use. If any rights holder objects, open an issue and it's gone.
Just keep swimming. 🐟