Reported issues for tether
Pod holds 20 of 21 GitHub reports that passed its relevance review. This can include external user reports, maintainer-confirmed bugs, and concrete feature gaps. Treat them as evidence to inspect, not a count of distinct defects.
Back to tether.
Most discussed
Silent write loss: link()/remember() return success but don't persist under multi-server lock contention
Severity: HIGH — silent data loss
Writes (link(), and in principle remember()/forget()) can return a success payload while persisting nothing. No error is raised or returned — the call looks like it worked and the data simply isn't in the DB. For a memory system this is the worst class of bug: it silently corrupts the graph and there's no signal.
Reproduced live today; this is also the root cause of a separate incident where a subagent's 30 link() calls all returned `{"linked":…
Read the thread · 2026-07-06 · closed · 2 comments
Slow MCP calls: cold-start (HF hub check + uvx re-spawn) paid repeatedly, not recall compute
Symptom
MCP tool calls to tether (recall, remember, …) feel slow / laggy, with variable latency — sometimes sub-second, sometimes multi-second stalls. This is a latency / environment problem, not a recall-quality bug (see #25 for that) and not a bug in the recall path itself.
Root cause: cold-start is paid repeatedly, and cold-start does avoidable network I/O
The per-call compute is cheap. Measured on this machine (minishlab/potion-base-8M, warm HF cache):
| step |…
Read the thread · 2026-07-04 · closed · 2 comments
recall returns full bodies (55KB+ payloads); adopt snippet + fetch-on-demand like other retrieval tools
Symptom
recall returns the full body of every hit. A single large memory dominates the payload — e.g. the project "blog journal" memory (id 4) is ~55KB on its own, and recall currently returns up to 20 full memories. Observed live: unrelated queries ("seed dominance", "hebbian edges") each returned a 55–64KB payload, led by the giant journal memory.
This is the latency users actually feel — not recall compute. Measured on the real path:
| value | |
|---|---|
| Warm recall… |
Read the thread · 2026-07-04 · closed · 1 comment
v0.3: recall ranks but doesn't filter — add an optional relevance floor
Found while testing v0.2 features live. recall returns candidates ranked by relevance up to limit, but with no similarity floor — so a query returns everything (sorted), not just what's actually close.
Repro
With semantic search on, a store of 3 unrelated notes, query "automobile" (a word that appears nowhere):
- The right note (
"I drive my car to the office") is correctly the top hit. ✅ - But the two unrelated notes (a lunch note, an editor note) are **also…
Read the thread · 2026-07-04 · closed · 1 comment
Privacy-preserving opt-in telemetry for active-installation and retention metrics
Product gap
Tether is distributed as a local-first Python package and does not require an account, so there is currently no reliable way to answer basic adoption questions:
- How many installations are actively using Tether?
- How many return after one, four, or twelve weeks?
- Which released versions remain active?
- Are semantic recall and sync actually being adopted?
GitHub traffic and PyPI downloads are useful acquisition signals, but neither represents real users. GitHub…
Read the thread · 2026-09-07 · open · 0 comments
Project awareness: lead the boot index with the current project, auto-tag work memories, prefer same-project hits
Why
The moment that makes a memory layer feel indispensable is the first message of a session: the agent already knows this project's decisions, gotchas and conventions without being asked. Today the auto-loaded tether://memory-index is "newest first, hubs above the cap" across the whole store. Someone using tether from several repos gets an index dominated by whatever they touched last, and recall ranks a hit from another project on equal footing with one from the repo they're…
Read the thread · 2026-09-05 · closed · 0 comments
Keyword recall returns nothing for natural-language queries: FTS5 query ANDs every word
Problem
_fts_query() turns a query into "w1" "w2" "w3" ... — every whitespace token double-quoted and space-separated, which FTS5 reads as implicit AND. A memory only matches if it contains every word of the query. For a two-word query that's fine; for the way an agent actually asks — a question or a descriptive phrase — it almost never matches anything, and recall silently falls through to the semantic arm alone (or to nothing at all without the [semantic] extra).
##…
Read the thread · 2026-09-05 · closed · 0 comments
Concurrent tool calls share one unlocked Store/connection: transaction errors and wrong action under parallel calls
Problem
The mcp SDK dispatches each inbound request as its own task and runs sync tool functions in anyio.to_thread.run_sync (verified in the installed mcp package: jsonrpc_dispatcher._spawn → start_soon, mcpserver/resolve.py → to_thread.run_sync). Claude Code issues parallel tool calls, so two remember/recall/link calls can run simultaneously on different threads against tether's single sqlite3 connection (check_same_thread=False) and single Store, which has…
Read the thread · 2026-09-05 · closed · 0 comments
Most recent
P0: mcp 2.0.0 removed mcp.server.fastmcp — every fresh install of tether-memory fails to start
Severity: HIGH — currently shipping; a fresh install is dead on arrival
mcp 2.0.0 was released 2026-07-28. It renamed FastMCP → MCPServer and moved mcp.server.fastmcp → mcp.server.mcpserver, with no back-compat alias (from mcp.server.fastmcp import FastMCP raises ModuleNotFoundError; mcp.FastMCP does not exist).
pyproject.toml declares dependencies = ["mcp>=1.0"] — no upper bound. So as of two days ago, pip install tether-memory / uvx tether-memory…
Read the thread · 2026-07-30 · closed · 0 comments
dismiss_cluster is registered as an MCP tool even when crystallization is off
Symptom
dismiss_cluster is registered unconditionally (src/tether/server.py:143), so every agent sees five tools even though the README's contract is "four memory verbs" and dismiss_cluster is "only relevant with TETHER_CRYSTALLIZE". With crystallization off (the default), the tool is pure noise in the agent's tool list — and a hallucinated call to it still executes Graph.dismiss_peak, writing rows to crystallize_dismissed that will silently suppress candidates if crystallization…
Read the thread · 2026-07-30 · closed · 0 comments
CLI: export has no matching import, and forget's reversibility has no surface
Symptom
Two asymmetries in the admin escape hatch (src/tether/cli.py, currently exactly export + purge):
tether exportexists,tether importdoesn't. The export is documented as "a plain backup independent of the DB file" (#49), but there is no way to restore that backup short of hand-written SQL — so as a backup it can be taken but not used.forget()is documented as reversible, but nothing exposes the reversal. The docstring says "clearvalid_toto restore"…
Read the thread · 2026-07-30 · closed · 0 comments
Sync only runs after writes — a read-only device never pulls other devices' updates
Symptom
With sync configured, sync_now() is invoked only from the four write paths — _remember_impl (src/tether/store.py:484), _link_impl (:902), _forget_impl (:926), _purge_impl (:940). recall() and the boot index never sync.
So a device that only reads in a session sees the other devices' writes only as of its own startup probe (_open_replica's initial conn.sync()), and stays stale until it happens to write something or restarts. That undercuts the "syncs your…
Read the thread · 2026-07-30 · closed · 0 comments
No status resource/tool exposing whether semantic search, sync, or assoc are actually active
Product gap
Several of tether's features degrade silently by design (that's the intended "degrade-never" behavior for reads) — but there is currently no way for the calling agent or the user to check which degraded state they're actually in. If the embedder fails to load, or sync fell back to local, the only signal is a stderr log line (e.g. sync offline, sync.py ~line 99) — nothing on the MCP tool/resource surface itself.
Why it matters
This makes tether's actual runtime…
Read the thread · 2026-07-06 · closed · 0 comments
recall has no exact tag-filter parameter; tag-based retrieval is not guaranteed-complete
Product gap
recall's parameters are query, type, limit, budget, session (server.py ~line 87) — there is no way to filter by tag directly. Tags are only reachable through ranked FTS/semantic search (tags are FTS-indexed as text), which means a tag-based query is a best-effort ranked search, not a guaranteed-complete retrieval.
Why it matters
This directly affects tether's own dogfooded workflow: the blog-journal convention (see the project's own CLAUDE.md instructions) requires…
Read the thread · 2026-07-06 · closed · 0 comments
forget is a hard DELETE with no export/backup path — inconsistent with tether's non-destructive design
Product gap
Every other write path in tether is explicitly non-destructive: consolidation supersedes (never overwrites), the forgetting-by-disconnection sweep soft-archives (reversible), and the README states this as a design principle ("Consolidation never deletes — only forget does"). But forget() itself (store.py ~line 602) is a plain DELETE FROM memories WHERE id=? — permanent, no soft-delete via the existing valid_to machinery, and there is no export/dump capability anywhere in…
Read the thread · 2026-07-06 · closed · 0 comments
remember() upsert silently wipes the links JSON column if links isn't re-passed
Severity: LOW-MEDIUM — same column already flagged racy in #38
The UPDATE path of remember()'s upsert (store.py ~lines 306-310) unconditionally writes links=? using links_s = json.dumps(links or []) (~line 298) — i.e. whatever links value was passed to this specific call, defaulting to []/None if omitted.
Failure scenario
An agent re-remembers an existing memory (same type+title, e.g. to refine its body) without re-passing the links argument it originally set. The UPDATE…
Read the thread · 2026-07-06 · closed · 0 comments
A recall query matching nothing still returns primed session context, mislabeled via:{seed:true}
Severity: MEDIUM — silent context pollution, compounds #30's payload problem
When a query's FTS/semantic seeding produces zero direct hits, _seed_scores returns {}. But activated = dict(seeds) (store.py ~line 516) then still absorbs session_activation()'s previously-primed members (~line 517) — so if the session has any primed memories, activated is non-empty even though seeds was empty, and the if not activated: return [] guard (~line 518) is bypassed.
With seeds={},…
Read the thread · 2026-07-06 · closed · 0 comments
Sync degrade-to-local fallback is only evaluated once at startup, not mid-session
Severity: LOW-MEDIUM — README overstates resilience
The local/remote fallback decision is made exactly once, in open_connection (sync.py ~lines 93-100), and the resulting connection is memoized at the module level in _get_store() (server.py ~line 32). There is no reconnect/re-degrade logic afterward, and the replica's background sync (_safe_sync, sync.py ~lines 49-53) is fire-and-forget.
Failure scenario
A device connects successfully to the Turso/libSQL primary at startup (so…
Read the thread · 2026-07-06 · closed · 0 comments
No WAL/busy_timeout on local SQLite connection; recall's own writes can lock-contend
Severity: MEDIUM — surfaces as a "read" call failing with database is locked
_local() (sync.py ~line 44-46) opens a plain sqlite3.connect(...) with no PRAGMA journal_mode=WAL and no PRAGMA busy_timeout set. SQLite's default busy_timeout is 0.
recall() is not purely read-only when the associative graph is enabled (TETHER_ASSOC on): it calls resolve_session → meta_set/touch_session (writes to meta/session_members) and commits (store.py ~line 548).
Failure scenario…
Read the thread · 2026-07-06 · closed · 0 comments
recall can resurface superseded/archived memories via session priming (valid_to not filtered)
Severity: MEDIUM — violates consolidation's stated guarantee
session_activation() (graph.py ~lines 306-311) selects primed members from session_members with no valid_to filter, and _hydrate() (store.py ~lines 464-474) hydrates by id IN (...) with no valid_to IS NULL filter either.
Meanwhile, consolidation (supersession, ~store.py:324-325) and the forgetting sweep (~store.py:377-378) both set valid_to on a memory to mark it no-longer-current — but neither cleans up any…
Read the thread · 2026-07-06 · closed · 0 comments
The remaining reports are on the project's issue tracker.