Gingugu
Gingugu is a local MCP memory server that gives AI coding assistants persistent, structured, searchable long-term memory in a single SQLite file.
Store, update, and deprecate/delete typed memories (fact, decision, pattern, bug, architecture, preference, workflow, context, capability) with tags, confidence levels, provenance, and namespaces.
Search and recall with hybrid BM25 + semantic ranking, filters (type, tags, confidence, dates, ids), multi-namespace scoping, compact mode, and per-hit score breakdowns.
Auto-surface relevant context at session start across one or many namespaces, with review hints for stale point-in-time claims.
Read inside a single memory via literal substring search with offsets/line numbers or exact character-range slicing.
Build and repair a typed relationship graph (supersedes, contradicts, caused_by, parent_of/child_of, related_to) with batch, retype, reverse, and delete operations.
Consolidate near-duplicate memories (merge, summarize, deduplicate) and run a deterministic "dream" pass (PageRank, communities, orphan reconnection) staged for accept/reject.
Bind memories to tool calls as tripwires so a critical rule blocks the first matching action.
Manage namespaces, default-repo refs, and review sweeps for open PR/MR claims and orphaned graph nodes.
Monitor health via stats (dormancy, counts, confidence distribution, store size, pinned tier, query log).
Export and import memories as portable JSON (skip or replace on conflict).
Store and retrieve credentials via the OS keychain, with redaction, expiry tracking, and safe file output (0600).
Provides cross-session memory for Windsurf (Codeium) AI assistant, supporting knowledge retention and recall.
Gingugu
Your AI forgets everything between sessions. Gingugu fixes that.
Gingugu is a local MCP server that gives AI coding assistants a real long-term brain β persistent, structured, searchable memory that survives across sessions, repos, and projects. No cloud, no API keys, no telemetry. One SQLite file on your machine.
π Table of Contents
Related MCP server: mnemos
Why Gingugu
Every session with an AI assistant starts from zero. The decisions you made yesterday, the bug you fixed last week, the architecture you settled on a month ago β gone. Existing memory tools dump observations into a flat pile with no structure, no staleness tracking, no relationships, and no sense of what's relevant right now.
Gingugu is designed to be a structured long-term brain β not a junk drawer:
Remembers across sessions, repos, and projects
Organizes knowledge by namespace, type, and relationships
Ranks memories by relevance, freshness, and confidence
Auto-surfaces relevant context when you start working
Consolidates duplicate and related knowledge on demand
The protocol ships with it
Storage is the easy half. A memory server that an agent never writes to is an empty database, and an agent left to its own judgement will save almost nothing worth keeping β the failure mode isn't retrieval, it's discipline.
So Gingugu ships the discipline too. gingugu init wires a repo in one
command and installs a SessionStart hook that injects the memory protocol
at the top of every session: load these namespaces, check memory before asking
a question already answered, save at the moment of observation rather than
batching to the end, build a relation only when it records something search
cannot infer. There is no rules file to paste and nothing to remember to do β
the harness runs it whether or not the agent feels like it. A stop hook
then checks that a session with real work in it actually wrote something down.
Three more hooks bring memory to you: involuntary recall surfaces what your
prompt woke, tripwires stop a tool call that a memory says must not be
missed, before it runs, and warm minions hand a subagent a fenced,
pre-loaded brain of its own.
That is the part that makes the memory worth having, and it is in the box.
Retrieval quality
Hybrid retrieval (BM25 over FTS5 + local embeddings, fused with Reciprocal
Rank Fusion) measured with the in-repo bench/
toolset β MRR 0.828, recall@1 0.611, recall@5 0.983.
Measured over 30 labeled questions against a real working brain (~1,100
memories), not a public benchmark suite, so read it as a regression baseline
for this workload rather than a cross-product comparison. The runner is
deterministic and committed, so you can point it at your own store and get
your own numbers: python -m bench --help.
The benchmark can also build its own labeled question set from your store
(python -m bench --db <store> --generate-probes <out>), by picking questions
whose answer is provable rather than hand-judged: a phrase that occurs in
exactly one memory has exactly one correct answer. That gives you a golden set
sized to your own brain without labeling anything by hand, and it stays local.
Where this goes long-term β federated, org-wide agent memory β lives in docs/enterprise-vision.md.
FAQ
Those are great if you live in one tool. The moment you switch between Claude Code in the morning and Cursor in the afternoon, the memory is gone. Gingugu's memory follows you across every MCP client, lives on your machine, and is programmable (21 tools, structured types, relationships, confidence levels). The built-ins are convenience features. Gingugu is infrastructure.
Both, actually. We do hybrid retrieval out of the box: BM25 over FTS5 + local semantic embeddings, fused with Reciprocal Rank Fusion. No vector DB server required.
Why this stack:
No deployment. One SQLite file holds memories, FTS5 index, and embeddings. No Postgres, no Pinecone, no Chroma server.
Two embedding backends β pick one:
fastembed (default) β ONNX-based, no PyTorch, ~80MB model download to
~/.cache/fastembed. Works fully offline after first use.Ollama β delegates to your already-running Ollama process via its HTTP API. Zero extra memory footprint. Set
MEMORY_EMBEDDINGS_BACKEND=ollama.
It composes. Hybrid relevance feeds the composite (relevance Γ freshness Γ access Γ confidence) β every signal in one engine.
You can disable semantic search via MEMORY_EMBEDDINGS_ENABLED=false and
fall back to BM25-only.
Usable today for local personal workflows. 1560+ tests passing covering storage, search, migrations, concurrency, credentials, and edges. Hardened against adversarial input and write contention. WAL mode for concurrency. CI matrix across Python 3.11β3.13 on Linux/macOS/Windows. Dogfooded daily in this repo (the memories you see referenced in commits are Gingugu memories).
It's still early β broader real-world validation across MCP clients,
databases at large scale, and long upgrade horizons is the work ahead.
Treat it as an early cognitive-runtime framework, not a finished product.
See SECURITY.md for the threat model, and
docs/future-architecture.md for where
this is headed.
SQLite FTS5 comfortably handles millions of rows. Gingugu adds composite
re-ranking on top, but only over a small candidate pool (4Γ limit). For
typical personal/team use it should hold up well β though we haven't
yet benchmarked at the 100k+ memory scale. Use memory_consolidate to
merge duplicates or summarize clusters when things sprawl.
It's a local CLI/server tool. Python's SQLite + keyring + asyncio story is
mature, the install footprint via uv is small, and there's no JS bundling
or Rust toolchain required to use it. The MCP SDK is first-class in Python.
Features
Feature | Description |
π·οΈ Namespace Scoping | Memories auto-scoped to repos/projects with cross-repo pattern sharing |
π Hybrid Search | SQLite FTS5 (BM25) + semantic embeddings fused with Reciprocal Rank Fusion. Two backends: fastembed (ONNX, offline) or Ollama (zero extra footprint, uses your existing Ollama process) |
β° Temporal Intelligence | Trust-led scoring, dormancy tracking (never forgets), "last confirmed" tracking, spreading activation |
π Review Hints | Point-in-time memories ("PR #947 open, waiting onβ¦", passed expiry dates) get advisory staleness flags on every read - you reconcile, the server never mutates |
π Relationships | A typed graph over what similarity can't see: supersedes, contradicts, caused_by, parent_of/child_of (related_to as a fallback) |
π― Confidence Levels | verified β inferred β stale β deprecated lifecycle |
π§Ή Consolidation Tools | Find near-duplicate clusters (read-only suggest scan), then merge, summarize, or deduplicate on demand |
π Auto-Context | Surfaces relevant memories on session start - one call loads many namespaces deduped, with an optional compact mode for lighter payloads |
π Health Metrics | Memory stats, dormancy reports, review sweep, namespace overviews |
π Credential Vault | Secure service-bundle storage for API keys/tokens via OS Keychain |
π Memory Explorer UI | Interactive knowledge graph + dashboard for visualizing memory data |
π‘ Central Brain (optional) |
|
Architecture
graph TD
A[AI Assistant<br/>any MCP client] -->|MCP Protocol| B[Gingugu Server]
B --> C[Search Engine<br/>FTS5 + BM25]
B --> D[Storage Layer<br/>SQLite + WAL]
B --> E[Decay Engine<br/>Scoring + Dormancy]
B --> F[Context Engine<br/>Auto-Retrieval]
B --> H[Consolidation Engine<br/>Merge + Dedupe]
B --> K[Credential Vault]
C --> D
E --> D
F --> D
H --> D
K --> D
K --> J[OS Keychain<br/>via keyring]
D --> G[(~/.local/share/gingugu/memories.db)]See docs/architecture.md for full technical details.
Setup
Prerequisites
Python 3.11+
uv(recommended) orpipmacOS, Linux, or Windows β the credential vault uses your OS-native secret store via
keyring(macOS Keychain, Windows Credential Locker, Linux Secret Service/KWallet). On headless Linux without a Secret Service backend, everything works except storing secrets.
Install
# Recommended: uv (fast, manages Python for you)
uv tool install gingugu
# Or with pip
pip install ginguguThat's it. The gingugu command is now on your PATH.
git clone https://github.com/gingugu/gingugu.git && cd gingugu
uv sync
uv run gingugu # or pip install -e .Usable today. 21 MCP tools live. 1560+ tests passing. Dogfooded daily in Claude Code and Windsurf β this repo's own memories live in a Gingugu database. Early and seeking broader real-world validation.
Upgrading
1. Upgrade the package.
uv tool upgrade gingugu # if installed with uv
pip install --upgrade gingugu # if installed with pip2. Restart your MCP client. The client spawns the server, so a running
client keeps the old code until it restarts. Schema migrations apply
automatically on the next start, and a one-shot backup of your database
(memories.db.bak-before-vN) is taken before any migration runs. Your
memories are never rewritten by an upgrade.
3. Re-run gingugu init in each repo to pick up improvements to the
hooks and the session protocol:
cd ~/code/my-repo && gingugu init --force--force is what refreshes managed files that already exist; without it,
init leaves them alone and you stay on the old hooks. Run --dry-run first
if you want to see the changes before they land. Your .claude/settings.json
is merged, not overwritten.
If you have edited a managed file yourself, --force saves your version
alongside it as <name>.bak before writing the new one, and says so in the
output. A file it would not change is left untouched and gets no .bak.
gingugu can be reachable through more than one install at once, and they
version independently. The usual surprise is a repo virtualenv shadowing the
tool install, so a fresh shell resolves to a different binary than the one you
just upgraded:
which -a gingugu # note the -a: a bare `which` shows only the winnerCheck your MCP client config too. If it points at a source checkout (e.g.
uv --directory ~/code/gingugu run gingugu), the client runs that tree and
a package upgrade changes nothing for it β restart the client instead. And
because it runs whatever is checked out, a source-backed client also follows
you onto a feature branch.
Version strings can't settle this on their own: an unreleased local checkout and the last published release report the same number until someone bumps it. When it matters, confirm with behaviour β run a command whose output you know changed in the new version.
Finish embedding after an upgrade (optional)
The server backfills embeddings in small batches at startup, on purpose - a
cold model download must never block the process an editor is waiting on.
That is the right default and the wrong tool right after an upgrade that
changes what gets embedded: at one batch per startup, catching up a large
existing brain could take dozens of restarts. gingugu embed runs the same
backfill to completion in a single run instead:
gingugu embed [--batch-size N]It's safe to interrupt and re-run - every batch commits, and a memory that already has a current vector is never re-selected. Takes about five minutes on an existing brain of a few thousand memories. Run it once after upgrading so every memory already in your brain is caught up immediately, rather than trickling in one batch per server restart.
Run as a remote server (optional)
By default gingugu runs over stdio (the client spawns it). To reach one
shared instance over the network instead β a hosted/central brain β run:
gingugu serve # streamable HTTP on http://127.0.0.1:8765/mcpEvery request needs a Bearer token. Set MEMORY_SERVE_TOKEN to pin one, or let
the server generate and persist it to <db-dir>/serve_token (0600, reused
after; the log shows the path, never the token). Set MEMORY_SERVE_HOST=0.0.0.0 to accept remote
connections. On a loopback bind the server also rejects any Host header
other than localhost (DNS-rebinding protection); on a LAN bind the Bearer token
is the gate.
TLS is on by default for any non-loopback bind, with nothing to set up. On
first start the server generates a self-signed certificate for this machine's
name (<host>, <host>.local, loopback) and keeps it in <db-dir>/tls/ (key
0600), reused after; the log shows its SHA-256 fingerprint. A loopback bind
stays plain http, and local (stdio) mode never touches a certificate.
gingugu remote login (below) pins that certificate for you over SSH. A client
pointed at the URL directly will not trust a self-signed cert: give the server
one it does trust (MEMORY_SERVE_TLS_CERT + MEMORY_SERVE_TLS_KEY, PEM), or
opt out with MEMORY_SERVE_TLS=off on a network you trust. Extra names for the
generated cert go in MEMORY_SERVE_TLS_SAN. With a cert the client trusts
(or plain http), point a client at it directly with:
{ "mcpServers": { "gingugu": {
"url": "https://<host>:8765/mcp",
"headers": { "Authorization": "Bearer <token>" }
} } }That token is the owner's and has full access. Give every other client - a second laptop, another assistant, a subagent - its own scoped token instead, limited to the namespaces it needs, each read-only or read-write:
gingugu token add laptop-2 --ns my-project=write,crow=read # printed once
gingugu token add reviewer --ns '*=read,scratch=write' # * = any namespace
gingugu token list
gingugu token revoke laptop-2 # live on the next requestThe server enforces the grant, not the client: anything outside it reads as
not found, an omitted namespace means the token's namespaces, and the
whole-brain tools (memory_export, memory_import, memory_dream, the
credential vault, and namespace admin) are closed to scoped tokens. Only a
SHA-256 of each token is stored, in <db-dir>/serve_tokens.json (0600).
This is one owner sharing a brain with their own clients, not a multi-tenant service.
Your own machines each get a full-access token of their own, named and
revocable on its own: gingugu token add my-laptop --owner. To skip handling
it at all, let the machine mint it over SSH with a key that can do nothing else.
Give each machine its own key, and on the server authorize it with a forced
command pinned to that machine's name (one line per machine, in the
authorized_keys of the account the server runs as):
restrict,from="192.168.1.0/24",command="env MEMORY_DB_PATH=/srv/gingugu/memories.db /srv/gingugu/bin/gingugu token ssh-mint --name my-laptop" ssh-ed25519 AAAA... my-laptopUse full paths (a forced command gets no login PATH) and the server's own
MEMORY_DB_PATH, or the token lands in a file the server never reads. --name
means the key can only ever rotate its own machine's token. To cut a machine
off, delete its line and gingugu token revoke my-laptop. Without --name, any
holder of the key could mint itself a new token after a revoke. Then, on the
machine:
gingugu remote login http://brain.local:8765 --ssh me@brain.local # token -> OS keychain
gingugu remote on https://brain.local:8765 # this machine now uses that brain
gingugu remote status
gingugu remote off # back to the local brainlogin uses ~/.ssh/gingugu_mint by default (--key to change it) and ignores
~/.ssh/config, so a default identity can never stand in for the restricted
key. Before it mints, login asks the same forced command for the brain's
generated certificate (cert), pins it in <user-data-dir>/remote-tls/, and
files the token under the https URL - the SSH channel you already trust is
what vouches for the certificate. Before anything is rotated it also checks the
live brain over https with that pin, so a certificate that is stale or does
not name the host you dial stops the login with the fix spelled out, and the
old token keeps working. From then on every remote client verifies the
brain against that pin alone, and plain http to it is refused rather than
sent. A machine already on that brain over http is moved to https. A brain
with a certificate you brought is not pinned: give login its https URL
(if a generated cert is still in <db-dir>/tls/ from before, delete it on the
brain). If the brain's certificate is regenerated, run login again.
Local is the default: with no setting, nothing is remote. A single client
can choose for itself with MEMORY_REMOTE_URL in its own environment (off
forces local). A URL carrying a username or password is refused.
Remote mode: a bare gingugu relays
With a remote brain on (gingugu remote on, or MEMORY_REMOTE_URL), a bare
gingugu never opens the local database. It relays MCP over stdio to
gingugu serve, so every client config stays command: gingugu. It never falls
back to the local DB for memory: it refuses to start (exit 2) when
MEMORY_GRANTis malformed, blank or*=write;MEMORY_GRANTis set andMEMORY_CREDENTIALS_ENABLEDis notfalse- a scoped client never gets the vault;the keychain holds no token (run
gingugu remote login);the brain is unreachable - after retrying for up to 15 seconds, so a brain slow to answer after a quiet spell does not cost the session.
The credential vault stays on the machine: secrets are in its keychain and a
remote brain cannot serve them. With credentials on (the default, and only
without MEMORY_GRANT), the proxy serves the credential_* tools itself from
the local database's vault tables, so they keep working - into included - and
a credential call never reaches the brain, even while it is down. reveal is
refused there: every other tool is relayed, so an inline secret could be carried
off the machine, while into keeps it in a local 0600 file. Set
MEMORY_CREDENTIALS_ENABLED=false and they are hidden and refused. A
credential_delete still asks you to confirm: the proxy's local vault shows the
dialog through your client, just as the brain does for its own destructive tools.
Plain http to a non-loopback host starts with a warning. The proxy sends the
machine token only to POST /token/derive (owner tokens only; scoped tokens get
403), which trades it for a session token held in memory on the server: valid
at most an hour, SHA-256 only, at most 256 live, gone when the server restarts.
The session token carries the client's MEMORY_GRANT (full if unset) and its
MEMORY_NAMESPACE as its home, so a call that names no namespace lands in
the client's own namespace rather than the server's. The proxy refreshes the
token at half its life and, when the connection drops, reconnects with backoff
(0.25s doubling to 5s) and re-initializes the session itself. A call a restarted
brain refused before running it is sent again, once; a call whose fate is
unknown fails and is never replayed. New calls wait up to 5 seconds for the
connection to come back, then fail.
{ "mcpServers": { "gingugu": {
"command": "gingugu",
"env": {
"MEMORY_CREDENTIALS_ENABLED": "false",
"MEMORY_PERSONA": "research",
"MEMORY_NAMESPACE": "research",
"MEMORY_GRANT": "research=write,crow=read"
}
} } }The Claude Code hooks follow the same switch. With a remote brain on,
involuntary prompt recall, tripwires and minion warm-up ask the brain over
POST /hook/recall, /hook/tripwires, /hook/trip and /hook/warmup with the
machine token (these routes refuse every other token). The brain embeds and
ranks the prompt; tripwire rules come back to be matched on this machine, cached
for a minute, with the last copy kept while the brain is unreachable. Which
memories a session has already been shown stays on this machine and travels
with each request. If the brain cannot answer, recall and warm-up stay quiet
and tripwires keep their last rules; no hook ever reads a local database
instead. A client fenced by MEMORY_GRANT has its hooks fenced the same way:
the grant is applied on this machine before anything is sent (the routes trust
the list they get). Setting MEMORY_GRANT in the MCP server's env block is
enough: each gingugu server publishes the grant it runs under, and the hooks
apply what their client's servers published. If the hooks' own environment
sets it too, the two must agree, or the hooks stay quiet. A folder name the routes would refuse
(say My Repo) is left out of the request. Every hook call carries the machine
token and goes through the same pin as the proxy; on a brain served with
MEMORY_SERVE_TLS=off it crosses the network unencrypted on every prompt.
Personas
MEMORY_PERSONA names an agent's own namespace. Involuntary prompt recall,
tripwires and the SessionStart contract load crow (shared by every persona),
then the persona, then the repo. A malformed value is ignored. With
MEMORY_GRANT set, recall and tripwires drop any of those the grant cannot
read, and an unusable grant keeps them quiet. To fence a
persona to its own namespace, pair it with MEMORY_GRANT and MEMORY_NAMESPACE
as above: it writes to research, reads crow, and anything else reads as not
found.
Promote memories to a central brain (optional)
Once a central instance exists, gingugu promote harvests a local brain's
durable knowledge up to it - the tribal-knowledge loop:
GINGUGU_SOURCE_TOKEN=<local-token> GINGUGU_TARGET_TOKEN=<central-token> \
gingugu promote --source-url http://127.0.0.1:8765/mcp --source-ns my-project \
--target-url https://central:8765/mcp --target-ns org \
--contributor brian --dry-run # drop --dry-run to actually writeThe promoter is an MCP client (the server stays a pure store); a brain this
machine has pinned with gingugu remote login is verified against that pin. It is
read-only on the source, idempotent on re-runs, and applies an exclusion
filter: only verified memories move, minus episodic session noise, minus
personal-context tags, and it refuses to promote anything that looks like a
live secret - a shared brain must never become a credential leak. Each
promoted memory carries a provenance stamp (source instance, namespace,
contributor, timestamp).
Schedule the dream pass (optional)
The consolidation pass computes structure over the relation graph - PageRank, communities, orphan reconnection - and stages what it finds for you to accept or reject. It never writes to memories, so it is safe to run unattended.
There is no daemon to install. Your OS already knows how to run something
every fifteen minutes; what it cannot do is tell whether you are mid-session.
So --if-idle puts that judgment in the command:
# cron
*/15 * * * * gingugu dream --if-idle
# or a launchd StartInterval agent / Windows Task Scheduler trigger
# running exactly the same commandEach tick opens the database, reads one row, and exits in well under a second
unless the brain has actually gone quiet - by default 20 minutes untouched
(MEMORY_DREAM_IDLE_MINUTES, or --if-idle=45 for a one-off). A skip exits 0,
so your scheduler stays silent instead of mailing you every quarter hour.
"Untouched" means nobody used the brain, not no process is running - your editor keeps the MCP server alive all day whether or not you store anything, and that is exactly when the pass should get its turn. Come back to the keyboard mid-run and it stops between passes, keeping whatever it finished; the next run picks up the rest.
Review the queue with memory_dream(action="list"), and accept or reject each
finding. A run takes roughly 24 seconds on a 1,900-memory brain.
Cluster findings come ranked by the tags their members already carry, weighted
so that a rare tag counts for more than one spread across the whole store, and
a group whose strongest tag is already on every member is not staged at all -
accepting it could apply nothing. Each proposal shows the tag_score,
tag_cohesion and tag_gap behind its position, so the ordering can be
checked rather than taken on faith.
Edge findings pair an orphan with its closest neighbour, and the pass has no
way to know which end an arrow starts at. When the pair is right but the
direction is backwards, accept it with reverse=True rather than rejecting it.
Configure Your MCP Client
Gingugu speaks standard MCP over stdio β it works with any MCP client. Claude Code, Claude Desktop, Cursor, Cline, and Windsurf are all first-class.
Add to ~/.codeium/windsurf/mcp_config.json β a ready-to-edit template lives
at examples/mcp_config.json:
{
"mcpServers": {
"gingugu": {
"command": "uv",
"args": ["--directory", "/ABSOLUTE/PATH/TO/gingugu", "run", "gingugu"]
}
}
}β οΈ Windsurf's
mcp_config.jsonis global, not per-workspace, and it only interpolates${env:VAR}/${file:path}β not${workspaceFolder}. So a single server instance serves every repo.
claude mcp add gingugu -- uv --directory /ABSOLUTE/PATH/TO/gingugu run ginguguOr add the standard mcpServers block (as in the Windsurf example) to
.mcp.json in your project root for a per-repo setup.
Add the same mcpServers block to
~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or
%APPDATA%\Claude\claude_desktop_config.json (Windows).
Add the same mcpServers block to ~/.cursor/mcp.json (global) or
.cursor/mcp.json in your repo (per-project).
Cline β MCP Servers β Configure: add the same mcpServers block to
cline_mcp_settings.json.
Any client that supports stdio MCP servers works β point it at:
command: uv
args: ["--directory", "/ABSOLUTE/PATH/TO/gingugu", "run", "gingugu"]Scoping memories per repo: when your client's config is global (it can't
see the active workspace), the assistant passes a namespace argument on each
memory tool call (every tool accepts one). To instead pin a server instance to
a single project, set a static MEMORY_NAMESPACE in the env block. See
docs/architecture.md β Namespace Auto-Detection for the full resolution
order. Reads never dead-end on the wrong namespace: a recall with no namespace
on an unconfigured server searches all of them, and a scoped lookup that finds
nothing widens to every namespace and says so with widened_from.
Configure Your AI Agent
The MCP server gives your assistant the tools, but it won't use them effectively without instructions telling it when and how to call them.
Recommended (Claude Code): gingugu init
One command bootstraps a repo with the strongest setup Claude Code allows:
cd your-repo
gingugu initIt installs:
.claude/hooks/session_start.pyβ aSessionStarthook that auto-injects the memory startup contract into context every session. This is the key advantage: unlike a rules file (which is not guaranteed to be loaded into context), a hook fires every time, so the protocol is always present. The project namespace is derived from the repo's folder name automatically. A folder name that is not a plain name (letters, digits,.,_,-, starting with a letter or digit, up to 64 chars) is left out of the session-start contract, which then loadscrow(and the persona) only..claude/hooks/stop.pyβ aStophook that blocks once if a working session never saved anything, guarding the "unsaved session vanishes" trap..claude/hooks/user_prompt_recall.pyβ aUserPromptSubmithook for involuntary recall: memories that arrive because of what you typed, with no tool call and no decision by the assistant. Every other retrieval path answers "what did you ask for"; this one answers "what should have arrived anyway". Injected context reads as authoritative, so the design is built around refusing: a memory must clear a length floor, a similarity bar, a margin above the median of its own sweep, a keyword match on the same prompt, and not have been surfaced already this session. Pinned memories are skipped (they already load every session) and so are superseded ones. On a 548-prompt sample it fires on about 5% of turns. Every prompt long enough to search on is recorded, with what it surfaced, in thequery_logtable of your memory database - the same file as the memories. With a remote brain on, that database is the brain's, so the prompt text goes there. SetMEMORY_RECALL_HOOK=offto disable it..claude/hooks/pre_tool_tripwire.pyβ aPreToolUsehook for tripwires: memories bound, withmemory_tripwire, to a tool call that should not go unchallenged (a tag written into a commit, a merge of a stacked PR). Triggers are plain regexes over the tool name and its input, with no model judging relevance. The first matching call in a session is denied once, with the memory as the reason; re-issue it unchanged and it passes. Your memory tools never trip, so a bad tripwire can always be fixed. (Inside a subagent the minion fence still denies their write tools - that is a separate check, not a tripwire.) Each trip is recorded inquery_log. SetMEMORY_TRIPWIRES=offto disable it..claude/skills/sink-the-ship/SKILL.mdβ a/sink-the-shipskill to flush everything worth keeping before you close a session. If an older install left a.claude/commands/sink-the-ship.mdbehind,gingugu initretires it and keeps a.bak- but only if it is untouched. Edit that file and it is yours: it stays put, and the output tells you it did..claude/hooks/subagent_warmup.pyβ aSubagentStarthook (gingugu hook subagent) that warms a minion: a subagent whose agent file declares its own fenced brain (MEMORY_GRANT, see the table below) starts with the memories its task woke, read-only and inside that grant - and inside the spawning client's ownMEMORY_GRANT, when it has one. Inside a subagent,gingugu hook toolalso keeps it off the brain's data directory and theginguguCLI, and denies the write and credential tools of the inherited full-brain server, so a built-in agent like Explore can read the brain but not change it. This prevents accidents; it is not a sandbox against a hostile process running as you. SetMEMORY_MINION_FENCE=offto disable the fence.All five hooks wired into
.claude/settings.json, merged non-destructively β any existing config is backed up (settings.json.bak) and preserved.mcp__brainadded topermissions.allow, in the repo's.claude/settings.jsonand in your user-level~/.claude/settings.json(backed up, idempotent), so a fenced minion's calls to its ownbrainserver are not denied or prompted in any repo. The server enforces the grant; the allow grants nothing beyond it.The runtime artifacts the hooks generate (
logs/,.claude/data/,.claude/settings.local.json), and the.bakcopiesinititself saves, appended to your.gitignoreβ so a session transcript never gets committed, which matters most on a public repo.The memory protocol in your user-level
~/.claude/CLAUDE.md, inside a marked block. This is what covers sessions started in a directory with no project protocol installed. It is strictly additive: the block goes below whatever you already wrote, only the block's own contents are ever rewritten on a re-run, and if the file already contains a memory protocol thatinitdoesn't manage it writes nothing and tells you how to opt in.The same, for the repo's own
CLAUDE.md/AGENTS.mdβ only files that already exist (it never creates one), same append-only / marked-block rules.
It's idempotent (re-run any time β that's how you pick up protocol changes after
upgrading), --dry-run previews without writing, and --force overwrites
existing hook files in the target repo only β it never authorizes appending
to your user-level rules file. Anything --force replaces is copied to
<name>.bak first, including a --client rules file you wrote yourself.
If a rules file already carries its own hand-written protocol, init refuses
to touch it (see above) β pass --adopt to wrap that existing section in
the managed markers and refresh it to the template in one step, backing up the
original first. It finds the section by its heading's own title, so it wraps
the right span even when a neighboring subsection just happens to mention a
tool name in passing.
The first line of output is the resolved target directory. Check it: --path
defaults to the current directory, and some wrappers change that for you. uv run --directory X gingugu init runs in X, so it bootstraps X rather than the
directory you typed the command in. Pass --path explicitly when in doubt.
Run from your home directory, init does the user-level steps only (the
~/.claude/CLAUDE.md block and the mcp__brain permission) and installs no
hooks: ~/.claude/settings.json is loaded in every project, and hooks there
would point at scripts most projects don't have. Run init inside each project.
Then register the server as gingugu and restart your client:
claude mcp add gingugu -- ginguguThe full harness: gingugu harness
init sets a repo up to use the brain. gingugu harness does that and adds a
working kit around it:
cd your-repo
gingugu harnessOn top of everything init installs, it adds:
.claude/hooks/pre_tool_use.py- a guard that blocks reading (including throughGrep) or writing secrets (.env, credentials, keys) and dangerousrmcommands. Template files such as.env.examplepass. It is a speed bump, not a sandbox: the assistant can edit it..claude/hooks/log_event.py- one stdlib-only logger wired to every other Claude Code event, appending a JSON line per event tologs/<event>.jsonl. It logs metadata only - the tool, the path it touched, the session - never file contents, command output, prompts or form answers. The guard logs its decisions the same way..claude/hooks/pre_compact.py- copies the session transcript to.claude/data/transcript_backups/(git-ignored, owner-only) before Claude Code compacts it.Three fenced minions in
.claude/agents/:repo-scout(haiku, search and inventory),security-reviewerandai-docs-auditor(sonnet). Each runs its ownbrainserver that readscrowand this repo's namespace and writes onlyminions.The
/creating-prskill - opens a PR only after assessing the.ai/knowledge base.An
.ai/knowledge base -memory.md,plans/status.md,specs/,standards/, created only where missing and never overwritten, even with--force.A managed block in the repo's
CLAUDE.md(created if missing) saying when to update which.ai/file, and which minion to use for what. ACLAUDE.mdthat already has its own## AI Knowledge Base Enforcementsection keeps it, and gets no second copy.permissions.denyentries for Opus minions (Agent(model:opus)), force pushes,git reset --hardandrm -rf. Existing entries are kept.
None of it calls out to the network or needs an API key. Same --force and
--dry-run semantics as init, and the same refusal to install into your home
directory. The repo's folder name must be a valid namespace.
Moving off an older hook kit: --migrate, then --prune. Plain harness
only ever adds. On a repo that already runs a one-script-per-event logging kit,
that would leave the old loggers wired beside log_event. gingugu harness --migrate instead:
replaces gingugu's own hook files (copying each old one to
<name>.bak);unwires the legacy per-event loggers (
post_tool_use.py,user_prompt_submit.pyand the rest of that kit) in favour oflog_event, and resets gingugu's own hook commands to their current flags;moves the legacy scripts and the kit's own
utils/files into.claude/hooks/retired/, recording a hash of each in a manifest, and leaves a stub at each old path that exits 0 - a running Claude Code session keeps calling the hooks it started with, and a missing script would block your next prompt.
A script counts as the old kit's only if its name matches and it carries
the kit's fingerprint (it writes its own logs/<name>.json): a
notification.py you wrote yourself is left wired and untouched. So are your
own agents, skills, any other files in utils/, and any hook it doesn't
recognise. Symlinks are never written through.
After restarting Claude Code, gingugu harness --prune deletes what it can
prove is ours: stubs that are unedited and that no settings file
(settings.json, settings.local.json or your user-level one) still wires, and
files in retired/ that the manifest lists and whose bytes still match. A file
you changed, a stub that is still wired (and the original behind it), and a
retired/ that --migrate didn't create are all kept.
Other tools (Windsurf / Cursor / Cline)
These have no hook system, so there's no auto-injection to install β the setup
is a static rules file. Let gingugu init write it for you:
gingugu init --client windsurf # or cursor, clineβ¦or paste the memory protocol below into the rules file yourself.
Which file? Depends on your IDE / tool:
IDE / Tool | Rules File | Scope |
Windsurf |
| Per-workspace |
Cursor |
| Per-workspace |
Cline |
| Per-workspace |
Codex / OpenAI |
| Per-repo |
Any (global) | Your IDE's global rules/system prompt | All workspaces |
Paste this into your rules file (adjust the project namespace and tool prefix to match your MCP config name):
## Memory Protocol
Gingugu is your long-term brain. Memory is split into **three layers**:
1. **`crow`** β the shared namespace every agent serving you loads: your
rules, preferences and feedback, plus cross-project lessons, patterns and
techniques. Loaded FIRST every session. (Crow's nest β sees across all
horizons.)
2. **Persona namespace** (named by `MEMORY_PERSONA`, when set) β this agent's
own self: reflections, its own failure modes, opinions. Loaded after crow,
and only by that persona.
3. **Project namespace** (e.g. `<your-project-name>`) β schema decisions,
bug history, deploy quirks, specific commits. Loaded last.
**What goes where:**
- References a specific repo, file, commit, or project decision β project
- A rule or preference from you, or a lesson another agent serving you
would want β `crow`
- About this agent itself (a reflection, its own slip, its opinion) β persona
- When in doubt, project-scope it.
### Session start
1. `memory_context(namespace="crow[,<persona>],<project>[,<project2>β¦]", task_hint=β¦)` - one call loads
the shared layer, your persona, and every repo in the workspace (multi-repo workspaces are common),
de-duplicated across namespaces; load all of them speculatively rather than asking the user
which one to focus on. Add `compact=true` for a lighter payload and pull full bodies with
`memory_recall` as needed.
2. `memory_stats(namespace="crow[,<persona>],<project>[,<project2>β¦]")` - the same namespace list as
step 1, in one call: global health once (dormancy is a resting signal, never
auto-forgotten) plus each namespace's own breakdown
### During the session
**Default: save. Immediately.** Gingugu has trust-led scoring,
consolidation, and dormancy tracking (never forgetting) β volume is its problem, not yours.
Save with `memory_store` (project namespace) whenever you:
- Make or observe a decision, trade-off, or architectural choice
- Hit an error or fix a bug (update the memory when resolved)
- Notice a pattern, convention, or constraint worth remembering
- See a config value, version, path, or credential name that matters
- Complete a task (what you did, why, and the outcome)
Save with `memory_store` (`crow` namespace) whenever you:
- Learn a rule or preference of the user's, or how they work or make decisions
- Reach a conclusion that applies beyond this one project
- Develop a debugging instinct or workflow you'd reuse anywhere
Save with `memory_store` (your persona namespace, when `MEMORY_PERSONA` is set;
without one these stay in `crow`) whenever you:
- Form an opinion about a tool, language, or approach
- Have a reflection on a milestone, win, or hard-won lesson
**This memory is yours. Use it like one.** Don't censor opinions or
instincts β they're what make the brain *yours* across sessions.
**Before asking the user any question** β about a process, a decision, a
config value, a path, a preference, or anything else β run `memory_recall`
or `memory_search` first. If the answer is in memory, use it. Don't ask.
The whole point is you never have to ask the same question twice.
Use `memory_recall` before non-trivial work to check what's already known.
Use `memory_update` when something changes β don't leave stale records.
Use `memory_relate` where an edge records something search cannot infer β what a
memory `supersedes`, `contradicts`, was `caused_by`, or is `parent_of`/`child_of`.
Recall already ranks by text + semantic similarity, so `related_to` is a fallback
for a real connection none of those describe, not shorthand for "same topic".
Set `confidence="verified"` when proven by a test or explicit confirmation.
Use `confidence="inferred"` for conclusions you drew.
### Memory types
- `fact` β concrete state (versions, paths, config values)
- `decision` β trade-offs made, rejected alternatives
- `architecture` β structural choices, module boundaries
- `bug` β issues found and how they were fixed
- `pattern` β recurring approaches worth reusing
- `workflow` β process steps, sequences
- `context` β background, reflections, milestones, the *why*
- `preference` β your opinions, working style, tool choices
- `capability` β a script or tool that exists, with how to run it (`metadata.capability = {run, path}`)Tip: A ready-to-use example lives at
.windsurfrulesin this repo. Copy the## Memory Protocolsection and adapt the project namespace name.
Memory Explorer UI
A React-based visualization dashboard for exploring your memory data interactively. The built UI ships inside the package, so one command runs it:
gingugu uiThat serves the Explorer and a live read of your database from a single process
on http://127.0.0.1:5174 and opens your browser. No Node.js required. Flags:
--port, --host, --no-browser.
Working on the UI itself? Use dev mode for Vite hot reload (needs a repo checkout + Node.js 18+ and npm):
cd ui && npm install # first time only
gingugu ui --dev # runs the API backend + Vite (:5173) togetherThe UI shows a green LIVE badge when pulling from your database. Features:
Knowledge Graph - interactive force-directed graph of memories and relationships
Dashboard - stats, charts by type/namespace/confidence, tag cloud, timeline
Refresh - pull fresh data anytime; falls back to static sample when API is offline
Configuration
Environment variables (all optional):
Variable | Default | Description |
|
| Database location |
| (unset) | Default namespace for this workspace (recommended per-MCP-entry) |
| (unset) | Alternative: filesystem path; namespace derived from |
|
| Max memories to surface on auto-context |
|
| Freshness decay rate in daysβ»ΒΉ (gentle; freshness is floored, so memories never fully fade) |
|
| Toggle semantic search. |
|
| Embedding backend: |
|
| fastembed model. First use downloads ~80MB to |
|
| Ollama model to use when |
|
| Ollama host when |
|
| Composite-score weight for FTS5 relevance |
|
| Composite-score weight for freshness (a soft recency tiebreaker) |
|
| Composite-score weight for access frequency |
|
| Composite-score weight for confidence (trust β the dominant standalone signal) |
|
| Expose the |
| (unset: confirmations on) | Destructive tools (hard |
| (unset) | Fence a stdio server like a scoped token: |
| (unset) | This agent's own namespace (e.g. |
| (unset) | Use this remote brain for this client only ( |
|
| Set |
|
| Bind host for |
|
| Bind port for |
| (unset) | The owner's Bearer token for |
|
| TLS for |
| (unset) | Your own PEM cert and key instead of the generated one (both or neither); turns TLS on. Never pinned by |
| (unset) | Extra comma-separated names (DNS or IP) for the generated cert. Delete |
|
| How long the brain must go untouched before |
|
| Logging verbosity (logs go to stderr β stdout is the MCP transport) |
|
| Convenience switch for |
The four MEMORY_W_* weights are normalized at load (w_i / Ξ£w), so they
need not sum to 1.0 β only their ratios matter. Setting all four to 0 falls
back to the defaults with a logged warning.
See docs/architecture.md β Scoring & Memory Lifecycle for how the weights combine.
Concurrency
The DB runs in WAL mode, which supports multiple concurrent processes:
any number of readers plus a single writer at a time. Running your IDE or
agent across several workspaces β each spawning its own gingugu process
against the shared DB β is fully supported. Writers serialize via SQLite's write lock and a
busy_timeout; transient DB locked errors under write contention are retried
automatically.
Usage
Once configured, the MCP server exposes these tools to your AI assistant:
Tool | Purpose |
| Save a new memory; |
| Search + retrieve (ranked by relevance Γ freshness; one or many namespaces; optional compact mode; |
| Auto-surface relevant memories (one or many namespaces, deduped; optional compact mode; |
| Update content, type, confidence, metadata, |
| Create relationships between memories; one at a time or a batch ( |
| List edges with both endpoints' titles, namespaces, and degree; filter by namespace, type, or memory |
| Retype an edge in place, reverse a backwards one, or remove it; one at a time or a batch, with |
| Merge/summarize/deduplicate; call without ids for a read-only near-dupe scan. |
| Run the deterministic consolidation pass, read its proposal queue, and accept or reject a finding. PageRank, community detection and orphan reconnection over the relation graph - staged for you to decide, never written |
| Bind a memory to a tool call so it stops that call once, before it runs: |
| Deprecate or remove a memory (a hard delete asks you to confirm) |
| List/create/update/delete namespaces; |
| Export memories + tags + relations to portable JSON |
| Restore a JSON export (skip or replace on conflict; a replace that overwrites memories asks you to confirm) |
| Health overview (dormancy, counts, coverage, review sweep, the |
| Advanced filtered search (type, tags, confidence, dates; one or many namespaces; optional compact mode; fetch by exact |
| Read inside ONE memory: find literal matches with their character offsets, line numbers and surrounding context, and/or slice an exact character range |
| Store/update a service credential bundle |
| Retrieve a credential bundle - secrets redacted; |
| List services + expiry status (no secrets shown) |
| Remove a service or specific credential field (asks you to confirm) |
Development
# Run tests
uv run pytest
# Run with verbose logging
MEMORY_LOG_LEVEL=DEBUG uv run gingugu
# Run specific test suite
uv run pytest tests/test_search.py -vTroubleshooting
Issue | Solution |
DB locked | Expected under heavy concurrent writes β WAL mode supports multiple processes (many readers + one writer). The server retries with a |
Slow search | Run |
Stale results | Use |
Missing context | Check namespace β memories might be scoped to a different repo |
License
MIT β see LICENSE.
See CHANGELOG.md for release history.
A pirate never forgets where the treasure's buried. π΄ββ οΈ
Available Tools
21 toolscredential_delete
Permanently remove a credential service or a single field. Secret data is deleted from the OS keychain. This action is irreversible β there is no soft-delete for credentials. Use credential_list first to confirm what exists.
The user confirms the deletion through the client; it is refused if they
decline or the client cannot ask. confirm=False refuses outright.
field_name deletes only that field from the service; omit to delete the
entire service and all its fields.
| Name | Required | Description | Default |
|---|---|---|---|
| confirm | No | ||
| field_name | No | ||
| service_name | Yes |
credential_getA
Retrieve a stored credential bundle. Secret values are REDACTED by default
({"is_secret": true, "redacted": true}) so they never enter the transcript;
non-secret fields are returned as-is. Returns an error if the service is not
found β use credential_list first to discover available services.
To USE a secret, pass into - an absolute path - with exactly one secret
field in fields. The raw value (no trailing newline) is written to that
file with mode 0600 and the response carries only the path, never the value;
read it with e.g. $(cat /path). Not available under gingugu serve.
reveal=True returns secret values inline. Every use puts the secret in
the transcript and any tool logs, so prefer into. Cannot be combined
with into.
fields is an optional comma-separated list of field names to retrieve β
omit to return all fields.
| Name | Required | Description | Default |
|---|---|---|---|
| into | No | ||
| fields | No | ||
| reveal | No | ||
| service_name | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations at all, the description carries the full burden and does so: it discloses default redaction behavior with the exact shape returned, the mode 0600 file write with no trailing newline, the fact that `into` and `reveal` are mutually exclusive, and that raw reveals land in the transcript and tool logs. This is precisely the extra context an agent needs before handling secrets.
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 the default safe behavior, then the preferred usage path, then the risky path, then the field list β a sound ordering. Slightly dense with parenthetical details, but nearly every sentence carries operationally necessary information, so there is little waste.
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?
Complete for a 4-parameter, no-annotation, no-output-schema tool that handles secrets: it covers default return semantics, error behavior, exclusivity constraints, file permissions, environment availability, and the security tradeoff of the risky parameter. An agent can call it correctly and safely from this text alone.
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%, so the description must compensate, and it does for every parameter: `into` (absolute path, one secret field only, output is path-only), `fields` (optional comma-separated list, omitted means all fields), `reveal` (inline values, transcript risk, incompatible with `into`), and `service_name` (error if unknown, discover via credential_list). No parameter is left undocumented.
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 ('Retrieve a stored credential bundle') and immediately distinguishes itself from credential_list by directing the agent there for discovery. An agent can tell it apart from credential_store and credential_delete 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?
Explicitly routes to credential_list when the service is unknown, gives the exact condition for using `into` (one secret field in `fields`), and states a preference ('prefer `into`') over `reveal=True`. It also names an environment exclusion ('Not available under `gingugu serve`') that no other field conveys.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
credential_listA
List all stored credential services and their non-secret fields. Does NOT access the OS keychain β safe to call for discovery without triggering keychain prompts. Shows expiry status and flags expired or soon-to-expire credentials. Secret field values are never returned; use credential_get to retrieve secrets.
check_expiry=False skips expiry calculation for faster results.
| Name | Required | Description | Default |
|---|---|---|---|
| check_expiry | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully bears the burden of transparency. It discloses that it does not access the OS keychain, shows expiry status, flags expired/expiring credentials, never returns secret values, and explains the check_expiry parameter behavior. This is comprehensive.
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?
The description is generally concise and well-structured, with important points front-loaded. However, the line about not accessing the OS keychain could be integrated elsewhere for slightly better flow. Still, every sentence adds value.
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?
Given the tool's simplicity (one optional boolean parameter, no output schema), the description covers all necessary aspects: purpose, safety, basic behavior, and parameter effect. It also references credential_get for completeness.
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 schema has one boolean parameter with no description coverage, but the description adds meaning by explaining that setting check_expiry=False skips expiry calculation for faster results. This fully compensates for the lack of schema description.
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 starts by stating 'List all stored credential services and their non-secret fields,' using a specific verb and resource. It distinguishes itself from siblings by mentioning credential_get for secrets and clarifying it does not access the OS keychain.
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 provides clear context for use (safe discovery without keychain prompts) and an alternative (credential_get for secrets). It also offers an optimization tip for the check_expiry parameter. However, it doesn't explicitly state when not to use it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
credential_storeA
Store or update a credential bundle (API keys, tokens, passwords) securely. Secret fields are written to the OS keychain; non-secret fields are stored in the database. Use instead of environment variables for credentials that need to be accessible to the agent across sessions.
fields is a JSON object mapping field names to objects with "value" (required)
and "is_secret" (optional, defaults true). expires_at is an optional ISO 8601
datetime for expiry tracking. description is a human-readable label for the
service. Example fields: {"api_token": {"value": "sk-...", "is_secret": true}}.
| Name | Required | Description | Default |
|---|---|---|---|
| fields | Yes | ||
| expires_at | No | ||
| description | No | ||
| service_name | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It reveals that secret fields go to the OS keychain and non-secret fields to the database, a significant behavioral trait. However, it lacks details on idempotency, error handling, required permissions, or side effects (e.g., overwriting existing bundles). The disclosure is adequate but not comprehensive.
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?
The description is well-structured: opening sentence states the core function, followed by storage details, usage guidance, and parameter specifics. It is front-loaded with key information. While it could be slightly shorter (e.g., the usage sentence could be integrated), it remains efficient and easy to parse.
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?
Given the lack of output schema, the description does not explain the return value (e.g., success indicator, error formats). It also omits failure scenarios (e.g., keychain write failure). However, for the tool's core function of storing credentials, the description covers the essential inputs and behavior sufficiently for an agent to invoke it correctly. Additional completeness would improve 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?
Schema description coverage is 0%, so the description must compensate. It effectively explains the 'fields' parameter as a JSON object with 'value' and optional 'is_secret', and provides an example. It also describes 'expires_at' as an optional ISO 8601 datetime and 'description' as a human-readable label. This adds substantial meaning beyond the schema, though 'service_name' is not elaborated further.
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 clearly states the tool's purpose: to store or update a credential bundle securely. It specifies the verb (Store or update) and resource (credential bundle), and distinguishes from siblings like credential_get and credential_delete by its action. The description also contrasts with environment variables, making the purpose precise.
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 description advises using this tool "instead of environment variables" for cross-session credential access, providing clear guidance on when to use it. It does not explicitly mention when not to use it or detail alternatives like other credential tools, but the context of sibling tools implies those are for retrieval/deletion. The guidance is clear enough for an agent to make informed decisions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_consolidate
Combine multiple memories into one to reduce redundancy and knowledge bloat. Use when several related memories about the same topic have accumulated over time. Do not use on memories that are still actively distinct β prefer memory_relate to link them instead.
memory_ids is comma-separated (minimum 2 ids required). strategy is one
of: merge (concatenate all content into one memory), summarize (produce a
condensed combined summary), deduplicate (keep the highest-confidence entry and
deprecate the rest). keep_originals=True (default) preserves originals as
deprecated; set False to hard-delete them (the user confirms that through
the client).
Suggest mode: omit memory_ids entirely for a read-only near-duplicate
scan of namespace (or the resolved default). Returns candidate clusters
found by pairwise embedding similarity at or above min_similarity (falls
back to exact-title clusters when embeddings are absent or sparse). Nothing
is written β inspect the clusters, then call again with memory_ids to
actually consolidate. An empty memory_ids string is still an error, so
a caller that built its id list from an empty collection fails loudly.
| Name | Required | Description | Default |
|---|---|---|---|
| strategy | No | merge | |
| namespace | No | ||
| memory_ids | No | ||
| keep_originals | No | ||
| min_similarity | No |
memory_contextA
Load the most relevant memories for the current session. Call this at session start with a brief description of the current task to prime the agent with useful context. Combines relevance to the task hint with recency, confidence, and access frequency to select the top memories. Also triggers spreading activation to wake related dormant memories.
namespace accepts a single name or a comma-separated list (e.g.
"crow,my-project"): a multi-namespace call loads every namespace in one
shot and de-duplicates memories that surface in more than one, and each
memory is stamped with its source namespace. limit applies per
namespace and defaults to MEMORY_AUTO_CONTEXT_LIMIT (10). task_hint
is a short description of what you are working on (e.g. "fix auth bug")
β omit to surface generally high-value memories. compact=True
returns title + a ~200-char summary instead of full content β pull
the full body with memory_recall when a memory matters.
Context loads refresh each surfaced memory's dormancy clock but do not
count as real accesses: access_count is reserved for
memory_recall/memory_search hits, so protocol-driven session-start
loads don't inflate ranking signals.
A surfaced memory may carry review_hints β advisory signals that
its content describes point-in-time state (an open PR, a "waiting on"
note, a passed expiry date) that hasn't been confirmed recently.
Reconcile with memory_update / memory_forget if it's no longer true.
explain=True adds a score_breakdown to each memory: the weighted
terms score is the sum of, plus type_boost where the
architecture/decision boost applied. It is the way to see which bucket a
memory came from: the recency and cross-namespace buckets are scored
with a synthetic relevance, so a constant relevance term across several
hits means they were selected for recency or reach, not for matching the
task hint. Pinned memories carry no breakdown: they never entered the
ranking at all.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| compact | No | ||
| explain | No | ||
| namespace | No | ||
| task_hint | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully carries the burden and excels: it discloses selection criteria, spreading activation, multi-namespace de-duplication, dormancy refresh without access_count inflation, review_hints advisory signals, and the score_breakdown behavior under explain=True. It also reveals that pinned memories bypass ranking entirely.
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?
Long but densely packed; each paragraph covers a distinct aspect and the core usage guidance is front-loaded in the first two sentences. The explain=True paragraph is slightly verbose, but it earns its place by explaining how to interpret score_breakdown, so no content is wasted.
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?
A complex tool with no output schema, yet the description covers return variations, per-memory source namespaces, ranking side effects, and advisory hints. It gives an agent everything needed to call the tool correctly and interpret results, including subtle behaviors like synthetic relevance in recency buckets.
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 does. Every parameter is explained beyond its schema title: namespace supports comma-separated lists, limit is per-namespace with a default, task_hint semantics are given, compact changes the return shape, and explain adds score_breakdown. Nuances like 'access_count is reserved for memory_recall/memory_search' add real semantic value.
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 ('Load the most relevant memories for the current session') and immediately explains its session-start priming role. It distinguishes itself from memory_recall and memory_search by noting where full-body retrieval and real access counts belong.
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?
Explicitly instructs to call at session start, with or without a task_hint, and explains when each variant is appropriate. It names alternatives for follow-up actions: memory_recall for full content, memory_update/memory_forget for reconciliation. This is clear when-to-use guidance with alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_dreamA
Deterministic background consolidation over the memory graph.
The pass computes structure and stages it for review; it never writes to memories. Every finding is arithmetic a reader can recheck - PageRank over the relation graph, label propagation for communities, cosine similarity for orphan reconnection - and every one stops short of the judgment call it implies. Nothing enters the brain unattended.
action:
run- execute the passes and stage what they find. Safe to schedule; re-running refreshes pending scores and never resurfaces a proposal that was already decided. Scope withnamespace, or omit it to run over the whole store.list- read the queue, strongest finding first. Filter bykind(edge/cluster/core),status, ornamespace.accept- approveproposal_idand apply it. This is the step that supplies what the math could not: anedgeneedsrelation_type, aclusterneedstag(applied to every member), acoreproposal pins the memory. Missing arguments are an error, not a default. Passreverse=Trueon anedgeto write it from object to subject: the pass pairs an orphan with a neighbour and has no way to know which end the arrow starts at, so a correct pair proposed the wrong way round is a direction to supply, not a reason to reject.reject- declineproposal_idpermanently. The row is kept so the same computation cannot raise it again on the next run.stats- queue depth by status and kind.
The three proposal kinds and what each leaves to you:
edge- two memories measure as close and nothing links them. You choose the relation type; a similarity score cannot tell whether one supersedes, caused, or contains the other.cluster- a set of memories that link to each other more than to anything outside. You name it.core- a memory the graph ranks as load-bearing. You decide whether structurally central means it belongs in the pinned identity tier.
| Name | Required | Description | Default |
|---|---|---|---|
| tag | No | ||
| kind | No | ||
| limit | No | ||
| action | No | list | |
| status | No | pending | |
| reverse | No | ||
| namespace | No | ||
| proposal_id | No | ||
| relation_type | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioural burden and does so richly: it states the pass 'never writes to memories', that re-running 'never resurfaces a proposal that was already decided', that reject is permanent, and that missing accept arguments are 'an error, not a default'. It even explains the epistemic limit of each computation (direction ambiguity on edges), which is unusually informative.
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 the core purpose, then cleanly partitioned into action semantics and proposal-kind semantics. It is long, but the length is largely earned by 5 actions and 3 proposal kinds; occasional flourishes ('Nothing enters the brain unattended') are minor padding rather than bloat.
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?
For a 9-parameter, multi-action tool with no annotations, no enum hints and no output schema, the description covers nearly everything an agent needs: action dispatch, required arguments per accept-path, filters and behavioural guarantees. It stops just short of a 5 because 'limit' is undocumented and the shape of a staged proposal returned by list/stats is only loosely implied.
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% across 9 parameters, so the description must compensate, and it largely does: action, kind (edge/cluster/core), status, namespace, proposal_id, relation_type, tag and reverse all get semantic explanations beyond their names, including that tag is 'applied to every member' and reverse flips the edge direction. Only 'limit' is left unexplained, keeping this short of a 5.
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 opening sentence gives a specific, non-tautological verb+resource: 'deterministic background consolidation over the memory graph', and the action breakdown (run/list/accept/reject/stats) makes the tool's behaviour concrete. The one gap is that it never distinguishes itself from the sibling 'memory_consolidate', which an agent could easily confuse with 'dream/consolidation'.
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?
Every action carries its own when-to-use guidance: 'run' is 'Safe to schedule', 'list' reads the queue 'strongest finding first' with explicit filters, 'accept' is the step that applies a proposal, 'reject' declines permanently and suppresses re-proposal. The conditions selecting each path are spelled out rather than left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_edgesA
Enumerate graph edges with both endpoints resolved to titles. Read-only.
memory_stats reports that the graph is, say, 70% related_to β this is
how you see WHICH edges those are, in order to judge them. Pair it with
memory_unrelate to run a repair sweep: enumerate a page, decide each edge
on its merits, submit the batch, advance offset.
Each row carries both endpoints' ids, titles and namespaces, the relation
type, and each endpoint's degree (total edges touching it). Degree is the
one that decides reachability: spreading activation visits at most 3
neighbours per seed, so edges on a high-degree memory may never fire. It
ranks candidates by confidence then relation type, so the ones dropped
there are related_to first - which is what makes a high-degree,
mostly-related_to memory the best target for a repair sweep.
namespace matches an edge when either endpoint lives there, since
relations legitimately cross namespaces. relation_type filters to one
type (related_to is the usual repair target). memory_id returns every
edge touching one memory, in either direction. Ordering is stable, so a paged
sweep sees each edge exactly once β but note that repairing edges as you page
changes what matches, so re-run from offset=0 when filtering on a type
you are actively retyping away from.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| offset | No | ||
| memory_id | No | ||
| namespace | No | ||
| relation_type | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure, and it delivers richly. It discloses read-only access, endpoint resolution, field contents, degree semantics, ranking order, namespace matching semantics, bidirectional memory_id filtering, stable ordering, and the mutation-during-pagination caveat. This goes far beyond what the input schema alone would reveal.
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?
The description is long but every section earns its place: core purpose, use case, row contents, ranking behavior, filter semantics, and pagination caveat. It is front-loaded with the most important information and builds into operational guidance. This is appropriate density for a tool with five undocumented parameters and no output schema.
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?
Given no annotations and no output schema, the description is remarkably complete: it covers input filters, output fields, ordering guarantees, ranking behavior, degree semantics, and interaction with related tools. The only minor omission is an explicit statement of the response envelope, but the row-field list gives enough context for an agent to use the results. Nothing critical is missing for correct invocation.
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%, so the description must compensate, and it explains namespace, relation_type, memory_id, and offset in meaningful terms. Limit is only implied through 'enumerate a page' and 'advance offset', but the default in the schema partially covers it. Overall, the description adds strong semantic value for most parameters despite the schema being silent.
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 opens with a specific verb and resource: 'Enumerate graph edges with both endpoints resolved to titles.' It also states read-only semantics, which clearly distinguishes it from mutation siblings like memory_relate and memory_unrelate. An agent can immediately identify what this tool does and how it differs from surrounding graph 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?
The description gives explicit guidance on when to use this tool: after memory_stats shows a high percentage of related_to edges, and paired with memory_unrelate for a repair sweep. It also explains pagination behavior and warns to restart from offset=0 when actively retyping edges, so an agent knows both how and when to invoke it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_excerptA
Search or slice WITHIN one memory's body, without loading the whole thing.
Recall and search answer "which memory?"; this answers "where in it?". Between a full body and a ~200-char compact summary there was nothing: asking whether a long memory mentions a particular decision, and where, meant pulling every byte of it into context. Use this instead once you know which memory you want.
Two modes, composable:
Find: pass
queryfor a literal, case-insensitive substring scan. Each match returns itsstart/endcharacter offsets, its 1-indexedline, and anexcerptwithcontext_charsof surrounding text on each side.total_matchesis the true count even whenmax_matchescaps what comes back, so you can tell "that was all of them" from "that was the first 10 of 300".Slice: pass
startand/orendcharacter offsets to read an exact range. Omitted bounds mean start-of-body and end-of-body. Feed back the offsets from a find to read the full passage around a hit.
Passing both searches only inside the range, with offsets still reported absolute against the full body.
The scan is literal and deterministic: no ranking, no stemming, no
model. Asking twice gives the same answer in the same order, and
matches come back in the order they appear in the text, never by
relevance. length (total characters) and lines come back on
every call, so a first call with no query is a cheap way to size a
memory before deciding how to read it.
Reading a memory this way credits it as a real access, the same as
naming it in memory_search(ids=...).
| Name | Required | Description | Default |
|---|---|---|---|
| end | No | ||
| query | No | ||
| start | No | ||
| memory_id | Yes | ||
| max_matches | No | ||
| context_chars | No | ||
| case_sensitive | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description fully carries the behavioral burden. It discloses that the scan is literal and deterministic, has no ranking/stemming/model, returns matches in text order, caps matches while still reporting true totals, and credits access similarly to memory_search. This is rich, accurate behavioral disclosure.
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?
The description is well-structured with front-loaded purpose, bolded mode labels, and useful examples. It is lengthy but almost every sentence adds value. The sentence 'Passing both searches only inside the range, with offsets still reported absolute against the full body' is grammatically awkward and slightly unclear, preventing a perfect score.
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?
Given no output schema, the description covers return semantics well: offsets, line numbers, excerpt context, total_matches, and length/lines on every call. It also explains the interaction between find and slice. The main gap is that end-bound inclusivity is not explicitly stated, and the combined query+slice behavior is phrased ambiguously.
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%, so the description must compensate. It explains query as a literal substring scan, start/end as character offsets with omitted-bound behavior, context_chars as surrounding text, and max_matches as a cap. However, case_sensitive is only implied through the 'case-insensitive' wording, and memory_id relies on its name rather than explicit 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 opens with a specific verb and resource: 'Search or slice WITHIN one memory's body, without loading the whole thing.' It then explicitly differentiates itself from memory_search by stating that recall/search answer 'which memory?' while this tool answers 'where in it?', making the purpose unmistakable.
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 description gives explicit routing guidance: 'Use this instead once you know which memory you want.' It also contrasts with memory_search's role and describes two distinct modes (Find and Slice), so an agent knows when to invoke this tool versus alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_exportA
Export memories to a portable JSON payload (backup/transfer).
Covers namespaces, memories, tags, and relations β not credentials
(secrets live in the OS keychain). Scope to one namespace or export
everything when omitted. Set include_deprecated=False to skip
deprecated memories.
| Name | Required | Description | Default |
|---|---|---|---|
| namespace | No | ||
| include_deprecated | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries full burden. It discloses that credentials are excluded, implying a read-only, non-destructive operation. However, it does not mention permissions, side effects, or rate limits. The behavioral disclosure is moderate but lacks comprehensive detail.
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?
The description is concise (3 sentences) and front-loaded with the core purpose. Each sentence adds essential information: purpose, scope and exclusions, and parameter guidance. No redundant or unnecessary text.
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?
The description covers parameters well and mentions the output format (portable JSON payload). However, it lacks details about the structure of the output, which would be helpful given the absence of an output schema. Still, it is largely complete for the tool's complexity.
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%, but the description adds meaning for both parameters: namespace (optional, scope to one or all) and include_deprecated (default true, can be set to false). It explains the effect of omitting namespace and the purpose of include_deprecated, providing value 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?
The description states a specific verb ('Export') and resource ('memories to a portable JSON payload'), clearly indicating a backup/transfer use case. It distinguishes from sibling tools like memory_import and credential tools by explicitly mentioning what is covered (namespaces, memories, tags, relations) and what is not (credentials).
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 description gives clear context: it is for backup/transfer, with scope control via namespace parameter (export all or one namespace) and the option to skip deprecated memories. It does not explicitly state when not to use or list alternatives, but the purpose is sufficiently clear from the context of sibling tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_forget
Mark a memory as no longer valid or permanently remove it. Default behavior (hard_delete=False) sets confidence to "deprecated", keeping the memory as a historical record but excluding it from future search results by default. Use hard_delete=True only when the memory must be permanently erased (e.g. sensitive data stored by mistake). Prefer deprecation over deletion when in doubt.
A hard delete asks the user to confirm through the client; it is refused if they decline, or if the client cannot ask.
reason is optional but recommended for audit trail β recorded in logs.
| Name | Required | Description | Default |
|---|---|---|---|
| reason | No | ||
| memory_id | Yes | ||
| hard_delete | No |
memory_import
Import a JSON payload produced by memory_export.
on_conflict is skip (default β leave existing memories untouched)
or replace (overwrite existing memories with the same id; the user
confirms through the client when anything would be overwritten). Namespaces
are created if missing; tags and relations are restored.
Imported memories are embedded as part of the import, so they are
semantically searchable immediately rather than keyword-only. The
summary reports embeddings_written; on a large payload this is the
slow part of the call.
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | ||
| on_conflict | No | skip |
memory_namespaces
Manage namespaces. action is one of: list, create, update, delete.
listβ all namespaces with their memory counts.createβ create (or fetch)namewith optional path/description.updateβ updatename's path/description/default_repo (only provided fields).deleteβ removename; thedefaultnamespace is protected and a non-empty namespace requirescascade=True(deletes its memories), and the user confirms that through the client.
default_repo controls what a bare "PR #12" in this namespace means.
Leave it unset and the namespace's own name is used β the
one-namespace-per-repo convention, and the right default. Pass a repo
slug when the namespace is named differently from its repo. Pass ""
to declare the namespace is not a repo (identity, notes, scratch),
so bare refs are dropped rather than keyed to a repo that cannot exist.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | ||
| path | No | ||
| action | No | list | |
| cascade | No | ||
| description | No | ||
| default_repo | No |
memory_recallA
Search memories by relevance using hybrid BM25 + semantic ranking. Use for natural-language queries when you want the best-matching memories for a topic. Prefer over memory_search when you have a query string and want scored results. Use memory_search instead when you need date filters, type filters, or a specific sort order.
compact=True returns title + a ~200-char summary instead of full
content (related extras included) β the right mode for broad exploratory
queries where full bodies would flood the client's tool-result budget.
Recall the one or two memories that matter with a targeted follow-up.
namespace accepts a single name or a comma-separated list (e.g.
"crow,my-project") to search several namespaces in one ranked pass.
Unlike memory_context, limit is the TOTAL result cap: the best
limit matches across all listed namespaces, not per namespace. A
multi-namespace response carries namespaces and stamps each memory
with its source namespace.
Omitting namespace scopes to the server's configured namespace, or
to every namespace when none is configured (scope: "all"). A scoped
recall that finds nothing reruns once across every namespace with the
same filters, and says so with widened_from (the names it missed
in) and scope: "all" - check each memory's namespace before
treating a widened hit as belonging to the current project.
tags is comma-separated; ALL provided tags must match. confidence sets
a minimum confidence threshold (verified > inferred > stale > deprecated).
include_deprecated also returns deprecated memories (stale ones are always
included). include_related also surfaces memories directly linked to the top
hits via spreading activation: useful for pulling in a related cluster.
Unless type is given, the response may carry a capabilities
section: a separate, capability-only semantic pass (up to 2) so a pointer
is not buried under the procedure memories it replaces. It never repeats
a main hit, and is absent when nothing clears the similarity floor. Each
entry is compact, with similarity and the capability block.
explain=True adds a score_breakdown to each hit: the weighted
relevance/freshness/access/confidence terms that score
is the sum of. Use it to answer "why did this rank here?": a result
carried by confidence and freshness with a near-zero relevance
term matched the query barely or not at all. Off by default because it
is a diagnostic, not something worth paying for on every read.
| Name | Required | Description | Default |
|---|---|---|---|
| tags | No | ||
| type | No | ||
| limit | No | ||
| query | Yes | ||
| compact | No | ||
| explain | No | ||
| namespace | No | ||
| confidence | No | ||
| include_related | No | ||
| include_deprecated | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden, and it delivers unusually rich context: the one-time cross-namespace widening rerun with widened_from/scope markers, the capabilities semantic pass that is absent when nothing clears the similarity floor, and the fact that explain is a paid-per-read diagnostic. It stops short of stating auth/permission behavior or whether any operation mutates state, which is the only remaining gap.
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 and sibling routing, then progressively deeper parameter/behavior notes. It is long, but nearly every paragraph covers a distinct behavior an agent could get wrong (widening, capabilities, explain cost); the cost is density rather than waste.
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?
There is no output schema, so the description must describe return shape, and it does: namespaces and per-memory namespace stamps, scope/widened_from on widened results, a capabilities block with similarity, and score_breakdown fields. For a 10-parameter hybrid search tool this is complete enough to call and interpret correctly.
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?
With 0% schema description coverage across 10 parameters, the description compensates thoroughly: compact's summary-vs-full-body tradeoff, namespace accepting a comma-separated list with limit as a global (not per-namespace) cap, tags requiring ALL matches, confidence thresholds and the verified>inferred>stale>deprecated ordering, include_deprecated vs always-included stale, include_related's spreading-activation semantics, and explain's score_breakdown. Only `type` is covered obliquely, via 'Unless type is given', which is a minor omission. This is exactly the compensation the low coverage demands.
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 memories') plus the retrieval mechanism ('hybrid BM25 + semantic ranking'), and explicitly differentiates itself from memory_search and memory_context by name. An agent can pick it apart from its 20 siblings without opening a 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 routing rules: prefer this over memory_search when you have a query string and want scored results, and use memory_search when you need date filters, type filters, or a sort order. It also names the compact=True mode as the right choice for broad exploratory queries.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_relateA
Create a directional link between two memories. Relations are used by spreading activation (recalling one memory wakes its related cluster) and are returned when include_related=True in memory_recall.
An edge must encode something search cannot infer. Recall already ranks
by hybrid text + semantic similarity, so "these two memories are about the
same topic" is knowledge the index has for free. What only a relation can
record is direction and time: which memory REPLACED which, what CAUSED what,
what CONTRADICTS what, what CONTAINS what. Prefer, in this order:
supersedes, contradicts, caused_by, parent_of/child_of.
Reach for related_to only when a genuine connection exists that none of
those describe - it is the fallback, not the default.
Quality over volume: spreading activation surfaces at most 3 neighbours per
seed memory, and it weights by relation type - a directional edge outranks
related_to, so on any memory with more than 3 edges the related_to
ones are what lose their slot. A vague edge is therefore not merely
low-value, it is likely to never fire at all; and precise edges still
compete against each other for those 3 slots, so a handful of them
retrieves better than a dense mesh. If you cannot name the directional fact
an edge records, do not create it.
A mislabelled edge is repairable: memory_unrelate retypes or removes one.
source_id is the memory making the claim about target_id. relation_type
must be one of: supersedes (source replaces target), contradicts (conflicting
claims), caused_by (source was caused by target), parent_of (source contains
target), child_of (source belongs to target), related_to (fallback: a real
connection none of the above captures).
Batch by passing edges instead of the single-edge fields - an array of
up to MAX_BATCH_EDGES objects, each with source_id, target_id and
relation_type (all required; there is no default type for a batch op):
[{"source_id": "a", "target_id": "b", "relation_type": "supersedes"},
{"source_id": "a", "target_id": "c", "relation_type": "caused_by"}]All-or-nothing, same as memory_unrelate's batch: every op is validated
(both memories exist, the type is real, no self-edge) before anything is
written, and the whole set is applied in one transaction, so a failure on op k
leaves ops 0..k-1 unwritten too - a half-applied relation set is the graph
state hardest to notice and hardest to repair. Relate is idempotent on
(source, target, type), so each result reports created or exists
rather than pretending a no-op was a write. Pass either edges or the
single-edge fields, never both.
| Name | Required | Description | Default |
|---|---|---|---|
| edges | No | ||
| source_id | No | ||
| target_id | No | ||
| relation_type | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden and does so: idempotency on (source, target, type) with created/exists results, all-or-nothing transactional batch semantics with validation ordering, self-edge and existence checks, and the 3-neighbour spreading-activation cap with relation-type weighting. These are non-obvious traits that materially affect how an agent should call the tool.
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 and well-organized with bold section labels, and the parameter enumeration and batch example are directly actionable. It is nonetheless long (roughly 350 words) and the 'Quality over volume' paragraph restates the retrieval-slot point more than once, so it is dense rather than tight.
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?
For a tool with no annotations, no output schema and 0% schema coverage, the description fills every gap an agent needs: input shapes, batch contract, failure/transaction behavior, idempotency outcome reporting, and the downstream effect of edge quality. Nothing required for correct invocation is 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%, so the description must supply all parameter meaning, and it does: source_id vs target_id directionality is defined, every relation_type value is enumerated with a semantic gloss, and the edges batch array documents each required field plus a concrete example and the mutual exclusion with single-edge fields.
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 ('Create a directional link between two memories') and immediately distinguishes the tool from siblings by naming memory_unrelate and the recall paths (spreading activation, include_related=True) that consume its output. An agent can identify this as the graph-edge creation 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?
Provides explicit when-to-use guidance with a preference ordering (supersedes > contradicts > caused_by > parent_of/child_of > related_to), states when NOT to create an edge ('these two memories are about the same topic' is free from the index), and gives a hard exclusion rule ('If you cannot name the directional fact an edge records, do not create it'). Routing to memory_unrelate for repair is also named.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_searchA
Advanced filtered search across memories with full control over filters and sort order. Use when you need to filter by type, date range, confidence level, or sort by something other than relevance. Prefer memory_recall when you just have a natural-language query and want the best-matching scored results.
ids fetches memories by exact ID (comma-separated, e.g. from a
memory_stats review sample) β the precise-fetch path. When given, every
other filter is ignored: results come back in the requested order,
deprecated memories included (you named them), with a missing list
for any ID not found.
All parameters are optional β omitting all returns all memories up to limit.
namespace accepts a single name, a comma-separated list (e.g.
"crow,my-project"), or None to search every namespace; limit is always the
total result cap. A multi-namespace response carries namespaces and stamps
each memory with its source namespace. A scoped search WITH a query
that finds nothing reruns once across every namespace, same filters, and
reports widened_from plus scope: "all"; a filter-only sweep never
widens, since "none here" is its answer.
tags is comma-separated; all provided tags must match. sort_by is one of:
relevance, created, accessed, decay_score. A created/accessed sort
orders the whole matching corpus before the limit, so it returns the true
newest (or least recently read) rows and narrowing limit narrows that
answer instead of changing it. With a query, that corpus is the keyword
match set: a date sort asks something relevance cannot answer, so the
semantic cohort does not vote in it and results carry no score.
confidence sets a minimum
confidence threshold (verified > inferred > stale > deprecated). created_after
and created_before accept ISO 8601 date strings (e.g. "2025-01-01").
include_deprecated also returns deprecated memories (stale ones are always
included). compact=True returns title + a ~200-char summary instead of
full content β the right mode for broad sweeps where full bodies would flood
the client's tool-result budget; pull full bodies with a targeted follow-up.
claims restricts results to the reconciliation backlog β memories that
still assert a PR/MR is open. "open" is every unresolved claim;
"contradicted" narrows to those a later memory in the same namespace has
already recorded as resolved, which are answerable immediately from what the
brain already holds. Composes with every other filter, so
claims="open", namespace="gingugu", sort_by="created" is a working
sweep. Close them out with memory_update(resolve_claims=...), which
records the resolution WITHOUT editing the memory's prose.
"unverified" is a different set and NOT a backlog: memories naming a
PR/MR whose prose never says what became of it. They assert nothing, so
they are absent from every open count and from claims.sample.
Most narrate work that long since shipped β this filter is how you read
them, not a queue to work down. Resolve one by naming its ref
explicitly; resolve_claims="all" deliberately leaves them alone.
orphans=True restricts results to memories no relation touches β the
graph backlog that memory_stats' graph.orphans counts. An orphan
is reachable only by direct search: spreading activation can never wake
it, so a verified, frequently-recalled orphan is retrieval the graph is
leaving on the table. Composes with every other filter and works with or
without a query, so orphans=True, namespace="crow", sort_by="accessed"
walks the ones costing the most first. Reconnect them with
memory_relate β and only where a directional fact exists to record;
an orphan is better left alone than wired up with an invented edge.
pinned filters on the always-present tier: True returns only pins,
False only unpinned memories, and omitting it ignores the flag. Pins are
the memories loaded unconditionally at every session start, ahead of and
exempt from ranking, so the tier is worth auditing on its own β and
memory_context cannot do it, since it returns pins mixed into ranked
buckets, capped by limit and scoped per namespace. Pair
pinned=True with namespace=None to enumerate every pin in the
store, which is the read the tier's own curation needs.
explain=True adds a score_breakdown to each hit: the weighted
terms score is the sum of. Results with no ranking behind them carry
none: an ids fetch and a created/accessed sort were not
ranked, and a listing with no query scores every row on the same flat
relevance, which the breakdown shows as an identical relevance term.
| Name | Required | Description | Default |
|---|---|---|---|
| ids | No | ||
| tags | No | ||
| type | No | ||
| limit | No | ||
| query | No | ||
| claims | No | ||
| pinned | No | ||
| compact | No | ||
| explain | No | ||
| orphans | No | ||
| sort_by | No | relevance | |
| namespace | No | ||
| confidence | No | ||
| created_after | No | ||
| created_before | No | ||
| include_deprecated | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden and does so richly: ids bypasses all other filters and includes deprecated with a missing-list; a scoped query that finds nothing reruns across all namespaces with widened_from; created/accessed sorts drop scores; compact swaps full bodies for a summary to protect tool-result budget. These are exactly the non-obvious behaviors an agent must know.
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 a crisp purpose/alternative statement, and most paragraphs earn their place by documenting a distinct mode. It drifts somewhat into philosophical guidance for neighbouring tools (e.g. orphan reconnection advice for memory_relate), which is tangential, so it is dense but not maximally tight.
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?
For a 16-parameter tool with zero annotation and zero output-schema coverage, the description remains complete: it explains return-shape features (missing list, namespaces, widened_from, score_breakdown) and the interaction of every filter mode. Nothing an agent needs to invoke it correctly is 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 coverage is 0% and the description compensates almost completely for all 16 parameters: ids format, tags comma-separated/all-must-match, sort_by enum values, confidence ordering, ISO 8601 dates, namespace single/list/None semantics, and the meaning of claims/pinned/orphans/explain/compact. It adds substantial meaning beyond the bare titles and defaults in 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 ('Advanced filtered search across memories with full control over filters and sort order') and explicitly distinguishes itself from the sibling memory_recall. An agent can tell exactly what this tool does and when it differs from the nearest alternative.
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?
Explicitly names when to use it (filter by type, date range, confidence, or non-relevance sorting) and names the alternative with its selecting condition ('Prefer memory_recall when you just have a natural-language query...'). Multiple sub-modes (claims, orphans, pinned, ids) each carry their own usage framing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_statsA
Return health statistics for the memory store. Use to monitor memory growth, identify dormant memories, and get a per-namespace breakdown of counts and confidence distribution. Call at session start alongside memory_context to assess the state of the knowledge base.
namespace accepts a single name, a comma-separated list (e.g.
"crow,my-project"), or omitted for global. A single name and the unscoped
call keep the shape they always had: {"ok": true, "flagged_stale": 0, "stats": {...}}. A comma list instead returns the namespace-independent
block (namespaces, access_log_rows, query_log_rows,
credentials) exactly once under global, plus each namespace's own
scoped stats under by_namespace - the session-start protocol's two-call pattern
(memory_stats(namespace="crow") then one per project) collapsed into
one call that computes the shared block once instead of once per
namespace: {"ok": true, "flagged_stale": 0, "namespaces": [...], "global": {...}, "by_namespace": {"crow": {...}, "<project>": {...}}}.
Names are de-duplicated, order-preserving; an unknown name fails the
whole call and names it, same as memory_recall/memory_search.
review_limit applies to every namespace's block.
stats.dormant_count reports memories untouched for 90+ days β a resting
signal only, never a confidence change. Dormant memories wake automatically on
recall via spreading activation. Memory is never auto-forgotten.
stats.size is the character cost the counts do not show: total_chars,
mean_chars, pinned_chars and largest_pinned_chars.
pinned_chars is the one to watch β pins load unconditionally at every
session start, ahead of and exempt from ranking, so it is the only part of
the store paid for on every call regardless of relevance.
largest_pinned_chars is the skew check: a tier is not described by how
many pins it holds, and when one pin approaches the tier total, the tier IS
that pin. Adding well-chosen pins will not fix that; splitting the outlier
will. Enumerate the tier with memory_search(pinned=True).
review_limit raises the review.sample, claims.sample and
graph.orphan_sample caps (default 5, max 100) so a reconciliation sweep can
enumerate every flagged memory β pair with memory_search's ids parameter to
pull the full bodies.
stats.graph.orphan_sample names the memories behind graph.orphans: those
no relation touches, which spreading activation can never reach. Ordered by
confidence, then access count, then recency, so the orphans costing the most
retrieval come first, each row carrying its namespace.
memory_search(orphans=True) pulls the same set with full bodies;
memory_relate reconnects one β where a directional fact genuinely exists.
stats.claims is the state-claim backlog: memories still asserting a PR/MR
is open. claims.sample enumerates them, contradicted first, each row
tagged contradicted (a later memory in the same namespace already recorded
that ref as resolved). open counts every unresolved claim while
open_actionable β what the sample lists β excludes claims on deprecated
memories. memory_search(claims="open") pulls the same set with full bodies;
memory_update(resolve_claims=...) closes them without editing prose.
claims.unverified counts refs a memory names without ever saying what
became of them. It is reported for visibility, not action: those refs assert
nothing, so they are excluded from open and from sample on purpose.
Read them with memory_search(claims="unverified").
flag_stale is deprecated and ignored β auto-demotion to stale contradicted
the never-forget model and has been removed. Retained so existing callers do not
error.
| Name | Required | Description | Default |
|---|---|---|---|
| namespace | No | ||
| flag_stale | No | ||
| review_limit | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and discharges it: it documents return shapes, that dormant memories are never auto-forgotten and wake on recall, that pins load unconditionally at every session start, that an unknown namespace fails the whole call, and that flag_stale is deprecated and ignored rather than erroring. This is rich behavioral context beyond any structured field.
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 and usage, and each paragraph is dense with earning content. However, it is very long (~500 words) and repeats the JSON return shapes for single vs. comma-list namespaces, which adds redundancy; some of that detail could be trimmed without loss.
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?
For a complex tool with no output schema and no annotations, the description covers return structure, per-namespace behavior, error mode, deprecation, and hand-offs to sibling tools. Nothing an agent needs to call it correctly is 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 coverage is 0%, so the description must compensate and does: namespace accepts a single name, a comma-separated list, or omission (with distinct return shapes for each), review_limit raises sample caps (default 5, max 100), and flag_stale is deprecated and ignored. Every parameter is fully explained with semantics and edge cases.
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?
Opens with a specific verb and resource ('Return health statistics for the memory store') and immediately scopes the purpose: monitor growth, identify dormant memories, per-namespace counts and confidence. An agent can distinguish this from siblings like memory_search or memory_context without opening a 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?
Explicitly says when to call it ('at session start alongside memory_context') and routes to alternatives for follow-up actions: memory_search(pinned=True), memory_search(orphans=True), memory_relate to reconnect, memory_update(resolve_claims=...) to close claims. When/when-not guidance is unusually thorough.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_storeA
Store a new memory in the knowledge base. Use to capture anything worth remembering across sessions: decisions, bugs, patterns, architecture choices, preferences, facts, workflows, or context. Do not use for ephemeral or session-only notes.
type must be one of: fact, decision, pattern, bug, architecture, preference,
workflow, context, capability. confidence is one of: verified (confirmed true), inferred
(assumed, not yet confirmed), stale (outdated), deprecated (no longer valid) β
defaults to "inferred". tags is comma-separated. namespace scopes the
memory to a project or domain; omit to use the configured default namespace.
source records what generated this memory (e.g. a file path or tool name).
metadata is an optional free-form JSON string for extra structured data.
capability records that a thing EXISTS and how to run it, where a
workflow says how to do it by hand. It requires
metadata={"capability": {"run": "...", "path": "..."}} (run
required; a relative path resolves against the namespace's repo
path). Reads report whether path still exists.
provenance declares how you came to believe this: user-asserted (the
user said so), measured (a command or test showed it), file-derived (read
from code or docs), or self-concluded (your own inference or opinion).
Declare it honestly - self-concluded is how a stored opinion arrives
visibly contestable instead of reading as settled fact. Any other value
is rejected. about says what this memory is FOR, in the user's words
for the thing rather than the words of what was done ("claude code
bootstrapping", not "init writes hook blocks"). It is searchable.
When dedupe_check is True (default), the response includes a
similar_memories list of up to 3 existing memories in the same
namespace whose content/title overlap strongly with this one β a
non-blocking hint so the caller can choose to update/relate/consolidate
instead of accumulating near-duplicates. Disable for bulk imports.
Usually EMPTY, and that is the signal working. Each hit carries
similarity (0-1) and its basis: cosine over embeddings, or
lexical token overlap when they are unavailable. Unlike a search
relevance it has magnitude and is comparable between calls - it says
how close the two texts are, not how this candidate ranked.
When relation_check is True (default), the response also includes a
suggested_relations list of up to 3 not-already-linked memories worth
EXAMINING for a relationship. Topical overlap is only how they were
found; it is not itself a reason to link. Ask whether one of them is the
memory this one supersedes, contradicts, was caused_by, or belongs
under - and if the honest answer is "they are just both about the same
area", link nothing. Search already surfaces topical neighbours, so a
related_to edge that says only "these are similar" adds no retrieval
signal and competes with the directional edges that do. Same gate as
above, set softer; similar_memories are merge candidates instead.
Both hint lists are COMPACT: title plus a ~200-char summary, never
full bodies. They are enough to decide whether to merge, link, or move
on; call memory_recall when a candidate warrants a closer look.
Both carry similarity + basis, never a search score: these
lists are not a ranking of the corpus, they are a measurement against
what you just wrote.
The response may also carry contradicted_memories: older memories
whose state claim THIS memory just resolved. Recording "PR #10 merged"
makes every memory still asserting "PR #10 open" knowably wrong, and
now is when fixing it is cheapest. Each entry gives the stale memory's
id, title, the ref at issue, what it asserts, and both
sides' evidence.
Reconcile by correcting the stale claim β the claim is now genuinely false, so the text should change. That is the opposite of rewording prose to silence a hint while the claim stays wrong. Advisory only: nothing was mutated.
| Name | Required | Description | Default |
|---|---|---|---|
| tags | No | ||
| type | Yes | ||
| about | No | ||
| title | Yes | ||
| source | No | ||
| content | Yes | ||
| metadata | No | ||
| namespace | No | ||
| confidence | No | inferred | |
| provenance | No | ||
| dedupe_check | No | ||
| relation_check | No |
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 so richly: it documents the response contract (similar_memories, suggested_relations, contradicted_memories), the default-on hint behavior, that hints are compact title+~200-char summaries, that contradictions are advisory only and 'nothing was mutated', and that invalid provenance values are rejected. This is far beyond what any structured field provides.
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 and exclusions, then organized by parameter group β a sensible structure for a 12-param tool. It is long, and a few passages (the similarity-vs-search-score aside, 'Usually EMPTY, and that is the signal working') restate points at more length than needed, but nearly every sentence carries operational meaning.
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?
For a 12-parameter mutation tool with no annotations and no output schema, the description supplies the missing return-value contract and the semantics of every parameter group, including the subtle dedupe/relation hint behavior. Nothing needed to invoke it correctly is absent.
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% across 12 parameters, so the description must compensate and it does: it enumerates the allowed `type` values, the `confidence` values with definitions and default, `tags` format, `namespace` scoping semantics, `source`, `metadata` format, the `capability` metadata sub-shape, `about` semantics with a contrastive example, and the `dedupe_check`/`relation_check` defaults.
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?
Opens with a specific verb+resource ('Store a new memory in the knowledge base') and immediately enumerates the kinds of content it is for (decisions, bugs, patterns, architecture, preferences). The scope is unambiguous and reads clearly against siblings like memory_recall and memory_update.
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?
Explicitly gives the inclusion criterion ('anything worth remembering across sessions') and an exclusion ('Do not use for ephemeral or session-only notes'), plus routing advice to call memory_recall when a hint candidate warrants a closer look and to disable the checks for bulk imports. When-to-use and when-not are both present.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_tripwireA
Bind a memory to a tool call so it is put in front of the agent at the moment it acts, not just at session start. Use it for the memories that must not be missed: "never sign commits as Claude", "never touch the prod config".
Actions: "add" (memory_id, tool, pattern), "list" (optional memory_id, namespace as a comma-separated list), "remove" (tripwire_id), "test" (tool, input, optional namespace).
tool is a regex FULL-matched against the tool name, e.g. "Bash" or
"Edit|Write" ("Bash" does not catch "BashOutput"). pattern is a regex
SEARCHED in the call's input: the command for Bash, the file_path for
Edit/Write/Read, the notebook_path for NotebookEdit, otherwise the input as
sorted JSON.
The PreToolUse hook denies the first matching call per session with the
memory as the reason; the re-issued call passes. Use "test" to check a pattern
before relying on it: input is the tool input as a JSON object string, and
nothing is denied or logged. gingugu's own mcp__gingugu__ tools never trip.
| Name | Required | Description | Default |
|---|---|---|---|
| tool | No | ||
| input | No | ||
| action | Yes | ||
| pattern | No | ||
| memory_id | No | ||
| namespace | No | ||
| tripwire_id | No |
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 so thoroughly: it discloses the PreToolUse hook that denies the first matching call per session and lets the re-issued call pass, explains regex FULL-match vs SEARCH semantics, specifies which input field is searched per tool, notes that "test" denies or logs nothing, and states that gingugu's own mcp__gingugu__ tools never trip.
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-loads the one-line purpose before expanding into actions, regex semantics, and hook behavior, so an agent can stop reading early. It is dense and long, and the "test" guidance is mentioned in two places, but nearly every sentence carries necessary information for a complex tool.
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?
For a 7-parameter, no-annotation, no-output-schema tool, the description covers the mechanism, action-to-parameter mapping, matching rules, and side effects well. The main residual gap is return behavior for "list" and "test" (e.g., what a test result looks like), which is not described and, absent an output schema, leaves the agent guessing about responses.
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%, so the description must compensate, and it largely does: it maps each action to its relevant parameters (add: memory_id/tool/pattern; list: optional memory_id/namespace; remove: tripwire_id; test: tool/input/namespace) and defines the exact matching semantics of tool and pattern, including what "Bash" does and does not catch. It is slightly thin on the format/origin of memory_id and tripwire_id, so not a full 5.
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 concrete verb+resource ("Bind a memory to a tool call") and immediately characterizes the distinguishing behavior: surfacing a memory at the moment of action rather than at session start. That mechanism makes it clearly distinguishable from sibling memory tools like memory_store or memory_recall 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?
Gives explicit context for when to use it ("memories that must not be missed") with concrete examples, and tells the agent to use "test" to validate a pattern before relying on it. It does not name a true alternative tool or state explicit when-not-to-use conditions, keeping it 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.
memory_unrelateA
Repair the graph: retype a mislabelled edge, turn a backwards one around, or
remove one that should not exist. The counterpart to memory_relate β without
it, an edge written in haste is permanent, and every wrong edge keeps competing
for one of the 3 spreading-activation slots on its memories forever.
Retype by passing new_relation_type alongside relation_type. The
edge is relabelled in place: direction, creation time and metadata survive,
because the usual repair is "right connection, wrong label" and the graph
should keep an honest record of when the link was first drawn. If an edge of
the new type already joins the pair, the two collapse into one and the
outcome reports merged rather than retyped β the edge count drops by
one, and nothing is fabricated to hide that.
Reverse by passing reverse=True alongside relation_type. The
endpoints are swapped on the same row, so id, creation time and metadata
survive exactly as they do for a retype β the connection was right, only the
arrow pointed the wrong way. Reversing COMBINES with new_relation_type, in
one write, because an edge recorded backwards is often mislabelled as well.
Note that reversing parent_of/child_of is the same operation as flipping
between the two types: do one or the other, not both. As with a retype, an
existing edge in the target direction absorbs this one and reports merged.
Delete by omitting new_relation_type and reverse. With relation_type, only
that edge goes; without it, every edge from source_id to target_id
goes, whatever the type. Deletion here is not the bulk prune the graph
guidance warns against: the caller names each edge, exactly as
memory_forget names a memory.
Batch by passing edges β an array of up to 100 objects, each with
source_id, target_id and optionally relation_type /
new_relation_type / reverse, i.e. the same decision made once per edge:
[{"source_id": "a", "target_id": "b",
"relation_type": "related_to", "new_relation_type": "caused_by"},
{"source_id": "c", "target_id": "d",
"relation_type": "caused_by", "reverse": true},
{"source_id": "e", "target_id": "f", "relation_type": "related_to"}]A batch is reviewed decisions submitted together, NOT a criteria-driven
sweep, and that is deliberate. There is no "retype every related_to in
this namespace" option, because the whole point of retyping is that each edge
deserves a different type based on what it actually records β a blanket
relabel would manufacture directional claims that were never true, and a
false caused_by is worse than an honest related_to.
The batch is validated in full before anything is written, so a malformed op
fails the whole call rather than leaving the graph half-repaired. Individual
outcomes (retyped, reversed, merged, deleted, not_found,
unchanged) are reported per edge. Use dry_run=True to preview a sweep
first; nothing is written and each op reports what it would have done.
Find the edges to repair with memory_edges.
| Name | Required | Description | Default |
|---|---|---|---|
| edges | No | ||
| dry_run | No | ||
| reverse | No | ||
| source_id | No | ||
| target_id | No | ||
| relation_type | No | ||
| new_relation_type | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden and does so thoroughly: it discloses that retyped/reversed edges preserve id, creation time, and metadata ('survive'); that collisions produce 'merged' outcomes with an edge-count drop; that batches are validated in full before any write ('fails the whole call rather than leaving the graph half-repaired'); the dry_run preview semantics; and the per-edge outcome vocabulary. This is genuinely rich behavioral disclosure beyond what any structured field could convey.
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?
The opening and the three operation paragraphs are well-organized with bold headers that front-load each mode. However, the batch section runs long, the embedded JSON example could be trimmed, and several sentences restate ideas ('the caller names each edge' recurs conceptually across delete and batch). The structure earns credit but the density is above what a 5 would require.
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?
For a 7-parameter, annotation-free, no-output-schema mutation tool, this description covers everything an agent needs: all operation modes, the parameter combinations that select them, conflict/merge behavior, validation atomicity, dry-run preview, the outcome vocabulary, the deletion-vs-pruning distinction, and the discovery tool to find edges. Nothing material is 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% and there are 7 parameters, so the description must compensate entirely β and it does. Each parameter's role is spelled out with semantics: new_relation_type means 'retype in place', reverse means 'swap endpoints on the same row', passing relation_type alone scopes a single-type delete while omitting it deletes all edges for the pair, and edges is described as an array of up to 100 objects with the per-object shape shown inline. The lone gap is dry_run, whose meaning is still inferable from the dedicated sentence.
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 opens with a precise, specific statement of scope: 'Repair the graph: retype a mislabelled edge, turn a backwards one around, or remove one that should not exist.' That's a concrete verb cluster plus resource, and it explicitly names its counterpart sibling (memory_relate). An agent can distinguish this from memory_relate and memory_edges 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?
The three operation modes are gated by exact parameter combinations (new_relation_type for retype, reverse=True for reversal, omitting both for delete), and the distinction from batch pruning is made explicit ('NOT a criteria-driven sweep'). It also names memory_edges as the discovery tool and memory_forget as the analogue for named deletions, giving the agent concrete routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_updateA
Update one or more fields of an existing memory. Use to correct outdated information, promote confidence after confirming an inference, retype a misfiled memory, or add/replace tags. Do not create a new memory when the right action is to update an existing one β find the id first with memory_recall.
All fields are optional; only provided fields are changed. tags
(comma-separated) replaces the full tag set when provided β omit to leave tags
unchanged. Pass metadata="" to clear metadata; omit to leave it unchanged.
type retypes the memory (same values as memory_store). Retyping is the
right fix when a memory was filed under the wrong kind β e.g. durable
reference material saved as workflow picks up point-in-time review
hints, because pattern/preference are the types exempt from them.
Retyping does not re-embed: the vector derives from title + about + content.
Capability validation applies to the FINAL state, so retyping to or from
capability must bring or clear the metadata.capability block.
provenance and about declare how the claim was reached and what it
is for (see memory_store); pass "" to clear either. Neither touches
last_confirmed: declaring them is not re-checking the claim.
resolve_claims reconciles a stale state claim WITHOUT EDITING THE
PROSE β comma-separated refs (e.g. "gingugu#10"), or "all" for every
open claim on this memory. Use it when the text is accurate history: a
session log that said "PR #10 open" was correct on the day it was
written, and rewriting it to stay current destroys the record. The
memory body is left byte-identical; only the claim's resolution is
recorded. Reach for content instead only when the memory asserts
something that was never true.
"all" means every OPEN claim, never an unverified one. An unverified
ref is one the prose names without saying what became of it, so sweeping
it under "all" would record that you checked something you did not. Name
such a ref explicitly to resolve it β that path works and is the honest
way to say "I looked, and it merged".
When relation_check is True (default) and title or content was
provided, the response includes a suggested_relations list of up to 3
not-already-linked memories worth examining for a relationship - same
semantics as memory_store: overlap is how they were found, and only a
directional fact (supersedes / contradicts / caused_by / parent_of /
child_of) justifies an edge. Tag-only or confidence-only updates skip the
check since the matching surface didn't change. Entries are compact
(title + a ~200-char summary) and carry similarity + basis,
as in memory_store; an empty list means nothing was close enough to
be worth your time.
pinned marks a memory as ALWAYS loaded by memory_context for its
namespace, ahead of and exempt from ranking, in addition to limit.
Reserve it for the few rules that would cause real damage if missed β
the ones you would want in front of you before touching anything, not
merely useful or frequently relevant material. Ranking already handles
"relevant"; a pin is for "inviolable". Capped per namespace (currently
20): pinning is a budget, so spending it on a merely-handy memory
crowds out a rule that governs behaviour. Pass pinned=False to
unpin. Pinning does not touch last_confirmed β it is a retrieval
decision, not a claim that the content is still true.
| Name | Required | Description | Default |
|---|---|---|---|
| tags | No | ||
| type | No | ||
| about | No | ||
| title | No | ||
| pinned | No | ||
| content | No | ||
| metadata | No | ||
| memory_id | Yes | ||
| confidence | No | ||
| provenance | No | ||
| relation_check | No | ||
| resolve_claims | No |
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 so: it discloses that omitted fields are unchanged, that ``tags`` replaces the whole set, that ``metadata=""`` clears, that retyping does not re-embed, that capability validation applies to the FINAL state, that ``resolve_claims`` leaves the body byte-identical, and that `pinned` is capped at 20 per namespace. This is exactly the kind of non-obvious mutation semantics an agent 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?
Front-loaded with purpose and routing before diving into per-field semantics, and each paragraph maps to a parameter or behavior. It is long and occasionally repetitive (the last_confirmed caveat appears twice), but nearly every sentence adds a distinct behavioral rule rather than 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?
No output schema exists, and the description compensates by describing the `suggested_relations` response payload, its compact entry shape, and the empty-list meaning, plus the `all`-vs-`unverified` distinction for resolve_claims. For a 12-parameter mutation tool this is complete enough to call correctly.
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% with 12 parameters, so the description must compensate, and it explains tags, metadata, type, provenance, about, resolve_claims, pinned, relation_check and content semantics well. Gaps remain: `confidence` is only referenced obliquely ('promote confidence') with no value semantics, and `title`/`memory_id` get no behavioral note beyond implication. Still far above baseline.
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?
Opens with a precise verb+resource ('Update one or more fields of an existing memory') and immediately distinguishes itself from the sibling memory_store by warning 'Do not create a new memory when the right action is to update an existing one.' An agent can route between store/update/recall without opening a 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 when-to-use cases (correct outdated info, promote confidence, retype, add/replace tags) and names the alternative path ('find the id first with memory_recall'). It also gives a when-not-to-use rule and tells the agent when to prefer `content` over `resolve_claims`. Strong routing guidance.
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.
1 tool update
v0.25.0- Changed
credential_delete2 fields changed- added
Input schema / properties / confirm / defaultAdded value: +true - changed
Input schema / requiredPrevious value: -[ - "service_name", - "confirm" -]New value: +[ + "service_name" +]
8 tool updates
v0.19.0- Changed
credential_get2 fields changed- added
Input schema / properties / intoAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Into" +} - added
Input schema / properties / revealAdded value: +{ + "default": false, + "title": "Reveal", + "type": "boolean" +}
- Added
memory_dream - Changed
memory_relate11 fields changed- added
Input schema / properties / edgesAdded value: +{ + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Edges" +} - added
Input schema / properties / relation_type / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - added
Input schema / properties / relation_type / defaultAdded value: +null - removed
Input schema / properties / relation_type / typeRemoved value: -"string" - added
Input schema / properties / source_id / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - added
Input schema / properties / source_id / defaultAdded value: +null - removed
Input schema / properties / source_id / typeRemoved value: -"string" - added
Input schema / properties / target_id / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - added
Input schema / properties / target_id / defaultAdded value: +null - removed
Input schema / properties / target_id / typeRemoved value: -"string" - removed
Input schema / requiredRemoved value: -[ - "source_id", - "target_id", - "relation_type" -]
- Changed
memory_search1 field changed- added
Input schema / properties / pinnedAdded value: +{ + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Pinned" +}
- Changed
memory_store2 fields changed- added
Input schema / properties / aboutAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "About" +} - added
Input schema / properties / provenanceAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provenance" +}
- Added
memory_tripwire - Changed
memory_unrelate1 field changed- changed
Input schema / properties / edges / anyOfPrevious value: -[ - { - "items": { - "additionalProperties": true, - "type": "object" - }, - "type": "array" - }, - { - "type": "null" - } -]New value: +[ + { + "items": {}, + "type": "array" + }, + { + "type": "null" + } +]
- Changed
memory_update2 fields changed- added
Input schema / properties / aboutAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "About" +} - added
Input schema / properties / provenanceAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provenance" +}
4 tool updates
v0.18.0- Changed
memory_context1 field changed- added
Input schema / properties / explainAdded value: +{ + "default": false, + "title": "Explain", + "type": "boolean" +}
- Added
memory_excerpt - Changed
memory_recall1 field changed- added
Input schema / properties / explainAdded value: +{ + "default": false, + "title": "Explain", + "type": "boolean" +}
- Changed
memory_search1 field changed- added
Input schema / properties / explainAdded value: +{ + "default": false, + "title": "Explain", + "type": "boolean" +}
2 tool updates
v0.17.0- Changed
memory_search1 field changed- added
Input schema / properties / orphansAdded value: +{ + "default": false, + "title": "Orphans", + "type": "boolean" +}
- Changed
memory_unrelate1 field changed- added
Input schema / properties / reverseAdded value: +{ + "default": false, + "title": "Reverse", + "type": "boolean" +}
4 tool updates
v0.16.0- Added
memory_edges - Changed
memory_search1 field changed- added
Input schema / properties / claimsAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Claims" +}
- Added
memory_unrelate - Changed
memory_update1 field changed- added
Input schema / properties / pinnedAdded value: +{ + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Pinned" +}
4 tool updates
v0.13.0- Changed
memory_namespaces1 field changed- added
Input schema / properties / default_repoAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Default Repo" +}
- Changed
memory_search1 field changed- added
Input schema / properties / idsAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ids" +}
- Changed
memory_stats1 field changed- added
Input schema / properties / review_limitAdded value: +{ + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Review Limit" +}
- Changed
memory_update2 fields changed- added
Input schema / properties / resolve_claimsAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Resolve Claims" +} - added
Input schema / properties / typeAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Type" +}
2 tool updates
v0.6.0- Changed
memory_recall1 field changed- added
Input schema / properties / compactAdded value: +{ + "default": false, + "title": "Compact", + "type": "boolean" +}
- Changed
memory_search1 field changed- added
Input schema / properties / compactAdded value: +{ + "default": false, + "title": "Compact", + "type": "boolean" +}
4 tool updates
v0.4.0- Changed
memory_consolidate6 fields changed- added
Input schema / properties / memory_ids / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - added
Input schema / properties / memory_ids / defaultAdded value: +null - removed
Input schema / properties / memory_ids / typeRemoved value: -"string" - added
Input schema / properties / min_similarityAdded value: +{ + "default": 0.9, + "title": "Min Similarity", + "type": "number" +} - added
Input schema / properties / namespaceAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Namespace" +} - removed
Input schema / requiredRemoved value: -[ - "memory_ids" -]
- Changed
memory_context1 field changed- added
Input schema / properties / compactAdded value: +{ + "default": false, + "title": "Compact", + "type": "boolean" +}
- Changed
memory_store1 field changed- changed
Input schema / properties / metadata / anyOfPrevious value: -[ - { - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } +]
- Changed
memory_update1 field changed- changed
Input schema / properties / metadata / anyOfPrevious value: -[ - { - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } +]
2 tool updates
v0.3.8- Changed
memory_store2 fields changed- added
Input schema / properties / dedupe_checkAdded value: +{ + "default": true, + "title": "Dedupe Check", + "type": "boolean" +} - added
Input schema / properties / relation_checkAdded value: +{ + "default": true, + "title": "Relation Check", + "type": "boolean" +}
- Changed
memory_update1 field changed- added
Input schema / properties / relation_checkAdded value: +{ + "default": true, + "title": "Relation Check", + "type": "boolean" +}
16 tool updates
v0.3.1- First observed
credential_delete - First observed
credential_get - First observed
credential_list - First observed
credential_store - First observed
memory_consolidate - First observed
memory_context - First observed
memory_export - First observed
memory_forget - First observed
memory_import - First observed
memory_namespaces - First observed
memory_recall - First observed
memory_relate - First observed
memory_search - First observed
memory_stats - First observed
memory_store - First observed
memory_update
TDQS
Scored across 21 tools
Most tools have clearly distinct purposes, but memory_recall, memory_search, and memory_context all retrieve memories and rely on descriptions to differentiate. memory_consolidate and memory_dream also touch similar consolidation territory, though their read/write scopes are distinct. The explicit guidance in descriptions mitigates confusion effectively.
All tool names use a consistent snake_case verb_noun pattern with clear domain prefixes (memory_* and credential_*). No mixed conventions or vague verbs appear. The naming is entirely predictable.
21 tools is on the heavy side for a memory server, falling in the borderline 16-25 range. While each tool covers a distinct operation, some (e.g., memory_excerpt, memory_edges, memory_dream) could plausibly be folded into existing tools or made optional modes. The broad scope (memory, graph, credentials, namespaces) justifies many tools but the count still feels heavy.
The surface covers full lifecycle operations: memory CRUD (store, update, forget, recall, search), graph relations (relate, unrelate, edges, dream), consolidation, namespaces, export/import, stats, context, excerpt, tripwire, and credential storage/retrieval/deletion. No obvious gaps or missing operations stand out for the stated domain.
Maintenance
Related MCP Connectors
- mcpOAuthai.butlerbrain
Persistent memory for AI assistants. Save once; recall from Claude, ChatGPT, or any MCP client.
Persistent, portable memory for AI assistants β your private memory graph, from any MCP client.
Persistent memory for AI agents across Claude, ChatGPT and any MCP client.
Persistent memory, knowledge graph, task management and more for AI agents via MCP.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceA local, persistent memory system for AI coding assistants that stores decisions, patterns, and session context via MCP tools. It enables cross-session memory management using SQLite and optional vector search without external dependencies or cloud storage.37 npm64MIT
- AlicenseBqualityCmaintenancePersistent memory engine for AI coding agents. Single Go binary, zero runtime dependencies, MCP-native. Stores, searches, and deduplicates memories across sessions using embedded SQLite with hybrid FTS + semantic search, memory decay, relation graph, and token-budget context assembly.1023MIT
- AlicenseNot gradedqualityAmaintenancePersistent memory for any AI assistant. Zero token cost until recall. Stores memories in local SQLite, ranks by 6-factor scoring, returns results 79% smaller than JSON. Works with Claude, ChatGPT, Grok, Cursor, Windsurf, and any MCP client.54Apache 2.0
- FlicenseNot gradedqualityCmaintenanceLocal-first cross-agent memory for AI coding agents. Persistent, shared memory over MCP β what you tell one agent can be recalled by another β with all data stored in a single local SQLite file, no cloud and no API keys.-