Every new chat, your AI forgets. You re-explain the project, the decisions, the gotchas — every time, in every tool. Yggdrasil is a tiny always-on memory that any agent plugs into. Open a new session, in any project, with any AI, and it already knows what you decided, what broke, and what's still open.

$ cd ~/projects/checkout-api && claude        # a brand-new session

🌳 Yggdrasil  (injected automatically at session start)
   • [project_status] payments refactor: idempotency keys added; open: e2e tests
   • [lesson] webhook 401 → signing secret rotated; update env + redeploy

> "have I solved a flaky websocket reconnect anywhere before?"

🌳 recall → found in project `realtime-dash`:
   refresh the token *before* opening the socket, then retry with capped backoff.

No "let me remind you what we did yesterday." It's just there.

🚀 Install

Two commands, inside Claude Code (the plugin launches via uv):

/plugin marketplace add VonderVuflya/Yggdrasil
/plugin install yggdrasil

The engine lazy-starts on first use and generates its own local token — no API key, no cloud, nothing to configure. Codex and Cursor use the same flow.

Host / tool Command
uvx (recommended CLI) uvx --from yggdrasil-memory ygg install
npm / npx npx yggdrasil-memory install
pipx pipx install yggdrasil-memory && ygg install
pip pip install yggdrasil-memory && ygg install
Homebrew (macOS) brew install VonderVuflya/tap/yggdrasil && ygg install
Claude Desktop (app) drag the .mcpb from the latest release onto Settings → Extensions, paste your token (ygg token) — the desktop app then shares the same memory as your CLI agents (guide)
from source uvx --from git+https://github.com/VonderVuflya/yggdrasil.git ygg install

ygg install is a one-time guided setup: it installs an always-on background service, registers the MCP tools with every agent host it finds — Claude Code, Codex, OpenCode — and, if your hardware allows, recommends optional local models (or pick none to stay zero-config).

Install OpenCode first, then run ygg install (or ygg redeploy if Yggdrasil is already set up) — the entry is written for you and merged into any existing opencode.json. Confirm with:

opencode mcp list        # -> ✓ yggdrasil connected

Installed OpenCode after Yggdrasil? Just re-run ygg install.

If you'd rather write it by hand, note that OpenCode's schema differs from Claude's in four places at once — servers live under mcp (not mcpServers), type is required, command is one array (not command + args), and env is environment (not env), so the Claude snippet won't port:

// ~/.config/opencode/opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "yggdrasil": {
      "type": "local",
      "command": ["/path/to/python3", "~/.yggdrasil/scripts/ygg_mcp_server.py"],
      "enabled": true,
      "environment": { "YGG_ENGINE_URL": "http://127.0.0.1:42069" }
    }
  }
}

No token goes in the config — the engine reads the 0600 ~/.yggdrasil/token itself. Run ygg doctor if the tools don't show up.

There is also a yggdrasil-memory skill for any Claude surface: MCP connects the tools, the skill teaches the agent when to use them. Use both for the best behavior.

Try it with nothing installed and a throwaway DB: uvx --from yggdrasil-memory ygg serve --reset --db /tmp/ygg.sqlite.

Then just work: ask your agent "recall what we decided about this project", tell it "remember this decision" — next session it's already there. Verify the install any time with ygg doctor.

Already have history? Seed memory from your existing Claude Code + Codex transcripts, Obsidian vaults, and CLAUDE.md repos — distilled locally:

ygg seed --dry-run    # see what it would import; drop the flag to distill for real

Leaving another memory tool? ygg import --from mcp-memory --path memory.json pulls its whole store into Yggdrasil (deduped, secret-guarded) — then you can delete it.

Why

  • 🧠 Persistent — decisions, lessons, and project status survive across sessions.
  • 🔌 One brain, every tool — Claude Code, Codex, OpenCode, and any MCP host share the same memory.
  • 🌐 Cross-project recall"this looks like what you did in project B — reuse it?"
  • 🧹 Curated, not captured — your agent saves the few things that matter; governance dedupes and archives, never deletes.
  • 🌱 Self-maintaining (opt-in) — a small local model consolidates memory in the background. Zero API tokens.
  • 🪪 One identity everywhere — an optional name and persona every agent picks up, so Claude Code and Codex feel like the same assistant.
  • 🔒 100% local — your memory lives on your machine. No cloud, no account, no telemetry.

🧠 How it works

Yggdrasil is memory + tools — the intelligence is your LLM. It just makes sure the right memory is in front of the right agent at the right moment.

  • 🛎️ Always-on daemon — a tiny local service (~21 MB RAM) your agents reach over MCP tools (ygg_search, ygg_recall, ygg_remember …).
  • 🪝 Hooks — session start auto-injects identity, project status, and open follow-ups (~300 tokens); an optional per-prompt hook auto-recalls memory relevant to each request.
  • 📌 Ranking — pinned and frequently-recalled memories surface first.
  • 🧹 Governance — duplicates and conflicts are queued for review; changes are non-destructive (archive, never delete).
  • 📓 Obsidian — every memory doubles as a plain-Markdown note you can read, edit, and grep.

🎛️ Memory tiers — zero-config by default

Out of the box, Yggdrasil runs on SQLite + FTS5 with zero dependencies — instant keyword search, no models, nothing to download. Optional local models via Ollama add two independent tiers:

Tier You add You gain
0 · default nothing — SQLite + FTS5 keyword search, zero deps, instant — recall@1 = 0.77
1 · semantic an embedding model (all-minilm 45 MB · paraphrase-multilingual ~560 MB) search by meaning, across languages — recall@1 = 0.94, recall@3 1.00
2 · self-maintaining a small LLM (qwen2.5:1.5b ~1 GB) background dedupe/merge of memory (propose-only)

Ollama only computes vectors and runs the background model — every memory and every vector stays in the same local SQLite. ygg install detects your hardware and recommends a fit (ygg recommend shows the full catalog).

Embeddings (semantic search):

Model Size Good for
all-minilm 45 MB English, tiny & fast
nomic-embed-text 274 MB English, better quality (768d)
mxbai-embed-large 670 MB English, high quality (1024d)
paraphrase-multilingual ~560 MB multilingual (EN/RU + 50 langs, 768d)
bge-m3 1.2 GB multilingual, top quality (heavier)

Embedding backend — Ollama by default. To use an OpenAI-compatible /v1/embeddings server instead (llama.cpp's llama-server --embeddings, OpenRouter, LM Studio, vLLM), set embed_backend:

# local llama.cpp — no key needed
ygg config set embed_backend openai
ygg config set embed_url http://127.0.0.1:8080/v1
ygg config set embed_model bge-small-en-v1.5
ygg redeploy

# OpenRouter — free embeddings, no GPU needed
ygg config set embed_backend openai
ygg config set embed_url https://openrouter.ai/api/v1
ygg config set embed_model nvidia/llama-nemotron-embed-vl-1b-v2:free
ygg config set embed_api_key sk-or-...    # or export YGG_EMBED_API_KEY
ygg redeploy

The key is stored in ~/.yggdrasil/embed_api_key (0600) rather than config.json, and reaches the daemon as a file path — so it never shows up in ps, the launchd plist or the systemd unit. ygg config list masks it.

Check it took with ygg doctor — dense should name your model:

✓ dense    active (nvidia/llama-nemotron-embed-vl-1b-v2:free)

1. Use an inference key, not a provisioning key. Keys from openrouter.ai/settings/provisioning-keys can only mint other keys — embedding calls with one return a baffling 401 User not found. Create a normal key at openrouter.ai/settings/keys instead.

Note that GET /api/v1/models answers 200 OK for any key, valid or not — it ignores auth entirely, so it can't tell you whether your key works. Check GET /api/v1/key instead: it returns is_provisioning_key, and fails outright on a bad key.

2. Privacy settings silently hide most models. If a model 404s with All providers have been ignored, the model is fine — your account is filtering out every provider that serves it. Fix it at openrouter.ai/settings/privacy. That filter is also why openai/text-embedding-3-* can come back 403 on a provider's terms of service.

Browse what's actually available at openrouter.ai/models?output_modalities=embeddings (26 models at the time of writing). Useful ones:

Model Price / 1M tokens
nvidia/llama-nemotron-embed-vl-1b-v2:free $0
perplexity/pplx-embed-v1-0.6b $0.004
intfloat/multilingual-e5-large $0.01 — multilingual
google/gemini-embedding-2 $0.20

Staying local still wins on quality and privacy: on the 232-memory / 110-query corpus, local paraphrase-multilingual scores recall@1 0.964 vs 0.946 for the free hosted model — and your memories never leave the machine. Reach for a hosted backend when the box can't run Ollama, not to chase accuracy.

Bigger vectors do not buy accuracy here — on the same corpus mxbai-embed-large (1024d) scores 0.809 and nomic-embed-text (768d) 0.818, a difference their confidence intervals swallow whole. What actually moves the number is whether the model handles your languages: both are English-only and collapse to 0.40–0.45 on cross-language queries, where the multilingual default holds 0.95.

Background consolidation (small LLM):

Model Size Good for
qwen2.5:0.5b ~400 MB tiny, fast on CPU
qwen2.5:1.5b ~1 GB best CPU default
llama3.2:3b ~2 GB better quality, slower on CPU

The engine itself is swappable — any service meeting the MemoryBackend contract is a drop-in (YGG_ENGINE_URL); see docs/backend-boundary.md.

📊 The numbers

Measured by eval/ygg_eval.py — 232 memories, 110 labelled queries, ranking weights tuned on the dev split only, so holdout is the unbiased number (recall@1, with the paraphrase-multilingual model):

Search view holdout recall@1 recall@3 zero-dep lexical
Within a project (the real path, pool ~11) 0.94 1.00 0.76
Whole store (no filter, pool 232) 0.72 0.87 0.69

Within a project — the path you use — the right memory is #1 for 0.94 of queries and in the top 3 every time (recall@3 = 1.00). Searching the whole store with no filter is harder (recall@1 0.72, recall@3 0.87 across all 232). Zero-dep lexical mode already solves keyword and code-identifier queries (1.00); the local model adds meaning and cross-language (crosslingual 0.25 → 0.95). The full breakdown in BENCHMARKS.md has 95% CIs, pool sizes, and per-class scores — rerun it in a minute: python3 eval/ygg_eval.py --report.

🆚 Yggdrasil vs the rest

Everyone else either auto-captures transcripts or sells you a cloud. Yggdrasil's bet: keep the few things that matter, curated and de-duped, in plain rows you own — and share them across every tool and project.

Yggdrasil Built-in memory (Claude Code · Codex) claude-mem mem0 / OpenMemory basic-memory
Curated decisions / lessons / status (not transcripts) ⚠️ auto-notes ❌ captures everything ⚠️ ⚠️ free-form notes
One memory across tools ❌ vendor-siloed
Cross-project recall ("solved this in project B") ❌ repo-scoped ⚠️ ⚠️ ⚠️
100% local by default ⚠️ cloud sync add-on ❌ hosted-first
Zero dependencies (stdlib + SQLite) ❌ Node + Bun + worker daemon ❌ Docker + Qdrant + LLM key
Works with no LLM & no API key ❌ AI-compresses
Semantic search, fully local ✅ opt-in Ollama ❌ grep-only ⚠️ optional Chroma ⚠️ needs API key or Docker stack
Plain Markdown you own (Obsidian-ready)

Closest neighbor — claude-mem: capture-everything memory that records and AI-compresses every session (Node 20+ and Bun, a persistent worker daemon; Chroma optional). Yggdrasil is the opposite bet: a small, high-signal store instead of a growing firehose. mem0 is an SDK plus a hosted platform for building apps that remember their users — even self-hosted it needs an LLM API key. Built-in memories are genuinely useful — and structurally siloed: one vendor, one repo, one machine, literal grep. Yggdrasil is the layer above them (and ygg seed can bootstrap itself from those same transcripts). Different layer entirely: context-mode (live context window) and Context7 (fresh library docs) — both pair fine with Yggdrasil.

🧰 Commands

Agents see six MCP tools: ygg_health, ygg_bootstrap, ygg_search, ygg_recall, ygg_remember, ygg_materialize — auto-registered by the plugin or ygg install.

Memory ops

Command What it does
ygg recall --query "…" Cross-project search — "have I done this anywhere?"
ygg search --project P --query "…" Project-scoped search (--type, --tag, --limit, --json)
ygg remember --project P --type lesson --content "…" Save a durable memory (secret-guarded, deduped)
ygg bootstrap --project P Pull a project's memory before starting work
ygg pin --id ID · ygg unpin --id ID Pin a memory so it reliably surfaces
ygg relate --from A --rel solves --to B · ygg relations --id ID Link memories (solves/supersedes/contradicts) · see why a memory exists / what replaced it
ygg supersede --id OLD --by NEW Archive an outdated memory — --by records what replaced it
ygg materialize --id ID --project P Export one memory to an Obsidian note
ygg export-native --project P Write a curated digest into AGENTS.md/MEMORY.md — feed Claude Code & Codex's native memory
ygg import --from TOOL --path P Migrate another memory tool's store into Yggdrasil (mcp-memory, basic-memory; --dry-run first)
ygg review [--apply] Work the governance queue — consolidate duplicates, flag stale/conflicting memories (archive-only, reversible)
ygg delete --id ID · ygg reset … Hard-delete one memory · bulk-undo a bad seed (confirms first)

Cold start

Command What it does
ygg seed Distill Claude Code + Codex transcripts, Obsidian vaults, CLAUDE.md repos — incremental, deduped, fully local
ygg seed --dry-run · --force Discover + estimate only · re-distill everything
ygg seed --schedule 03:30 Nightly auto-distill (launchd) — memory keeps itself fresh; off / status
ygg sync --repo <your-git-repo> Sync memory across machines through your own git repo — plain JSON files, no cloud in the loop
ygg distill --source PATH Distill one dir/file into lessons
ygg reindex Backfill missing embeddings (restores dense recall)

Service & setup

Command What it does
ygg install · ygg doctor · ygg update Guided setup · diagnose with actionable fixes · upgrade
ygg config Show/set persistent settings (list · get · set · unset)
ygg status · start · stop · restart · logs Manage the always-on daemon
ygg hooks · unhooks · register SessionStart hook on/off · (re)register MCP
ygg recommend · token · uninstall Model catalog · print auth token · remove everything

Give it a personality — edit ~/.yggdrasil/identity.json:

{ "name": "Jarvis", "persona": "concise, proactive, dry wit", "user_facts": ["prefers TypeScript", "ships small PRs"] }

Heavy seeding, weak laptop? Point distillation at any box on your LAN — a desktop with Ollama, LM Studio, llama.cpp, even an iPhone running a local-LLM server app: ygg config set distill_url http://<box>:11434. Yggdrasil auto-detects the API dialect (Ollama or OpenAI-compatible); your data still never leaves your network — details in docs/ygg-cli.md.

❓ FAQ

Built-in memories are per-vendor, per-repo, per-machine, and retrieved by literal text match. Yggdrasil is the layer above: the same memory in Claude Code, Codex, and any MCP host, recall across projects, optional semantic search — still 100% local. It bridges them both ways: ygg seed distills your existing native memory + transcripts into the shared brain, and ygg export-native writes a curated digest back into AGENTS.md/MEMORY.md — so even a fresh clone or a tool without Yggdrasil still gets your curated memory.

No. The engine, the database, and the optional models all run locally. No account, no telemetry. The only outbound call is a version check against PyPI.

No — by design. Retrieval is automatic; writing is deliberate (the agent calls ygg_remember for durable lessons). Capture-everything pollutes memory and burns tokens, so we don't. The optional background model consolidates what's already saved (propose-only).

No. The default is pure lexical search — zero dependencies, instant. Semantic search is opt-in and uses a local model via Ollama. The installer recommends one that fits your hardware.

The engine idles at ~21 MB RAM (lexical default) with ~0% CPU; disk is tens of KB per memory. Session start injects ~300 tokens; each tool call returns a small snippet. All heavy work (indexing, embeddings, consolidation) runs off-LLM on your machine.

Yes. Memories materialize to Markdown notes in an Obsidian vault — read, edit, or remove them like any file. The engine never hard-deletes; it archives (reversible).

🚦 Status & roadmap

Alpha. The happy path and the governance loop are gate-tested (scripts/run_gates.sh); not yet hardened for multi-user or production use. macOS today; Linux/Windows service installers are built and in final on-device testing.

Next: 🛰️ cross-surface sync (one memory across CLI, web, and phone) · 🔗 relation graph (SOLVES / SUPERSEDES / CONTRADICTS) · 🐧 Linux/Windows GA.

🤝 Contributing

Issues and PRs welcome. Run scripts/run_gates.sh and python3 -m unittest discover -s tests before submitting — all gates must stay green.

📜 License

GNU AGPL v3.0 — see LICENSE. Free and open source: use, modify, self-host, redistribute. If you modify it or offer it as a network service, you must release your source under the same license.