
Limpet
io.github.KSym04由 GitHub 账号 KSym04 发布v0.17.0更新于 Oct 24, 2026
Persistent engineering memory for AI coding agents. Survives refactors, knows when it is stale.
概览
Limpet 是一个本地 MCP 服务器,为 AI 编程助手提供与代码锚定的持久记忆(决策、事实、坑点),当所描述的代码变动时自动标记为过期。
- 可以做什么
- 按任务召回带 token 预算的记忆包,过期与矛盾条目始终被标注
- 存储事实、决策、经历、洞见与意图,可锚定到代码,并附带证据、私有或幂等标记
- 查看文件或符号的结构大纲、其关联记忆以及符号血缘关系
- 报告未提交改动波及的符号、有风险的记忆与约束决策,并列出代码已变动的已验证事实
- 管理存储:索引、状态、遗忘、归档/恢复、受保护的导出/导入、节省账本、重新验证与合并建议
- 适用场景
- 适合助手在跨会话、上下文压缩或换机器后反复重新推导或追问代码背后原因的场景,并希望这些知识随仓库流转、在代码变动时明确失效。也适合团队通过提交的导出文件共享记忆。
- 运行要求
- 以本地进程运行:通过安装脚本、预编译发布包或 cargo install 安装单个 Rust 二进制,并注册到 Claude Code。数据按仓库存放在用户数据目录下的本地 SQLite 文件中。无需账号、API 密钥或环境变量;只有显式的更新命令会访问网络。
安装
在 SourceWeft 中
- 打开 控制台中的 Limpet,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
🐚 limpet
small shell. long memory.
[CI] [License: MIT] [Rust] [Platforms] [100% local]
Memory for AI coding agents that goes stale when your code does. Everything your agent learns about a codebase (decisions, verified facts, failed approaches, gotchas) survives every session, anchored to the actual code it describes, and flips to stale with the reason attached the moment that code moves on. One Rust binary, one SQLite file, standard MCP, zero network calls.
[animated demo: recall answers from memory, an edit flips it to stale with the reason, a revert heals it]
the anchor lifecycle, real output: recall answers instantly, an edit flips the memory stale, a revert heals it
The failure mode that kills AI coding assistants is not forgetting; it is remembering something that is no longer true. Vector stores trust similarity, time-decay tools trust the calendar, markdown notes trust forever, so an agent that still believes last month's version of a function is not unhelpful, it is confidently wrong. limpet anchors each memory to a normalized AST hash of the code it describes: rename or move the code and the memory follows, edit it and the memory goes visibly stale with a reason, revert and it heals. Noticing its own staleness is the entire premise, and no other open memory layer does it.
⏱️ 30 seconds to running
(Windows: irm https://raw.githubusercontent.com/KSym04/limpet/main/install.ps1 | iex; full install options below.)
Restart Claude Code, type /limpet in any project, and just work:
session 1: "the scanner batch size is 50 because shared hosts kill long requests" → stored as a
decision, anchored to the function that uses itsession 30, fresh context: you: why is the batch size 50? → agent: one
recall, ~350 tokens, full answer. No file spelunking, no re-teaching your own codebase.you edit that function → the memory flips to
stale: body_editedeverywhere it appears. Nothing ever pretends to be current when it is not.
🎯 Why limpet, in four checkable claims
- It knows when it is wrong. Memories anchor to AST hashes, follow renames and file moves, go stale on real edits with the reason attached, and heal on revert. → the anchor lifecycle
- It shows you what it saved. Every recall is priced against the file reads it replaced: 4.2x fewer tokens on the benchmark, and a live per-project ledger via
limpet stats. → the receipts - It anchors the whole repository. Eleven grammars for symbol-level anchoring; every other file (templates, styles, configs) anchorable at file level. → whole repo indexed
- It never lies by omission. Every response carries an honesty envelope: matched vs returned, what was dropped and why, how fresh the index is, how much is stale. The benchmark gate has killed limpet's own features when they crossed that line.
It is not a vector database, a code-search engine, or a call-graph oracle; it is the layer that remembers why, tied to the code, and tells you when the why no longer holds. → what limpet is not · design principles with the scars that earned them: PHILOSOPHY.md
🧠 Why this exists
Code indexers answer "what is where". The expensive knowledge is not in any file:
- why the batch size is 50
- which refactor was tried and rolled back, and what broke
- which method names are frozen because customers hook them
- what that weird cron job actually protects against
Agents re-derive or re-ask this every session, burning tokens, or worse, they guess. limpet gives that knowledge the same lifecycle engineers give it:
Three properties make it work, and nobody else combines them: anchored (AST body hashes, not line numbers or paths), honest (the envelope on every response; nothing truncates silently), evidenced (a fact can carry the command that proved it, and hands it back when the anchor goes stale).
👥 Who is this for
Who should skip it: throwaway scripts, repos you touch once, teams that do not use AI agents. Memory pays off only when questions repeat.
🧰 The six tools
Every response is wrapped in the honesty envelope:
A recalled item also names the significant task terms found verbatim in its
body ("matched": "sweep anchored", capped at three, in task order), so why
an item surfaced is inspectable without re-reading it. The field is absent
when no significant task term appears whole in the body: the item then
reached the pack through anchor proximity, a stopword-only hit, or a stemmed
or partial text match instead.
⚓ The anchor lifecycle
A multi-anchor memory dies only when every anchor dies. Losing one anchor while others still resolve degrades it to stale:anchor_lost so the surviving knowledge stays usable. And remember refuses an anchor it cannot resolve, loudly, at write time: no memory is ever born dead.
Twins (trait impls, overloads, accessor pairs) share one FQN, so a symbol spec takes an optional @<disamb> suffix naming the exact one, spelled as the source writes it, whitespace forgiven (f@(int a, String b) and the canonicalized stored form are one spelling): the Rust trait path (impl for an inherent impl), the Go receiver type (func for a package function), the Ruby receiver (self., <<self., # for an instance method), and the parameter list for C++, Java, C#, Python and JS/TS, generic arity and JS/TS static included. An ambiguous spec is refused with the exact typeable forms listed.
Rename-following is evidence-gated: a unique match on a trivial body (an empty function, a bare delegation stub, a near-empty file) is refused as follow evidence and surfaces as stale:low_entropy instead of silently re-pointing the anchor at a look-alike twin, and it heals the moment the original code returns.
Healing is confidence-neutral: going stale charges a confidence penalty once, and the pre-stale value is stored beside it, so when the reason evaporates (a branch switch back, a revert, a stash pop) the penalty is refunded in full. Decay is per reason, not per resolve pass, and a transient disappearance costs a memory nothing.
Staleness is also symmetric: revert the code (a rolled-back experiment, a git checkout) and the memory heals back to active on the next call, because the anchor hash matches again. No re-verification ritual for changes that un-happened. The same applies to invalidation: a branch switch, git stash, or mid-rebase state that makes files vanish briefly is not a death sentence: when the code comes back and the anchors resolve, the memory recovers. The only final state is superseded, which records a deliberate human decision rather than filesystem churn.
Contradictions are explicit links: when a new memory contradicts an old one, both stay visible with the conflict flagged until one supersedes the other. History is never silently overwritten.
🔬 Staleness models, compared
Every memory tool eventually faces the same question: is this still true? Four answers dominate the market, and none of them can see a silent code change.
The first four expire knowledge by guessing. limpet checks.
🪙 The receipts: token savings, measured
Persistent memory also happens to be dramatically cheaper than re-derivation. Where the savings come from:
- Answers travel as conclusions, not source. A memory is the 30-token distilled answer; the files it was learned from are thousands of tokens. Reading code to re-derive a known fact pays the full price every single session. Recall pays once per question.
- The worst spend is exploration that cannot succeed. Why a batch size is 50, which refactor was rolled back, which API is frozen: not in any file. Without memory the agent greps, reads, and still does not know, so the tokens bought nothing. With memory these are the cheapest questions of all.
- Responses are budget-packed and noise-cut.
recalltakes a token budget, packs best-first, drops the low-relevance tail, and reports what it omitted. You spend what you allowed, never what happened to match. - No re-teaching after context loss. Compaction,
/clear, new session: the knowledge survives outside the context window and comes back at recall prices, not re-derivation prices.
Measured with a reproducible benchmark, seeded with 12 memories over a realistic 9-file fixture service, asking 10 questions an agent typically re-answers every session:
4.2x fewer tokens (76% saved) across the benchmark. Reproduce it yourself:
And the number is not just a benchmark: limpet keeps your own receipt. Every recall is priced against its file-reading counterfactual with the same methodology. Inside a running server, admin {op:"ledger"} reports both a real session figure (tokens saved, reads avoided, recalls) and the lifetime one (the same, plus distinct queries); the limpet stats CLI and the UI header report lifetime only, because neither serves recalls and a session block there could only ever be a permanent zero. Negative savings are shown, never floored, and anchorless memories count zero baseline, so the figure is a conservative floor, not marketing.
[terminal statusline segment showing 9 active memories and 134k tokens saved]
the receipt, live in a Claude Code statusline: active memories and lifetime tokens saved, read straight from the store
Methodology, stated so the number can be checked rather than believed:
- "Without limpet" cost is the minimal file set containing the answer plus a flat 300 tokens for search round trips. Real agents read more than the minimal set, so real savings are higher.
- Tokens are estimated as ceil(bytes/4) on both sides identically.
- 8 of the 10 questions are marked "no" above: their answers exist in no file at any token price (decisions, history, tribal knowledge). File reading gets you the code but not the answer. We still charge limpet full price against the file-reading cost instead of claiming infinite savings.
- The script is a regression gate: it exits nonzero if savings drop below 4x.
- Fixture files are 58 to 179 lines. Real source files run several times larger, and the "without" side grows with file size while a recall response does not.
Freshness has a receipt too. bench/lag_bench.py builds synthetic repos of
2,000, 10,000 and 50,000 files, edits anchored symbols in batches of 1, 32
and 100, and measures how many sweep calls pass before every edit is flagged:
one call, at every size and every batch width, because files carrying
anchors reindex first inside the sweep budget. The same run prices the cost
of that sweep on a quiet repo (nothing changed): 156 ms per call at 2,000
files, 372 ms at 10,000, 1,206 ms at 50,000. That last number is why the
FS-event watcher is a designed, bench-gated backlog item rather than a
promise: staleness latency does not need it, sweep cost at 50k files does.
🗺️ Visual memory
The UI is its own command, separate from the MCP server. limpet serve (stdio) is what Claude Code launches for you automatically after limpet install; nothing listens on a port until you start limpet ui yourself. If http://127.0.0.1:9748 refuses connections, the MCP server is not broken; the UI just is not running.
[limpet visual memory graph laid out as a brain silhouette: memories colored by health in the cortex band, clamped to code symbols in the white matter]
limpet ui: the limpet store as a brain; green memories are trustworthy, amber went stale when their code changed, grey were superseded, squares are the symbols they clamp onto
Open http://127.0.0.1:9748 for a live view of the knowledge graph laid out as a brain: memories settle in the cortex band, the files and symbols they clamp onto sit in the white matter, and every anchor runs inward. Memories are sized by confidence and colored by health (green active, amber stale, red invalidated, grey superseded), verified ones carry a bright ring, and contradiction and supersession edges are drawn. In the all-projects view stores are spread over nine lobes, largest first, each labelled with its project name (past nine stores, lobes are shared), so the shape of your whole memory is one glance. The silhouette scales to what is visible and says so in the footer when a small project cannot fill it; the layout is deterministic (the same data always draws the same picture) and a settled graph does no per-frame work while you look at it. The "needs attention" filter shows exactly what went stale and why, with the re-verify command one click away. Where other tools visualize code structure, limpet visualizes what your agent knows and whether it is still true. Served by the same single binary, bound to 127.0.0.1 only.
📦 Install
One line. The installer downloads the latest release binary for your platform, verifies its sha256, installs it, and registers it with Claude Code:
macOS / Linux:
Windows (PowerShell):
Then restart Claude Code and type /limpet in any project. That is the whole setup. (LIMPET_INSTALL_DIR overrides the install location; LIMPET_VERSION pins a release tag.)
Prefer not to pipe a script into your shell? Every step below is the manual equivalent. Prebuilt binaries ship for Apple Silicon macOS, x86_64 Linux, and x86_64 Windows on the latest release; every asset has a published sha256; verify it before you trust the binary.
macOS (Apple Silicon)
macOS may quarantine a downloaded binary; if it refuses to run, clear the flag with xattr -d com.apple.quarantine /usr/local/bin/limpet.
Linux (x86_64)
Windows (x86_64, PowerShell)
With Rust (rustup.rs), the path for Intel macs, ARM Linux, and any platform without a prebuilt binary:
Restart Claude Code. Done. (limpet install --dry-run previews the exact config changes first; limpet uninstall reverses them.)
Update later with limpet update: it fetches the latest release binary for your platform, verifies it against the published sha256, and atomically replaces the running executable. limpet update --check reports whether a newer version exists without installing it. This is the only command that touches the network. Restart Claude Code afterward so the MCP server reloads onto the new binary.
🚀 Day-to-day usage
/limpet in any project indexes the code, recalls everything already known (stale items flagged), and switches the session to memory-first mode. From there the agent stores what it learns as it learns it; the 30-second story above is the whole loop.
Everyday commands:
Data lives under ~/.local/share/limpet/, one SQLite store per repository. Teammates run limpet import after pulling the JSONL (--path <repo-relative file> imports a different export).
Keep your MEMORY.md. limpet seed MEMORY.md chunks a plain notes file into individual memories: bullets and paragraphs become entries, a chunk that names a repo file is anchored to it (so it goes stale when that file changes), and everything seeds as mined (lower trust than verified facts until re-confirmed). Re-running on an unchanged file is a no-op. A reworded note is different: its old wording is already stored, so the write-path dedup refuses the new one and the report says so; re-run with --force to store the new wording too, then supersede the old from the agent side.
Statusline on any platform. limpet statusline --root <project dir> prints the shell segment (| 🐚 13 · ↑32k tokens saved, with the count hyperlinking to the project's graph when the UI is running) or nothing at all: it opens the store strictly read-only, never writes, and always exits 0, so it can sit in a prompt safely. Because the rendering lives in the binary, the same one-liner works from a bash statusline on macOS/Linux and a PowerShell or cmd statusline on Windows, with no sqlite3 CLI and no bash required:
The simplest setup is to let Claude Code call the binary directly as its whole statusline; add to ~/.claude/settings.json:
Already have a custom statusline? Append the binary's output as one segment, but do not query store.db with sqlite3 yourself. The per-repo store key scheme changes between versions (it moved from a path hash to a portable git-remote identity in v0.9.0), so a hand-rolled query silently stops matching and the segment just disappears. Only limpet statusline tracks the current scheme. Run limpet doctor to check how your statusline is wired: it reports ok when it delegates to the binary, warn when it hand-rolls a store query that will drift, and the exact line to add when nothing is wired.
Toggle it off with /limpet statusline (writes ~/.claude/.limpet-statusline-off; the command honors the flag).
Auto-recall at session start. Without any hook, memory-first behavior depends on the agent remembering to type /limpet. With a SessionStart hook, every new session in an already-indexed project opens with a one-line brief injected into context ("This project has limpet memory: 13 active memories, 2 stale…") and the agent recalls before it reads. Add to ~/.claude/settings.json:
limpet hook prints nothing when the project has no store (sessions outside indexed repos stay clean), opens the store strictly read-only, and always exits 0. The explicit PATH prefix matters: hooks run in a non-login shell that often lacks ~/.local/bin and ~/.cargo/bin, and a silently-missing binary is exactly the kind of failure this command is designed never to surface. On Windows use %USERPROFILE%\AppData\Local\Programs\limpet\limpet.exe hook.
Seeding a project
/limpet scan cold-starts a repository's memory from what already exists. light mode (default) harvests merge commits, tags, and the README; deep adds all docs, long-body commits, and the assistant's project memory directory. A preflight recall catches any candidates the store already answers, so re-runs only add gaps. The harvest runs in a subagent to keep raw git output and doc dumps out of the main context; only the curated candidate table comes back. You approve candidates in two tiers before anything is written: high-confidence items as a single block (reject-by-exception), borderlines one at a time. Each approved memory is stamped with an origin so duplicates are caught on future scans, and private-source items are stored with private: true so they are never exported.
🌳 Whole repo indexed, thin on purpose
Every file in the repository is indexed and anchorable. Files with a shipped grammar (PHP, JavaScript, TypeScript, Python, Rust, C/C++, Go, Java, Ruby, C#, Bash) get full symbol extraction: functions, classes, imports, name-based call references labeled syntactic, and inheritance edges (extends, implements, impl_trait, embeds, mixin) surfaced through map lineage. Every other file (.twig, .scss, .vue, .blade.php, .erb, .md, .yml, configs, anything) gets a file-level node with a content hash, so a memory can anchor to it and go stale:file_edited the moment it changes. On template-heavy stacks that is where the knowledge worth remembering actually lives.
Legacy encodings degrade gracefully: a grammar-matched file that is not valid UTF-8 (CP949 or UTF-16 source in an old C++ engine, say) keeps its file-level anchor instead of disappearing from the index. A grammar can only ever upgrade a file, never make it less anchorable.
What the walk skips, deliberately: everything in .gitignore, everything in an optional .limpetignore (gitignore syntax, works even outside a git repo), and a built-in set of near-universally generated or vendored trees (node_modules, vendor, target, dist, build, __pycache__, .venv/venv, Python caches, .next/.nuxt/.svelte-kit, Pods, .gradle, bower_components, coverage, .terraform), hidden junk, *.min.* assets, and files over 8MB. Source files over 512KB are indexed at file level but never parsed for symbols: the cap protects tree-sitter from generated bundles, and a giant hand-written translation unit stays anchorable instead of vanishing. Those bounds are what keep a large vendored dependency tree with no .gitignore from pegging your CPU; use .limpetignore to opt out anything else.
There is no LSP, no type inference, and no claim of a publishable call graph: the index exists to give memory anchor points, invalidation, and recall locality. Every shipped grammar has fixture coverage in the test suite; languages are added when they can be tested, not when they pad a number.
Freshness model: every tool call runs a bounded incremental sweep (changed files reparse in milliseconds via tree-sitter). Queries never block on indexing; anything still dirty is listed in the envelope.
⚙️ Per-repo config (.limpet.json)
An optional .limpet.json at the repository root tunes two things. It is a plain, size-bounded lookup table validated against the shipped grammars; a malformed file fails an explicit index loudly instead of being silently ignored.
extensionsmaps a filename suffix to one of the shipped grammars (php,js,ts,py,rust,cpp,go,java,rb,cs,bash), so template-heavy and legacy stacks get full symbol extraction on extensions the built-in table does not know. The longest matching suffix wins, so a specificblade.phpoverrides a genericphp.auto_import(defaulttrue) seeds a brand-new store from a committed.limpet/memory.jsonlon the first index, so a teammate who clones the repo gets the shared memory with no extra step. It runs once, only on a fresh store, through the same guarded path aslimpet import.
Portable identity. A repository's store is keyed by its git origin remote when it has one, falling back to the canonical path. Move a checkout or re-clone it and the memory follows; two different repositories can no longer collide onto one store. Existing stores migrate automatically on first open, and a store is never mis-claimed: an ambiguous legacy store is left in place rather than reassigned.
🔒 Security posture
- Local by default. Indexing, recall, memory, and the UI make no network calls, ever: no telemetry, no API keys, no cloud. The one exception is
limpet update, which you invoke explicitly; it fetches a checksum-verified release binary over HTTPS and sends nothing but alimpet/<version>User-Agent. The UI binds 127.0.0.1 and serves one embedded page, GET only. - Secrets never persist.
rememberscans every body and evidence output and refuses to store anything shaped like a credential (cloud access keys, provider tokens, PEM private-key blocks, JWTs), so a secret cannot reach the local store or a shared.limpet/memory.jsonl. - Private memories stay local. A memory stored with
private: trueis recalled normally within the project but is never included inexportoutput, so sensitive context cannot reach a shared.limpet/memory.jsonl. - No shell interpolation. External commands (git only) run with argument arrays; no string ever reaches a shell.
- Path validation. Every file path arriving over MCP is checked against the repository root; absolute paths and traversal are rejected at a single choke point.
- Parameterized SQL only. No query in the codebase concatenates user input.
- Malformed input survives. The JSON-RPC loop answers parse errors and handler panics with JSON-RPC errors and keeps serving.
installedits only its ownmcpServers.limpetentry and refuses to touch config it does not recognize;uninstallreverses exactly that.- Import is a guarded path. A
.limpet/memory.jsonlpulled from a teammate is untrusted input, soimportenforces the same rules asremember: secrets are rejected (they can never enter the store, even from a peer), bodies are size-capped, confidence is clamped, future-dated entries cannot poison the merge, and imported anchor hashes are re-resolved against your local code so a forged hash cannot fake freshness. Rejected lines are counted, never silently applied. - Broken setup?
limpet doctor. It checks the binary, the Claude Code registration (including a moved-binary mismatch), the skill file, the store, its version stamp, and the index, printing ok/FAIL per line. It runs automatically afterinstallandupdate.
🚫 What limpet is not
- Not a generic AI memory store, vector database, or RAG pipeline. No embeddings, no similarity guesswork: anchors are deterministic AST hashes, and staleness is a fact, not a score. Code-unaware memory stores never notice when the code moves on; noticing is limpet's entire premise.
- Not a code search engine. Your agent already has grep.
- Not a call-graph oracle. Call edges are syntactic and labeled as such.
- Not a cloud memory platform. No account, no sync, no server.
- Retrieval quality tracks what gets written: short, specific, anchored memories recall well, and the tool schemas steer agents toward exactly that.
🧭 Roadmap
See ROADMAP.md for what has shipped (portable repo identity, the statusline doctor, the structural lineage graph, grammar wave 2 with eleven languages, the 0.13 freshness pass, the 0.14 truth layer: verification as a ranking signal, contradictions surfaced and duplicates refused at write time, 0.15 freshness at scale, part 2: FQN disambiguation with @-addressable twins, the slot-first anchor ladder, matched terms on recall items, and the lag-bench watcher verdict, and 0.16 the refinement loop: admin reverify returning a re-proved memory to active, the schema v8 confidence refund behind it, read-only consolidation candidates, and a doctor advisory, on macOS/Linux, for a stale running server image, and 0.16.1 the audit follow-ups: punctuation-boundary secret detection in guaranteed linear time, a session ledger figure that is counted rather than inferred and never published by a surface that served nothing, and CI gates for clippy, dependency advisories, and the declared MSRV), what this release carries (the v1.0 stability contract's code half, enforced and documented in STABILITY.md; the visual memory page relaid as a brain; and a limpet ui fix that stops every browser request from waiting out a 5 s read timeout), and what is next (the 1.0 tag, once the contract has soaked). One rule governs all of it: a feature ships only if it feeds a receipt (the limpet stats ledger or the benchmark) or the honesty envelope.
⚖️ Reliance and license
limpet is an aid to judgment, not a substitute for it. Its freshness and confidence signals are heuristics computed from code structure, not proof: an "active" memory is limpet's best evidence that stored knowledge still holds, never a guarantee that it is correct or complete. Verify anything you would not want to be wrong about before you rely on it, especially in security-sensitive or production changes. The whole design philosophy is to surface uncertainty rather than hide it (PHILOSOPHY.md), and that only protects you if you read the flags it gives you.
The software is provided "as is", without warranty of any kind, and the authors carry no liability for its use, as set out in the license below.
Contributions are welcome and are accepted under the same MIT license as the project (inbound = outbound): by opening a pull request you agree your contribution may be distributed under these terms. See CONTRIBUTING.md.
📄 License
MIT. See LICENSE. The MIT permission notice includes an explicit disclaimer of warranty and limitation of liability; those clauses are the legally operative protection for both users and authors.
来源:README.md,提交 ff6087b
工具
0版本历史
1- v0.17.0最新Sep 16, 2026
评分
已满足适用检查项的 50%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通25 分
请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。
- 已声明认证方式10 分
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分