Ultan is a neuroscience-inspired, agentic memory system for Claude Code — a layered architecture that learns your preferences and coding conventions and recalls them across every session, project, and machine.
> /plugin marketplace add nickroci/ultan> /plugin install ultan@ultan
What's inside
Ultan is a neuroscience-inspired, agentic memory system for Claude Code — a layered architecture that learns your preferences and coding conventions and recalls them across every session, project, and machine. A pair of curator agents tends the library: the Librarian keeps your markdown knowledge graph neat — deduped, linked, and organised — while the Scholar gates what's worth keeping and keeps every entry accurate and up to date as things change. On retrieval, a stack of BM25, vector embeddings, cross-encoder reranking, and agentic search surfaces the right memory at the right moment. Plain markdown on your disk you can grep and git. No cloud.
⚠️ Beta. Under active development. Expect breaking changes and rough edges — feedback welcome.
"Show me a man who has read all of the books of one of the major branches of knowledge — say, military history — and I'll show you a man more ignorant than the merest churl. For while he has read, others have written; and the body of available knowledge has grown so much faster than his understanding of it that he is, on balance, less learned at the end of his studies than at their beginning."
— Master Ultan, Gene Wolfe
The memory features that exist today — CLAUDE.md, Cursor rules, ChatGPT memory, the various provider built-ins — are there but I always felt they did not surface the lessons that had been learned well enough. They seem to be rarely useful, bloat the context, get ignored and when they fire they often feel trivial: shallow grep against a static rules file, no judgment about what's worth remembering vs what isn't, no idea what's stale, no composition with the current turn. I was tired of teaching the same agent the same thing over and over and I did not want to have to manually curate an ever changing set of shared knowledge per project and globally.
So I wondered what could be achieved with a much more advanced system. There are obviously many alternatives but none had all the features that I wanted. Real memory is salience-gated at write time, decays without reinforcement, mutates on retrieval, resists deletion of high-importance traces, and uses different mechanisms for different latencies (ambient familiarity, deliberate recall, fast suppression). So we took the neurology seriously and built towards it — a curator pair (currently Sonnet + Opus) gating writes by surprise magnitude; three retrieval tiers each tuned to a different cognitive analog; surfacing-aware decay with optional arousal pinning; an opt-in mutation/reconsolidation pathway; archive-don't-delete so contradictions can resurrect old traces. Tokens cost something — it's deliberately token-heavy, and the curator runs on your Claude Code subscription (Max/Pro quota, not a metered API bill) — but less than the friction of repeating yourself.
Ultan watches your conversations as you work, learns your preferences and conventions, and surfaces them when they matter. It's the "remember when you told me to always use uv" that you wish Claude already did natively, except organised, deduplicated, validated, and proactively consulted before the agent interrupts you to ask something you've already answered.
It's your library. On your disk. In plain markdown. You can ls it, cat it, git it.
Ultan installs as a native Claude Code plugin — no editing settings.json, no
daemon to babysit. You just need uv on your PATH; the
plugin uses it to provision Ultan's runtime on first use.
Inside Claude Code:
/plugin marketplace add nickroci/ultan
/plugin install ultan@ultan # choose "user" scope to enable it in every project
/reload-plugins # load the hooks + MCP into the running session
⏳
/plugin installlooks frozen — it isn't. Claude Code shows no progress while it downloads the plugin, which can take a minute. Don't cancel; the "plugin changed" confirmation appears when it's done. After that, the first session provisions Ultan's retrieval stack (torch + the embedding/rerank models — a few hundred MB) in the background, and you can keep working while it finishes.
That's it. Skills and slash commands hot-load the instant you install; /reload-plugins
pulls in the hooks and the MCP server. A full Claude Code restart is not required —
your next prompt kicks off provisioning automatically (a fresh session works too; you
don't need one).
🩺 Wondering what it's doing? Ask Claude to run
ultan doctor. It reports whether the background install is still running, the daemon's state (warming / healthy / idle), priming latency, and capture freshness — at any stage, even mid-install.
The plugin provisions that retrieval stack into its private storage in the
background, triggered by whichever happens first: the plugin's MCP server starting
(right after install/reload — the spec has no install-time script, so this is the
earliest the plugin gets to run), a fresh session's SessionStart, or your next
prompt. All three funnel into one lock-guarded installer, so they never race. Until
it finishes, priming falls back to a fast lexical scan; after that the daemon
lazy-starts on demand. Models download anonymously from HuggingFace — see
First-start expectations below.
You now have:
/ultan <text> to save a memory, /ultan-advisor <question> to consult the libraryultan-search skill and the ultan_recall MCP tool~/.agent-mem/knowledge/ as plain markdown — local, no cloud, no telemetryEverything Ultan owns lives under ~/.agent-mem/ on your local disk — no cloud sync, no hosted database, no telemetry. Override with AGENT_MEM_HOME=/some/other/path if you want a different root.
| Path | What's in it |
|---|---|
~/.agent-mem/knowledge/ | Your library — plain markdown. This is the data; everything else in this table is derived or transient. ls, cat, git init it. |
~/.agent-mem/events.jsonl | Append-only stream of hook events. Hooks write, the daemon tails. Truncates on rotation. |
~/.agent-mem/daemon.log | Rotated daemon log (~5 MiB cap). |
~/.agent-mem/.bm25.idx, .embeddings.idx | Search indexes over the library. Rebuilt automatically when the library changes. |
~/.agent-mem/sweep-state.json | Last-decay-sweep timestamp (24h cooldown). |
~/.agent-mem/pending-nudges.md | Scholar writes nudges here; the hook reads and clears them on the next turn. |
~/.agent-mem/cost.json | Running tally of LLM spend across Librarian / Scholar / Advisor calls. |
~/.agent-mem/runs/ | Per-call audit log (cost, duration, decisions) + full LLM transcripts (7-day TTL). |
See Storage on disk below for the full layout including the folders inside knowledge/.
The Tier-1 retrieval pipeline uses two open HuggingFace models, downloaded
into your local cache on first daemon start (~/.cache/huggingface/,
re-used by every subsequent run):
| Model | Role | Size on disk |
|---|---|---|
nomic-ai/nomic-embed-text-v1.5 | sentence-transformer embedder (asymmetric query/document, Matryoshka 768→128) | ~270 MB |
cross-encoder/ms-marco-MiniLM-L-12-v2 | reranker that scores (query, body) pairs after RRF fusion | ~130 MB |
Both are downloaded anonymously — no HuggingFace account or token is
needed. If you have outbound network restrictions, pre-cache them on a
machine with internet access and copy ~/.cache/huggingface/hub/
across. nomic-embed-v1.5 requires trust_remote_code=True because its
RoPE attention module ships as custom code in the model repo; the daemon
sets this only for the nomic model name, not as a global default.
Boot time on a warm M-series Mac (cold models, warm HF cache):
Total: ~25 s from uv run agent-mem-daemon -v to "priming RPC ready".
The startup runs a single end-to-end priming search before opening the
socket — by the time the hook can connect, every MPS kernel in the
retrieval path is JIT-compiled, so the first real request is hot.
Steady-state resident footprint: ~2.5 GB physical memory on Apple Silicon (unified memory, includes Metal device-side allocations); peak of ~3.4 GB during boot warmup. On a discrete-GPU box the split between RSS and GPU memory looks different; on CPU-only hardware the daemon still works but per-request rerank latency drifts up.
When the daemon is running, the UserPromptSubmit hook makes a Unix-socket
call into it (hard cap 2 s; warm steady-state ~300-500 ms) to get a
priming snippet keyed on your current prompt (not the last batch's
curation). When the daemon is down, the hook falls back to a tiny
in-process lexical scan so you still see relevant entries.
To save a memory explicitly: /ultan never deploy to prod without my explicit OK.
To ask before asking the user: /ultan-advisor should I use respx or hand-roll an httpx mock?.
Ultan is modelled — deliberately, at the level of the architecture, not as decoration — on how mammalian brains decide what to remember and how to surface it again later. Three ideas drive the whole system:
FAQ
ultan is a Claude Code plugin with 2 hand-picked skills for development work, indexed on Flowy. Install it with the command on its page. It includes epiphany, ultan-search. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.
Is this plugin yours?
Claim it with GitHubSubmit a pluginPromote it