aether-cache
Aether Cache
Shared memory for AI coding tools that stays true to your code.
Claude Code, Cursor, and other AI tools each forget your project between sessions, and when they do remember things, nothing checks whether those memories are still correct. Aether Cache gives every tool one shared, local memory, and keeps it honest:
Memories go stale when the code changes. Each memory is linked to the files it's about, with a snapshot of their git content hash. When
docker-compose.ymlchanges, the memory about it is retired automatically instead of being fed to your AI forever.Contradictions get replaced, not hoarded. "We migrated to MySQL" retires "we use Postgres" instead of sitting next to it and confusing the model. Near-identical repeats are merged.
Every memory can explain itself. Which tool and session saved it, which file versions it was based on, and every change since: created, seen again, went stale, confirmed, replaced.
It works with the files you already use. Current memories sync into a managed section of
AGENTS.mdandCLAUDE.md, so they're committed, reviewed in PRs, and visible to teammates who don't run Aether Cache.
It runs entirely on your machine, on top of a database engine I wrote from scratch.
Not a Redis clone
Aether Cache started as a from-scratch rebuild of Redis in Java, then kept going. Every layer here is written from scratch, and each one does something the layer beneath it can't:
Layer | What it adds |
MCP server | Claude Code, Cursor, and any MCP tool share one memory |
Git awareness | Memories retire themselves when the code they describe changes |
Memory layer | Status, provenance, full history, duplicate merging, and conflict review, inside the engine |
HNSW vector index | Semantic search: 99.4% recall at 0.84 ms on 100K vectors, 22× faster than exact search |
Redis-compatible engine | RESP protocol, 50+ commands, replication, transactions, and crash-safe persistence (0 acknowledged writes lost across 1,039,890 writes and 8 |
The bottom layer alone is a working Redis: redis-cli, redis-benchmark, and redis-py talk to it unchanged. The
memory layer, git awareness, and MCP server are things Redis doesn't do at all.
Claude Code ─┐ ┌─────────────────────────────────────────────┐
Cursor ──────┼─ MCP ─► aether_cache ─┼─ RESP ─► Java engine (Main.java) │
other tools ─┘ (Python: embeddings,│ memories, HNSW vector index, │
git hashing, sync) │ history, append-only file on disk │
└─────────────────────────────────────────────┘Related MCP server: total-agent-memory
What it looks like
A memory linked to a file, the file changes, and the next lookup catches it:
> remember("The database runs Postgres via docker compose", files=["docker-compose.yml"])
Saved as m1.
... someone edits docker-compose.yml ...
> recall("database setup")
These memories were just retired because files they depend on changed. If one is still true,
call confirm_memory; if it's wrong, save the corrected fact with remember:
[m1] "The database runs Postgres via docker compose" is now marked outdated: docker-compose.yml changed.
> confirm_memory("m1") # still true, so re-link it to the new version of the file
Confirmed m1; it's active again and linked to the current file versions.
> explain_memory("m1")
m1: "The database runs Postgres via docker compose"
status: active, project: demo
saved by: claude-code (session eea54b5c)
linked files: docker-compose.yml@02ea346...
history:
19:16 created: by claude-code (session eea54b5c), linked to docker-compose.yml@87da641...
19:16 active -> stale: docker-compose.yml changed since this was saved
19:16 re-linked: to docker-compose.yml@02ea346... (confirmed still true)
19:16 stale -> active: confirmed still trueA newer fact replacing an older one:
> remember("The API timeout was raised to 60 seconds")
Saved as m3.
Related existing memories:
[m1] (similarity 0.58) The API timeout is 30 seconds
If m3 makes any of these no longer true, call replace_memory(old_id, "m3"). Otherwise do nothing.
> replace_memory("m1", "m3")
m1 retired; m3 replaces it.Quick start
You need uv (curl -LsSf https://astral.sh/uv/install.sh | sh), which also takes
care of Python for you. Then:
uv tool install aether-cache
aether-cache setupsetup starts the engine in the background, downloads the embedding model once (about 90 MB), and connects every
supported AI tool it finds on your machine: Claude Code, Cursor, and Codex. That's it. Open your AI tool in any git
repo and try:
Remember that our tests need DOCKER_HOST set. It depends on docker-compose.yml.
Then start a new session and ask how to run the tests.
The engine runs on Java 11+. If you have it, it's used. If you don't, setup downloads a private copy (about
50 MB) into ~/.aether-cache/java, checks it against its published checksum, and uses it only for Aether Cache;
nothing is installed system-wide, and aether-cache uninstall removes it.
Already keep notes for your AI in CLAUDE.md, AGENTS.md, or Cursor rules? Run aether-cache import in the repo
(or use Import notes in the dashboard) to turn them into memories, linked to the files they mention.
Memories are kept per project (the git repo's folder name), so projects never mix. Everything stays on your machine:
the engine only accepts connections from it, and your memories live in ~/.aether-cache.
Command | What it does |
| One-time setup: engine, model, and AI tools |
| Adds the repo you're in (or a path) so the dashboard can check its files; the dashboard can also find and add repos for you |
| Opens the dashboard: see what your AI remembers, and confirm or retire memories whose files changed |
| Turns the notes in |
| Saves memories, with their linked files and history, to a JSON file ( |
| Is it running, and what's in this project's memory |
| Checks everything and tells you how to fix what's wrong |
| Look at memories from the terminal |
| Writes current memories into |
| Connects one tool ( |
| Runs or stops the engine (it also starts on its own when a tool needs it) |
| Disconnects your AI tools, stops the engine, and asks before deleting memories |
If aether-cache isn't found after installing, run uv tool update-shell and open a new terminal. Port 7380 taken?
Set AETHER_PORT and run setup again. To uninstall: aether-cache uninstall, then uv tool uninstall aether-cache.
Tools the AI can call
Tool | What it does |
| Saves a durable fact, optionally linked to files. Merges near-identical repeats and shows related memories so the AI can decide whether the new fact replaces one. |
| Searches the project's memories by meaning. Checks linked files first, so it never returns a memory whose code changed underneath it. |
| Where a memory came from and its full history. |
| Retires an older memory that a newer one makes untrue. |
| Retires a memory that turned out to be wrong. |
| Brings back a memory that's still true after its files changed, re-linked to the current versions. |
| Runs the file check on demand. |
| Reviews pairs of very similar memories that may contradict each other. |
| Writes current memories into |
Design decisions, and what the measurements said
Similarity can't detect contradictions, so the AI judges
The first version flagged a new memory as a possible contradiction when it was similar to an existing one. Calibrating
with real all-MiniLM-L6-v2 embeddings showed that can't work:
Pair | Kind | Similarity |
"we use Postgres" / "the database is Postgres" | duplicate | 0.73 |
"tests need DOCKER_HOST set" / "you have to set DOCKER_HOST before running tests" | duplicate | 0.92 |
"we use Postgres" / "we migrated to MySQL" | contradiction | 0.30 |
"API timeout is 30 seconds" / "API timeout is 60 seconds" | contradiction | 0.94 |
"tests need DOCKER_HOST set" / "integration tests need a running Redis" | related, both true | 0.34 |
Duplicates scored 0.73 to 0.92 and contradictions 0.30 to 0.94, so no threshold separates them. A threshold low enough to merge real duplicates would silently merge "timeout is 30s" with "timeout is 60s", the worst bug this product could have. So the engine only auto-merges near-identical text (similarity ≥ 0.97). For everything else, embeddings find the related memories and the AI, which already understands the sentences, decides whether one replaces another.
Staleness compares file contents, not commits
Each linked file is stored as path@<git blob hash>, computed with git hash-object. Comparing contents rather than
commit ids catches uncommitted edits, ignores commits that didn't touch the file, and needs no git hooks: recall
re-hashes the handful of linked files before every search.
Memories never mutate silently
Every change is a logged event with a timestamp and a reason. The engine logs each memory with the id and timestamp it assigned, so replaying the log after a restart rebuilds the exact same memories and histories, down to the millisecond, instead of minting new ones.
The engine underneath
A Redis-compatible server written from scratch in Java. It speaks RESP, so redis-cli, redis-benchmark, and
redis-py work with it unchanged. The core was built by completing every stage and extension of the
CodeCrafters "Build Your Own Redis" challenge; the vector index,
persistence hardening, and the memory layer are my own additions.
Throughput vs. official Redis
redis-benchmark -t set,get, 100K requests, 50 parallel clients, 3-byte values, same machine:
Aether Cache | Redis 8.10.2 | Aether Cache as % of Redis | |
SET | 205.8K ops/sec | 233.6K ops/sec | 88% |
GET | 225.7K ops/sec | 242.7K ops/sec | 93% |
p50 latency | 0.119 ms | 0.111 ms | |
p99 latency (SET / GET) | 0.375 / 0.231 ms | 0.359 / 0.159 ms | |
max latency (SET) | 19.7 ms | 1.3 ms | JVM GC / JIT pauses |
Typical latency is on par with Redis. The gap is in worst-case latency, from JVM garbage collection and JIT warm-up, which Redis (written in C) doesn't have.
HNSW vector search vs. exact search
100,000 AG News headlines embedded with all-MiniLM-L6-v2 (384 dimensions, cosine similarity), 500 held-out queries,
k = 10. Recall is measured against exact brute-force search on the same data.
Mode | Recall@10 | p50 latency | p99 latency |
Exact (brute force) | 1.000 | 18.58 ms | 20.52 ms |
HNSW, ef=10 | 0.905 | 0.49 ms | 1.38 ms |
HNSW, ef=20 | 0.951 | 0.52 ms | 0.73 ms |
HNSW, ef=50 | 0.985 | 0.66 ms | 0.89 ms |
HNSW, ef=100 | 0.994 | 0.84 ms | 1.41 ms |
HNSW, ef=200 | 0.998 | 1.24 ms | 2.19 ms |
At ef=100, HNSW returns 99.4% of the true top-10 about 22× faster than exact search. Building the index for 100K
vectors took 136 seconds (M=16, M0=32, efConstruction=200). Latencies are measured from the Python client, so they
include network and client overhead. Full setup and raw numbers: results.json.
How it works: each vector links to its nearest neighbors in a layered graph. A search starts on a sparse top layer
of long-range links, hops greedily toward the query, drops down a layer at a time, and finishes with a best-first search
on the bottom layer that keeps the ef best candidates. Neighbor selection uses the diversity heuristic from the HNSW
paper (prefer neighbors in different directions over near-duplicates), which is what keeps recall high.
Filtered search: HNSW doesn't know about memory status, so searches over-fetch candidates, drop the ones that don't match (stale or superseded), widen the search if too few survive, and fall back to an exact scan if needed. In a test with 2,000 memories where only 2% were active, filtered searches returned exactly the correct top 10 on 30/30 queries. The catch: at that selectivity, the exact fallback runs most of the time, which is the known weak spot of post-filtering on graph indexes.
Crash safety
Every write goes to an append-only file (AOF) that is replayed on startup.
kill -9 crash test (crash_test.py): a client writes as fast as it can, the server is killed at a random moment,
restarted, and every write the server acknowledged is checked.
fsync policy | Crashes | Acknowledged writes | Lost |
| 5 | 757,295 | 0 |
| 3 | 282,595 | 0 |
Recovery took 0.6 seconds per restart, replaying up to ~185K writes.
Torn writes: a machine crash can cut the last write off halfway. The first version refused to start on such a file; now replay stops at the last complete command, trims the partial one, and starts normally with all earlier data intact.
Cost of each fsync policy (SET throughput, 50 clients):
Policy | Throughput | p50 latency |
| 42.9K/sec | 1.159 ms |
| 113.9K/sec | 0.103 ms |
| 118.8K/sec | 0.103 ms |
everysec is 2.7× faster than always and within 4% of no, which is why it's the sensible default. On macOS, fsync
doesn't force the SSD's own cache to flush, so always looks cheaper here than it would on a Linux server.
Full command list
Memory:
MEM.ADD,MEM.GET,MEM.SEARCH(withSTATUSfilter),MEM.STATUS,MEM.WHY,MEM.LISTMEM.FILES,MEM.SEEN,MEM.CONFLICTS,MEM.RESOLVE
Vectors:
VADD,VSEARCH(HNSW, tunableEF,EXACTmode)Strings:
GET,SET(EX/PX),INCR,STRLEN,TYPE,KEYS *Lists:
RPUSH,LPUSH,LRANGE,LLEN,LPOP, blockingBLPOPStreams:
XADD(explicit, partial, and auto IDs),XRANGE,XREAD(withBLOCKand$)Sorted sets:
ZADD,ZRANK,ZRANGE,ZCARD,ZSCORE,ZREMBitmaps:
SETBIT,GETBIT,BITCOUNT,BITOP AND/OR(binary-safe strings)Geospatial:
GEOADD,GEOPOS,GEODIST,GEOSEARCH ... FROMLONLAT ... BYRADIUSTransactions:
MULTI,EXEC,DISCARD,WATCH,UNWATCH(optimistic locking via per-key write versions)Pub/Sub:
SUBSCRIBE,UNSUBSCRIBE,PUBLISHReplication:
Master–replica handshake (
PING,REPLCONF,PSYNC) and command propagationREPLCONF GETACKoffset tracking andWAIT
Persistence: RDB loading; AOF with a manifest,
appendfsync always/everysec/no, and torn-write recoveryAuth:
AUTH,ACL WHOAMI/GETUSER/SETUSER(SHA-256 password hashes)
Running the engine on its own
java src/main/java/Main.java # port 6379
java src/main/java/Main.java --bind 127.0.0.1 # accept connections from this machine only
java src/main/java/Main.java --port 6380 --replicaof "localhost 6379"
java src/main/java/Main.java --dir /tmp/data --appendonly yes --appendfsync everysecReproducing the benchmarks
Each benchmark runs against the engine you've started:
redis-benchmark -p 6379 -t set,get # throughput; also run against redis-server to compare
python vector_bench.py # HNSW recall and latency (start the engine with -Xmx4g)
python crash_test.py everysec 5 # kill -9 crash test
python memory_mcp/calibrate.py # embedding similarity calibrationRelated work
Aether Cache is an early, open-source project. These are the production systems in the same space:
Vector search in Redis-style stores:
Redis 8 vector sets and valkey-search add HNSW vector search to Redis and Valkey.
hnswlib is the reference HNSW implementation.
Malkov & Yashunin (2016) is the paper this index follows.
Memory for AI agents: Mem0, Zep, Letta, and the MCP reference memory server.
Instruction files: AGENTS.md and
CLAUDE.md. Aether Cache syncs into these rather than replacing them.
Status and limitations
Aether Cache is v1 and early. It works end to end in automated tests and is now being tried in real Claude Code sessions.
Not done yet:
A with-memory vs. without-memory evaluation on real coding tasks, to measure whether it actually reduces re-explaining.
AOF compaction: the log only grows, and every status change adds a line.
Engine limitations:
Threading: one OS thread per connection, not an event loop. That's fine for a local tool, but it won't scale to thousands of clients like Redis.
Transactions:
EXECisn't isolated, so other clients' commands can interleave with a transaction's commands.Vector index: re-adding a vector updates it without rewiring its graph links, and there's no delete. Retired memories are filtered out, not removed.
Replication: full resync only, and a new replica assumes the master's dataset is empty.
Smaller gaps:
KEYS *lists only string keys.GEOSEARCHscans every member.ACL supports only the
defaultuser.HELLO(RESP3) isn't supported, so clients must use RESP2.
Project layout
src/main/java/Main.java the engine (single file, from the CodeCrafters setup)
aether_cache/ the Python package behind the `aether-cache` command
server.py MCP server: embeddings, git staleness, AGENTS.md/CLAUDE.md sync
engine.py finds Java, starts and stops the engine in the background
javart.py downloads a private copy of Java when the computer has none
connect.py registers Aether Cache with Claude Code, Cursor, and Codex
cli.py the `aether-cache` command
ui.py, ui/index.html the dashboard (`aether-cache ui`): a local server, its API, and the page
portability.py import from notes files, export to JSON
engine/aether-engine.jar the prebuilt engine (rebuild with `python scripts/build_engine.py`)
memory_mcp/calibrate.py similarity calibration for duplicate/contradiction thresholds
vector_bench.py HNSW benchmark (writes results.json)
crash_test.py kill -9 crash-safety testLicense
MIT. See LICENSE.
Available Tools
10 toolscheck_staleA
Check whether any memory's linked files have changed since it was saved, and retire those memories. recall already does this automatically; call it directly after big changes.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It does disclose a destructive effect ("retire those memories") and that recall duplicates this behavior implicitly, which is valuable. But it never defines what retirement means (deletion vs. flagging vs. mark_outdated), nor whether the check is report-only, leaving real ambiguity about consequences.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no filler, with the action and its scope front-loaded before the sibling cross-reference. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, and the description covers purpose, triggering condition, and the sibling overlap. The only shortfall is the undefined semantics of "retire," which matters for a mutating, no-parameter tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is no parameter meaning to convey; the description correctly explains the implicit scope (all memories with linked files) instead of inventing arguments. Baseline 4 applies for a parameterless tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: check whether a memory's linked files changed since save, then retire those memories. It also names the sibling that performs the same work (recall), so an agent can separate it from the other memory tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit condition for calling it ("directly after big changes") and names the alternative (recall does this automatically). The phrasing is slightly ambiguous about why to call it at all if recall is automatic, but the when-to-use signal is present and concrete.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
confirm_memoryA
Mark a memory as still true (e.g. after it was retired because a linked file changed, but the fact itself didn't). Re-links it to the current version of its files.
| Name | Required | Description | Default |
|---|---|---|---|
| memory_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It usefully discloses a side effect beyond the name — it re-links the memory to the current version of its files — but says nothing about required permissions, reversibility, or what happens to conflicting/stale links.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the core action and followed by the qualifying scenario and the side effect. No filler, though the parenthetical adds a level of nesting that slightly slows reading.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Output schema exists, so return values need not be described, and the tool is a simple single-parameter mutation. However, with zero annotations the description should cover more of the mutation's behavior — permissions, reversibility, or how re-linking interacts with check_stale/sync_to_files — to be fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
One required parameter with 0% schema description coverage, and the description never mentions it. The parameter is a single self-evident identifier ('memory_id') for the memory being confirmed, so the gap is small, but no format or sourcing guidance is added.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Mark a memory as still true') and clarifies the state transition being reversed (retired when a linked file changed). This implicitly contrasts with mark_outdated and replace_memory, though no sibling is named explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The parenthetical supplies a concrete trigger condition — use it after a memory was retired because a linked file changed but the fact itself didn't. It does not state the inverse case (when the fact is no longer true, use mark_outdated) or name alternatives, so it stops short of a full when/when-not routing guide.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
explain_memoryA
Show where a memory came from (which tool and session saved it, which files it's linked to) and everything that has happened to it since. Use it when a memory looks wrong or surprising.
| Name | Required | Description | Default |
|---|---|---|---|
| memory_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. The naming ('explain', 'show') strongly implies a read-only inspection, and the description discloses the two categories of information returned (origin and subsequent history), but it never states read-only semantics, whether any state changes, or permission needs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the core purpose and closing with the usage cue. No filler or restated name/title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return structure need not be explained, and the description already says what is surfaced. For a one-parameter read tool this is nearly complete; only the read-only guarantee and memory_id format are missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% for the single memory_id parameter, so the description should compensate and does not — it says 'a memory' with no hint about ID format or how to obtain one. The parameter is self-describing enough ('memory_id', title 'Memory Id') that the gap is mild rather than fatal.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: 'show where a memory came from' plus 'everything that has happened to it since'. This clearly separates it from siblings like recall (retrieval) or check_stale (staleness), though it never names an alternative explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a concrete triggering condition: 'Use it when a memory looks wrong or surprising.' That is real when-to-use guidance, but there is no when-not guidance and no named alternative for related diagnostic tasks such as list_conflicts or check_stale.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_conflictsA
List pairs of memories in this project that may contradict each other and need a decision.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. 'List' implies a read-only operation, which is a reasonable signal, but the description does not confirm non-mutation, disclose whether results are ranked or filtered, or say anything about the decision workflow the pairs feed into.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One sentence, front-loaded with the verb, and every clause adds scope or intent. No filler or restated title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return structure need not be explained. The description covers scope and intent adequately for a zero-param lister, though it could have named resolve_conflict to complete the workflow picture.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing for the description to disambiguate; baseline 4 applies. The description correctly signals that scoping is implicit (current project) rather than parameterized.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (List) and resource (pairs of memories that may contradict each other), with the scope confined to 'this project'. It implicitly distinguishes itself from resolve_conflict by framing the output as items that 'need a decision', though it does not name that sibling.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'need a decision' hints that this is a discovery step preceding resolve_conflict, but the description never states when to call this versus resolve_conflict or check_stale. Usage is implied rather than spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
mark_outdatedA
Mark a memory as no longer true, with a short reason. It stops showing up in recall but keeps its history, so you can still see what it said and why it was retired.
| Name | Required | Description | Default |
|---|---|---|---|
| reason | Yes | ||
| memory_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does disclose the important behavior: the memory disappears from recall while its history and the retirement reason remain visible. That is exactly the state semantics an agent needs for a mutation. It does not cover reversibility (can a mark be undone?), permissions, or error behavior, which keeps it from a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences, front-loaded with the action and followed by the consequence. Every clause carries information (action, scope, recall effect, history retention, rationale for the retained reason) with no padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, and the description covers the core state transition and what is preserved. The main remaining gap is the undocumented parameters and unstated failure/undo behavior for a mutation tool with no annotations.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% — neither memory_id nor reason has any description. The description partially compensates by framing the target as "a memory" and characterizing reason as "a short reason," giving a hint about length and intent. It adds no format, ID convention, or constraint detail for memory_id, so the gap is only partly closed.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource+target state: "Mark a memory as no longer true." The effect clause ("stops showing up in recall but keeps its history") cleanly separates it from a hard delete or from replace_memory, which is useful for an agent scanning siblings. It stops short of naming an alternative tool explicitly, so it is clear but not fully differentiated.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the state change described (memory is no longer true), but there is no explicit guidance on when to prefer this over replace_memory (correct the content) or confirm_memory (re-affirm), and no statement of preconditions such as the memory needing to exist. An agent can infer the case but is not routed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
recallA
Search this project's shared memory by meaning. Call it at the start of every task, and whenever project-specific knowledge (setup, conventions, past decisions) might matter. Only current (active) memories are returned; outdated or replaced ones are skipped.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden and it does disclose a real behavioral trait: only active memories are returned and outdated/replaced ones are skipped. It does not mention auth requirements, rate limits, or result ordering, but the filtering semantics are a meaningful addition.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with purpose followed by the call trigger and the result-filtering caveat. Nothing is redundant or padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return structure need not be explained. The description covers purpose, timing, and result scope for a read-only search tool, but leaves the limit parameter semantics and any guidance on query phrasing unstated.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate. 'By meaning' usefully defines the query parameter as a semantic/natural-language search rather than keyword matching, but the limit parameter is never explained and no format or defaults are given beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Search this project's shared memory') plus the retrieval mechanism ('by meaning'), which separates it at a glance from siblings like remember, mark_outdated, or list_conflicts. An agent can pick this tool without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit timing guidance: 'Call it at the start of every task, and whenever project-specific knowledge (setup, conventions, past decisions) might matter.' That is strong when-to-use context, though no alternatives or exclusions are named (e.g., when to prefer explain_memory or check_stale).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
rememberA
Save a durable fact about this project so future sessions, in any AI tool, know it. Use this, not a built-in memory or notes file, whenever the user asks you to remember something about the project.
Good facts: conventions ("we use pnpm, not npm"), setup gotchas ("tests need DOCKER_HOST set"),
decisions and their reasons. Not: temporary task details or anything in the code already.
files: repo-relative paths this fact depends on (e.g. ["docker-compose.yml"]), so the memory
can be flagged when those files change.
| Name | Required | Description | Default |
|---|---|---|---|
| fact | Yes | ||
| files | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description must carry the behavioral load. It does disclose real traits: the fact persists across sessions and tools, and dependent files cause the memory to be flagged when those files change. However, it says nothing about what happens when a conflicting or duplicate fact already exists (notable given siblings like list_conflicts and resolve_conflict), nor about failures or permissions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then usage, then content guidance, then the auxiliary parameter. Every line contributes, though the 'files' paragraph reads like schema documentation appended with a leading lowercase parameter name, which slightly breaks the prose flow.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, and the description covers persistence semantics, trigger conditions, and the file-linkage behavior well. The remaining gap is conflict/duplicate handling, which is meaningful in a toolset that exposes list_conflicts, resolve_conflict, and mark_outdated.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate, and it largely does. The 'files' parameter is fully explained (repo-relative paths, concrete example, and the purpose of the linkage), and 'fact' receives content guidance via the good/bad fact examples, which is more useful than a bare type declaration.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ('Save a durable fact about this project') plus the scope that matters: durable across future sessions and any AI tool. It clearly separates itself from an external alternative (built-in memory or notes file), but it never distinguishes itself from its actual siblings such as recall, replace_memory, or mark_outdated, so an agent gets no routing help within the tool family.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives an explicit when-to-use trigger ('whenever the user asks you to remember something about the project'), an explicit when-not ('temporary task details or anything in the code already'), and contrasts with a competing mechanism ('use this, not a built-in memory or notes file'). Good-fact and bad-fact examples remove remaining ambiguity.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
replace_memoryA
Retire an older memory because a newer one replaces it (e.g. "we use Postgres" replaced by "we migrated to MySQL"). The old memory stops showing up in recall but keeps its history.
| Name | Required | Description | Default |
|---|---|---|---|
| new_id | Yes | ||
| old_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses that the old memory disappears from recall but retains its history, which is the key non-destructive/retirement behavior an agent needs. It still omits whether the operation is reversible, what happens if new_id doesn't exist, and permission requirements.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences, front-loaded with the core action, and the parenthetical example earns its place by disambiguating the semantics. No filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values need no explanation, and the behavioral effect is covered. However, for a mutation-style tool with zero annotations and two undocumented required ID parameters, the description leaves an agent guessing about ID provenance and irreversibility.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% and both required parameters are undocumented titles ('Old Id', 'New Id'). The description implies the old/new relationship but never states that these are memory identifiers, nor any format or validity constraints, so it only partially compensates for the coverage gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Retire an older memory') plus the causal condition that defines it ('because a newer one replaces it'), with a concrete example. This distinguishes it from siblings like mark_outdated, which retires a memory without a superseding replacement.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The selection condition is clear and concrete: use this when a newer memory supersedes an older one, and the Postgres/MySQL example makes the scenario unambiguous. It does not explicitly name mark_outdated as the alternative for retiring without a replacement, nor state exclusions, so it stops short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
resolve_conflictA
Settle a possible conflict. replaces_old=True: the new memory replaces the old one (the old one is retired). replaces_old=False: both are true, keep both. Ask the user if you're not sure.
| Name | Required | Description | Default |
|---|---|---|---|
| new_id | Yes | ||
| old_id | Yes | ||
| replaces_old | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It usefully discloses the destructive outcome of replaces_old=True ('the old one is retired') and the non-destructive alternative, but says nothing about reversibility, permissions, error behavior, or whether other memories referencing the retired one are affected.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Tight and front-loaded: the core action comes first, then the two flag branches, then the escalation rule. No filler sentences, though the value-branch phrasing is slightly terse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be described, and the acceptance/rejection semantics of the boolean are covered. The main omission is any prerequisite about how a conflict is identified before calling this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate. It fully explains the pivotal boolean replaces_old with both branches, which is the hardest parameter; however, new_id and old_id are left entirely undocumented and their roles are only inferable from their names.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('settle') and resource ('a possible conflict'), and the flag semantics make the outcome unambiguous. It does not explicitly differentiate itself from siblings like list_conflicts or replace_memory, so the agent must infer the boundary.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Ask the user if you're not sure' gives a useful uncertainty rule, and the tool is implied to be used when a conflict is detected. However, it never names which sibling to use instead (e.g. list_conflicts to find conflicts, replace_memory for a straight replacement), leaving the routing decision to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sync_to_filesA
Write this project's current memories into the managed section of AGENTS.md / CLAUDE.md, so they're committed with the code: teammates and tools without Aether Cache see them, and changes show up in reviews. Call it when the user asks, or after a batch of new memories.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses the write target, that only the 'managed section' is touched (implying other file content is preserved), and the observable downstream effects (shared with teammates and non-Aether tools, visible in code review). It leaves one real ambiguity — whether the tool itself commits or merely writes files that are later committed — and says nothing about idempotency or overwrite behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the action and target, followed by the rationale and the trigger condition. No filler or restated name/title; every clause adds either scope, justification, or the call condition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation, and with zero parameters the remaining burden is behavioral. The description covers destination, audience, and trigger, but does not resolve whether the tool performs the commit or whether repeated calls are safe, which are the two things an agent would still want before invoking it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing for the description to clarify beyond the schema. The baseline for a no-argument tool applies, and the description correctly spends no words on parameter explanation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and destination ('Write this project's current memories into the managed section of AGENTS.md / CLAUDE.md'), which is materially different from siblings like remember (writes to the cache) or recall (reads). An agent can distinguish this persistence/sync action from the rest of the family without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit trigger conditions: 'Call it when the user asks, or after a batch of new memories.' That is clear when-to-use guidance, though it names no alternative or exclusion (e.g. it does not say what to use instead when only a single memory changed).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
10 tool updates
- First observed
check_stale - First observed
confirm_memory - First observed
explain_memory - First observed
list_conflicts - First observed
mark_outdated - First observed
recall - First observed
remember - First observed
replace_memory - First observed
resolve_conflict - First observed
sync_to_files
TDQS
Scored across 10 tools
Most tools have distinct purposes (create, read, inspect, sync, conflict management), but there is a cluster of retirement-oriented tools (mark_outdated, replace_memory, check_stale, resolve_conflict) that could be confused at a glance. Detailed descriptions mitigate the overlap, so an agent can usually select correctly.
The set is predominantly verb_noun in snake_case (explain_memory, mark_outdated, replace_memory, check_stale, confirm_memory, sync_to_files, list_conflicts, resolve_conflict), but two foundational tools (remember, recall) are bare verbs. This is a minor deviation that remains readable and largely predictable.
With 10 tools, the set is well-scoped for project memory management. Each tool serves a clear role across creation, retrieval, lifecycle, and conflict resolution, and no tool feels redundant or missing.
The lifecycle is well covered: create, search, inspect provenance, retire, replace, confirm, sync, and resolve conflicts. However, there is no explicit hard-delete or purge operation, and no direct way to list all memories without a semantic search, which are minor gaps for the domain.
Maintenance
Related MCP Connectors
Shared memory for coding agents. Stop re-explaining your codebase every session.
Private memory for AI agents: save and recall facts across Claude Code, Codex, Cursor and ChatGPT.
Persistent memory for Claude Code and Cursor. Stop re-explaining your project every session.
Shared memory for AI coding agents. Save once, reuse from Cursor, Claude Code, Codex.
Related MCP Servers
- AlicenseAqualityDmaintenanceHybrid semantic + keyword memory across all your projects. Works with Cursor, Claude Code, and your team.1124 PyPI1MIT
- AlicenseBqualityAmaintenancePersistent, local memory for AI coding agents that learns how you work, not just what you said. Supports Claude Code, Codex CLI, Cursor, and any MCP client.79269 PyPI73MIT
- AlicenseNot gradedqualityBmaintenanceProvides a local long-term memory layer for AI coding tools like Cursor and Claude Code, enabling cross-session, cross-tool sharing of project facts, user preferences, decisions, and workflows.12 npm2MIT
- AlicenseNot gradedqualityBmaintenancePortable AI memory that roams with you across models and harnesses — one shared memory for Claude Code, Codex, Cursor, Gemini CLI and more, stored as plain markdown files you own. Per-project spaces plus a shared layer, vault-wide search; runs locally over stdio/HTTP or hosted with OAuth 2.1 backed by your own GitHub repo.55 npmMIT