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.1 is implemented. Design and rationale: docs/superpowers/specs/2026-07-03-tether-design.md. Implementation plan: docs/superpowers/plans/2026-07-03-tether-v0.1-implementation.md.
Design at a glance
- Four verbs, nothing more:
remember·recall·link·forget. - Upsert on write so the store doesn't rot into near-duplicates.
- Rich recall (id, type, title, body, tags,
updated_at) so an agent can judge staleness and cite what it updates. - 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.
- Keyword search now, embeddings later — the SQLite schema is built so semantic search and a full entity/edge graph slot in without migrating data.
Install
Requires Python ≥3.10 on a POSIX system (Linux/macOS).
Register it with Claude Code — with uv:
claude mcp add tether -- uvx tether-memory
…or install it first:
pip install tether-memory
claude mcp add tether -- tether-memory
(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.)
By default memory lives in a local SQLite file at
~/.local/share/tether/memory.db (override with TETHER_DB). No accounts, no
network — this is the whole tool for a single machine.
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:
pip install 'tether-memory[sync]'
export TETHER_SYNC_URL='libsql://<your-db>.turso.io'
export TETHER_SYNC_TOKEN='<your-auth-token>'
If the backend is unreachable, tether logs sync offline and keeps working
against the local file; writes converge when it comes back.
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:
pip install 'tether-memory[semantic]'
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:
| Var | Default | Effect |
|---|---|---|
TETHER_SEMANTIC |
on | set 0/false/off to force keyword-only recall |
TETHER_EMBEDDING_MODEL |
minishlab/potion-base-8M |
override the local static model |
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:
| Var | Default | Effect |
|---|---|---|
TETHER_CONSOLIDATE |
off | on (1/true) merges a near-duplicate on write — supersedes the old fact instead of fragmenting the store (needs the [semantic] extra) |
TETHER_DEDUP_THRESHOLD |
0.92 |
cosine similarity required to treat two facts as duplicates |
TETHER_DECAY_HALF_LIFE_DAYS |
off | set a positive number to exponentially down-rank older facts in recall |
TETHER_AUTHOR |
device id | attribution recorded on each memory |
Consolidation never deletes — only forget does. 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:
| Arg / var | Default | Effect |
|---|---|---|
budget (per call) |
TETHER_RECALL_BUDGET |
how far to follow associations; 0 = direct matches only |
session (per call) |
time-bucketed | group related recalls so they prime each other |
TETHER_ASSOC |
on | set 0/false/off for plain keyword+semantic recall |
TETHER_RECALL_BUDGET |
8 |
default association breadth |
TETHER_PROTECT_HEAD |
8 |
how many top direct hits are locked above associations |
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) and a graph exists. 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. Below the cap, or without a graph, it's the full newest-first list as before. - 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.
| var | default | effect |
|---|---|---|
TETHER_BOOT_INDEX_CAP |
50 |
curate the boot-index above this size |
TETHER_FORGET |
off | enable the forgetting sweep |
TETHER_FORGET_AGE_DAYS |
90 |
minimum age to be eligible to fade |
TETHER_FORGET_INTERVAL |
20 |
writes between sweeps |
TETHER_FORGET_MAX_PER_SWEEP |
10 |
max archived 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
| Tool | What it does |
|---|---|
remember(type, title, body, tags?, links?) |
Save a memory; upserts on type+title so facts refine rather than duplicate |
recall(query, type?, limit?, budget?, session?) |
Hybrid keyword + semantic search, then follows the usage graph to related memories; returns id/type/title/body/tags/updated_at + a via receipt |
link(id_a, id_b) |
Bidirectional link between two memories |
forget(id) |
Delete a memory |
Plus an auto-loaded resource tether://memory-index — a compact one-line-per-memory index surfaced each session.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tether_memory-0.5.1.tar.gz.
File metadata
- Download URL: tether_memory-0.5.1.tar.gz
- Upload date:
- Size: 189.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc32cb738658c03fcabcd182583ecd0fb96dc63bbb4f8e6f1467c5a3f3aa8030
|
|
| MD5 |
6dba9e73362c18b0f7019f8665ac7eef
|
|
| BLAKE2b-256 |
702d39d25f0b387e848c5b7b4bb1b726b6fa4b93247d89ca524108424d9a8a82
|
Provenance
The following attestation bundles were made for tether_memory-0.5.1.tar.gz:
Publisher:
release.yml on sidyellur/tether
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tether_memory-0.5.1.tar.gz -
Subject digest:
bc32cb738658c03fcabcd182583ecd0fb96dc63bbb4f8e6f1467c5a3f3aa8030 - Sigstore transparency entry: 2078228464
- Sigstore integration time:
-
Permalink:
sidyellur/tether@999f03be5389ac0824b4cbe928606e063a23d515 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/sidyellur
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@999f03be5389ac0824b4cbe928606e063a23d515 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tether_memory-0.5.1-py3-none-any.whl.
File metadata
- Download URL: tether_memory-0.5.1-py3-none-any.whl
- Upload date:
- Size: 31.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
899f8852da0e865de556bccb43b2d7b215701449ed45ab74f1c17c4b1d3dbae0
|
|
| MD5 |
7789980a0421ebb0666feca69916c12f
|
|
| BLAKE2b-256 |
d64eb4999976d057beae558b1721f219292de9cde7595deca0dc8f58bae0b022
|
Provenance
The following attestation bundles were made for tether_memory-0.5.1-py3-none-any.whl:
Publisher:
release.yml on sidyellur/tether
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tether_memory-0.5.1-py3-none-any.whl -
Subject digest:
899f8852da0e865de556bccb43b2d7b215701449ed45ab74f1c17c4b1d3dbae0 - Sigstore transparency entry: 2078228911
- Sigstore integration time:
-
Permalink:
sidyellur/tether@999f03be5389ac0824b4cbe928606e063a23d515 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/sidyellur
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@999f03be5389ac0824b4cbe928606e063a23d515 -
Trigger Event:
push
-
Statement type: