
Tether
io.github.sidyellur由 GitHub 账号 sidyellur 发布v0.6.1更新于 Oct 24, 2026
Local-first agent memory over SQLite: remember, recall, link, forget. Degrades, never fails.
概览
Tether 是基于 SQLite 的本地优先记忆服务器,让助手记住、检索、关联和遗忘关于你与项目的持久笔记。
- 可以做什么
- 按类型、标题、正文、标签和链接保存并更新记忆,以类型加标题去重,使事实被修订而非重复堆积。以关键词检索为主、可选语义检索,按类型、标签或 id 过滤,并返回每条命中出现原因的凭据。双向关联两条记忆,并用遗忘操作软删除,保留可恢复性。读取自动加载的记忆索引和状态资源,可选提供结晶候选簇供反思。通过独立的命令行工具导出、导入、恢复和永久清除记忆,不暴露给助手。
- 适用场景
- 适合希望助手跨会话、跨设备保留上下文的场景:关于你的事实、项目决策、偏好和注意事项。也适合同时使用多个智能体或多台机器、想要一个共享记忆库而非各工具各自为政的人。
- 运行要求
- 需要 Linux、macOS 或 Windows 上的 Python 3.10 或更高版本;以 tether-memory 包从 PyPI 安装,作为本地 stdio 进程运行。默认无需账号和网络,记忆存放在本地 SQLite 文件,可用 TETHER_DB 覆盖。可选扩展提供语义检索和跨设备同步;同步需要 TETHER_SYNC_URL 和密钥 TETHER_SYNC_TOKEN。
安装
在 SourceWeft 中
- 打开 控制台中的 Tether,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
tether
A shared memory layer for personal agents, across devices. tether is an
MCP server backed by a local SQLite file. Any
MCP-compatible agent can remember, recall, link, and forget durable notes
— facts about you, your projects, your preferences — so context follows you
instead of dying with each session.
It runs local-only with zero configuration. Point it at a hosted libSQL/Turso primary and the same file becomes an embedded replica that syncs your memory across every device in near-real-time.
Why
The near future is personal agents living across many devices — laptop, desktop, phone. For that to feel like one assistant rather than several amnesiac ones, memory has to be a substrate that follows you: readable and writable from every device and from any agent, not siloed inside a single tool.
tether is that substrate. It is deliberately a convenience layer — it makes an
agent more useful when present, and never breaks the agent's work when degraded.
Status
v0.6.1. The core (four memory verbs + boot index + FTS5) shipped in v0.1; since then recall has grown a semantic arm, consolidation, an associative usage graph, a self-organizing store, and opt-in crystallization — each additive and each degrading cleanly to plain keyword recall. Every feature below is implemented.
Design and rationale start at
docs/superpowers/specs/2026-07-03-tether-design.md;
the associative core, seed-dominant recall, self-organizing store (Tier B1), and
crystallization (Tier B2) each have their own design doc under
docs/superpowers/specs/, with matching plans in
docs/superpowers/plans/.
Design at a glance
- Four memory verbs:
remember·recall·link·forget. (Enabling crystallization adds one reflection-control tool,dismiss_cluster— not a memory operation.) - Upsert on write so the store doesn't rot into near-duplicates.
- Rich recall (id, type, title, body, tags,
updated_at, plus aviareceipt saying why each hit surfaced) so an agent can judge staleness and cite what it updates.bodyis a query-centered excerpt, not the whole memory — see Excerpts. - An auto-loaded boot index — a compact one-line-per-memory list surfaced to the agent each session, so memory helps even when the agent doesn't think to search.
- Local-first, sync optional — the local path is untouched when no backend is configured; degradation never throws.
- Hybrid search, associative on top — FTS5 keyword hits and local static embeddings are fused, then a usage graph (explicit links, learned co-recall, semantic neighbours) pulls in connected memories. Every layer is additive and degrades to plain keyword recall.
- Safe under parallel tool calls — an agent that fires several
remember/recallcalls at once gets atomic, correctly-reported results; see Performance and durability.
Install
Requires Python ≥3.10 on Linux, macOS, or Windows.
Register it with Claude Code — with uv:
…or install it first:
(The package is named tether-memory on PyPI — tether was already reserved
as a common brand name. tether in claude mcp add tether -- ... is just the
label Claude Code uses to refer to this server; it doesn't need to match the
installed command.)
Or add it as a Claude Code plugin (this repo doubles as a one-plugin
marketplace; the plugin runs uvx --from 'tether-memory[semantic]', so
semantic recall comes along):
It is also listed in the MCP Registry
as mcp-name: io.github.sidyellur/tether.
By default memory lives in a local SQLite file at
~/.local/share/tether/memory.db — on Windows,
%LOCALAPPDATA%\tether\memory.db (override either with TETHER_DB, or set
XDG_DATA_HOME, which is honored on every platform). No accounts, no
network — this is the whole tool for a single machine.
Project awareness
tether knows which project it is serving: Claude Code sets
CLAUDE_PROJECT_DIR in every MCP server's environment, and tether takes the
directory's name as the project (override or disable with TETHER_PROJECT).
That drives three things, none of which need configuration:
- The boot index leads with this project. The auto-loaded index opens
with a
# This project (<name>)section, then# Everything else, so the agent starts a session already looking at the decisions and gotchas for the repo it is in rather than whatever you touched last, anywhere. - Work memories are tagged automatically.
project,feedbackandreferencememories get aproj:<name>tag unless the agent passes aproj:tag itself.usermemories are about you, not the work, and stay global. The tag is an ordinary tag:recall(tags="proj:<name>")lists a project's memories deterministically. - Recall prefers this project on a near-tie. A hit tagged with the current project ranks a few places ahead of an equally-good hit from another project; it never outranks a clearly better match, and untagged memories are neither boosted nor penalized.
Nothing falls back to the working directory: outside Claude Code (or with
TETHER_PROJECT=off) tether behaves exactly as before.
Sync across devices (optional)
Point tether at a Turso / libSQL database and the local file becomes an embedded replica — local-speed reads, writes that propagate to your other devices. Install the extra and set two env vars:
If the backend is unreachable, tether logs sync offline and keeps working
against the local file. Sync is a single-primary design: every write goes to
the hosted primary and replicas only pull, so a device that was merely
offline catches up (including tombstones from forget) on its next pull and
can never resurrect a memory another device forgot. The one gap: writes made
while degraded land only in that device's local file and are not merged
back later (#99 tracks reconciliation).
Writes push immediately. Reads also pull, debounced to at most once every
TETHER_SYNC_READ_INTERVAL seconds (default 30) — so a device that only
asks things still sees what your other devices wrote, instead of staying
frozen at its own startup state until it happens to write something. The
read-path pull is bounded much more tightly than the write-path one: if the
backend is slow, the recall returns local data and the pull lands for the
next read rather than making you wait.
One thing to know about replicas: libSQL forwards every write to the
hosted primary, and with the associative graph on (the default) recall
writes too — it records what was recalled together so memories can wire up
over time. On a replica that makes each recall a few network round-trips on
top of the local search. If that matters more to you than learned
associations, TETHER_ASSOC=0 makes recall read-only again.
Keyword search
The keyword arm is SQLite FTS5 over title, body and tags, ranked by bm25. Ask
in plain language: a memory that contains some of the query's words is a
hit, and one that contains more of them ranks higher, so "how do we run the
integration tests?" finds the note that says "pytest runs the tests" (common
function words are ignored). The index stems English words, so tests
matches test and deciding matches decided. Stemming is English-only;
turn it off for a store in another language and tether rebuilds the index
on the next start.
Measured on the LoCoMo long-conversation benchmark (one memory per dialogue turn, 1,536 questions, "did recall return the turns that answer it" in the top 10), the keyword arm alone finds the evidence for 61% of questions, against 54% for a textbook BM25 over the same text. That harness ships in the repo — see Benchmarks.
Semantic search (optional)
By default recall is hybrid: keyword (FTS5) results are fused with
semantic (vector) results, so a query finds relevant memories even when the
exact words differ ("automobile" recalls a note about your "car"). Semantic
recall runs a small static embedding model locally — no network, no API
key, nothing to hang on. Install the extra:
Without the extra (or with TETHER_SEMANTIC=0), tether runs keyword-only
FTS5 — semantic is a pure add-on and never a requirement. The first run embeds
existing memories once (a one-time backfill); after that it is incremental.
Environment:
Consolidation (optional)
tether keeps a superseded fact rather than overwriting it: when a memory is
replaced, the old one is marked no longer current (retained for history) and
excluded from recall and the boot index. Recall also gently favors more
recent facts. Two opt-in behaviors go further:
Consolidation never deletes — forget soft-deletes (see Tools), and
only the admin CLI's purge is permanent. All of this degrades to plain
keyword recall when the semantic extra is absent.
Associative recall (optional)
recall doesn't just return keyword/semantic matches — it follows a usage
graph to related memories, so asking about one thing surfaces its connected
context. The graph's edges come from three local, deterministic sources — no
LLM, no network:
- semantic — nearest neighbours by embedding (needs the
[semantic]extra), - explicit — the
link()verb, - hebbian — memories you recall together get wired together over time.
Every hit carries a via receipt saying why it surfaced (a direct match, or the
edge it came through), and two optional recall args tune it:
Associative recall is seed-dominant: the top direct matches are locked in place, and associations only fill the slots below them — so turning association on never demotes a hit that keyword/semantic search already ranked highly.
With TETHER_ASSOC=0 (or budget=0, or an empty graph), recall behaves exactly
as before — associative recall is purely additive and never breaks a lookup.
Self-organizing store (optional)
As a store grows, tether keeps it legible using the same usage graph:
- Hub-curated boot-index. The auto-loaded memory index is capped once it
passes
TETHER_BOOT_INDEX_CAP(default 50) — the cap always applies, so a large store never gets an unbounded index. With a graph, above the cap it shows two labeled slices — load-bearing memories (highest behavioral degree:explicitlinks + learned co-recall, never mere similarity) and the most recent ones — so the index stays small and shows what actually matters. Without a graph (TETHER_ASSOC=0), it falls back to a plain most-recent-N list instead of the hub/recency split. Below the cap it's the full newest-first list either way. - Forgetting-by-disconnection (opt-in,
TETHER_FORGET). A bounded sweep runs everyTETHER_FORGET_INTERVALwrites and soft-archives memories that are both old (TETHER_FORGET_AGE_DAYS, default 90) and behaviorally isolated (noexplicit/hebbianedge — semantic similarity doesn't count). Archived memories drop out of recall and the boot-index but are retained and reversible (it reuses the same mark-invalid machinery as consolidation; nothing is deleted). Safety rails: never runs without a live behavioral graph, below2 × CAPmemories, or more thanTETHER_FORGET_MAX_PER_SWEEP(default 10) per sweep.
With TETHER_FORGET off (default) and a normal store size, recall and the
boot-index behave exactly as before.
Crystallization (optional, off by default)
With TETHER_CRYSTALLIZE=1, tether reflects: it detects dense clusters of
related memories and offers them for naming. Read tether://crystallization
during a reflection pass (it is pull-only, never auto-loaded) to get candidate
clusters; name a real principle with remember(..., crystallizes=[source_ids])
— which writes the principle and links it over its sources — or drop a candidate
with dismiss_cluster(id_a, id_b). Clusters are seeded by explicit links +
usage (semantic similarity fills out membership), so this finds "these belong
together" structure, not mere topical similarity. tether finds the structure;
your agent supplies the words.
A crystallized principle becomes a boot-index hub and is reachable from its sources in recall. Note: this makes "named" a third importance signal alongside "used" and "linked" — deliberate, since an agent judging something principle-worthy is a strong signal.
Tools
Plus three resources: the auto-loaded tether://memory-index (a compact
one-line-per-memory index surfaced each session), the pull-only
tether://status (runtime config: semantic/sync state, memory and edge
counts, DB path — for debugging what's actually active), and, with
TETHER_CRYSTALLIZE, the pull-only tether://crystallization (candidate
clusters for a reflection pass).
Excerpts
recall returns a relevance-centered excerpt of each memory's body, not
the whole thing — the window is centered on the first query term that appears,
so you see why the memory matched rather than just its opening lines. A hit
that was cut also carries truncated: true and body_chars (the real length),
and you fetch the one memory you actually want in full with recall(id=N).
This is the search-engine shape: the result list is an index of pointers with enough text to judge relevance, not a payload of documents. It matters because memories can be large — a single 44KB journal memory made unrelated queries cost ~57–67KB per call while the retrieval itself took under a millisecond. The response was fat, not the engine:
A memory shorter than the excerpt width is returned whole and unmarked, exactly as before.
Performance and durability
tether is meant to be invisible in an agent's loop, so the hot paths are measured and kept flat as the store grows. Numbers below are from a local SQLite store with the semantic and associative layers on, single process:
A few things that make this hold:
- Writes don't scale with the store. The embedding matrix used for
semantic search and neighbour wiring is kept in memory and patched row by
row on every write, rather than re-read from SQLite. It is rebuilt only
when vectors change wholesale (a model change, a backfill) or when another
process has written to the file (a CLI
purge, a second server, a sync pull) — SQLite'sdata_versioncounter catches that. - Parallel tool calls are serialized. MCP runs each tool call on its own
thread, and agents issue calls in parallel. All Store operations take one
lock, so a
recalland arememberarriving together are each atomic: no interleaved transactions, no half-committed writes, andactionis always right. - Commits don't fsync. Local connections run WAL with
synchronous=NORMAL: still safe against corruption, but the last few transactions can be lost if the machine loses power before a checkpoint (an application crash loses nothing). Everyrememberand, with the graph on, everyrecallcommits, so this is one disk sync saved per call. - Search stays cheap. Vector search is a single numpy matmul over the in-memory matrix — well under a millisecond at thousands of memories, which is why there is no vector-index extension to install. Keyword cost is FTS5's: proportional to how many memories match the query.
Costs to expect once: the first boot after installing the [semantic] extra
(or changing the model) embeds every existing memory and wires its
neighbours, which takes a second or two per few thousand memories. The boot
index and tag-only lookups scan the store on each call; both are fast at
typical sizes (under 10 ms at 2,000 memories) and are the next things on the
list to cache.
Benchmarks
Two harnesses live in bench/, both runnable without an API key:
python -m bench.locomo— retrieval-only evaluation on LoCoMo, ten long multi-session conversations with ~1,500 questions each labelled with the dialogue turns that answer it. Every turn becomes a memory; the score is whetherrecall(question)returns those turns (recall@k, MRR), per condition: keyword only, keyword + semantic, and the full associative path, next to a textbook BM25 baseline. No LLM is involved, so the number measures the one thing a memory layer controls — did it hand the agent the right facts — and is not comparable to the LLM-judged "accuracy" figures memory vendors publish on the same dataset. The data (~1.5 MB) is downloaded on first use. Install the[semantic]extra to measure the semantic and associative conditions with the real model.python -m bench.run— tether's own associative-recall evaluation: a controlled corpus of tasks whose members are used together, measuring what the usage graph adds over keyword + semantic search after simulated use. Needs the[semantic]extra.
Export and permanent deletion
forget never deletes data — it soft-deletes, like consolidation. Two admin
operations, deliberately kept off the MCP tool surface so an agent can't
trigger them, live in a small CLI instead:
import replays records through the normal write path, so it upserts on
type+title like remember does — importing into a non-empty store merges
rather than duplicating. Ids are not preserved (an id in the file may map to a
different one here); links are remapped accordingly, and a link pointing
outside the file is dropped rather than pointed at the wrong memory. The
report tells you what happened: {"created", "updated", "skipped", "linked", "dropped_links"}.
restore clears valid_to, reversing a forget (or a consolidation, or a
forgetting sweep). It refuses if a newer memory has since claimed the same
type+title, naming the blocker rather than failing opaquely.
purge refuses to run without --yes. All commands honor the same
TETHER_DB/TETHER_SYNC_* env vars as the server.
License
MIT
来源:README.md,提交 8804d0d
工具
0版本历史
1- v0.6.1最新Sep 16, 2026
评分
已满足适用检查项的 50%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通25 分
请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。
- 已声明认证方式10 分
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分