Skip to main content
Glama

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.

Python MCP SQLite License Glama


πŸ“‹ 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:

  1. No deployment. One SQLite file holds memories, FTS5 index, and embeddings. No Postgres, no Pinecone, no Chroma server.

  2. 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.

  3. 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)

gingugu serve runs the same server over HTTP behind Bearer tokens, each client scoped to its own namespaces; gingugu promote harvests a local brain's durable knowledge up to it with provenance stamps


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) or pip

  • macOS, 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 gingugu

That'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 pip

2. 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 winner

Check 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/mcp

Every 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 request

The 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-laptop

Use 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 brain

login 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_GRANT is malformed, blank or *=write;

  • MEMORY_GRANT is set and MEMORY_CREDENTIALS_ENABLED is not false - 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 write

The 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 command

Each 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.json is 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 gingugu

Or 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.

One command bootstraps a repo with the strongest setup Claude Code allows:

cd your-repo
gingugu init

It installs:

  • .claude/hooks/session_start.py β€” a SessionStart hook 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 loads crow (and the persona) only.

  • .claude/hooks/stop.py β€” a Stop hook that blocks once if a working session never saved anything, guarding the "unsaved session vanishes" trap.

  • .claude/hooks/user_prompt_recall.py β€” a UserPromptSubmit hook 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 the query_log table 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. Set MEMORY_RECALL_HOOK=off to disable it.

  • .claude/hooks/pre_tool_tripwire.py β€” a PreToolUse hook for tripwires: memories bound, with memory_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 in query_log. Set MEMORY_TRIPWIRES=off to disable it.

  • .claude/skills/sink-the-ship/SKILL.md β€” a /sink-the-ship skill to flush everything worth keeping before you close a session. If an older install left a .claude/commands/sink-the-ship.md behind, gingugu init retires 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 β€” a SubagentStart hook (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 own MEMORY_GRANT, when it has one. Inside a subagent, gingugu hook tool also keeps it off the brain's data directory and the gingugu CLI, 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. Set MEMORY_MINION_FENCE=off to 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__brain added to permissions.allow, in the repo's .claude/settings.json and in your user-level ~/.claude/settings.json (backed up, idempotent), so a fenced minion's calls to its own brain server 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 .bak copies init itself 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 that init doesn'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 -- gingugu

The 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 harness

On top of everything init installs, it adds:

  • .claude/hooks/pre_tool_use.py - a guard that blocks reading (including through Grep) or writing secrets (.env, credentials, keys) and dangerous rm commands. Template files such as .env.example pass. 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 to logs/<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-reviewer and ai-docs-auditor (sonnet). Each runs its own brain server that reads crow and this repo's namespace and writes only minions.

  • The /creating-pr skill - 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. A CLAUDE.md that already has its own ## AI Knowledge Base Enforcement section keeps it, and gets no second copy.

  • permissions.deny entries for Opus minions (Agent(model:opus)), force pushes, git reset --hard and rm -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.py and the rest of that kit) in favour of log_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

.windsurfrules (repo root)

Per-workspace

Cursor

.cursorrules (repo root)

Per-workspace

Cline

.clinerules (repo root)

Per-workspace

Codex / OpenAI

AGENTS.md (repo root)

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 .windsurfrules in this repo. Copy the ## Memory Protocol section 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 ui

That 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) together

The 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

MEMORY_DB_PATH

~/.local/share/gingugu/memories.db (macOS/Linux) Β· %LOCALAPPDATA%\gingugu\memories.db (Windows)

Database location

MEMORY_NAMESPACE

(unset)

Default namespace for this workspace (recommended per-MCP-entry)

MEMORY_NAMESPACE_PATH

(unset)

Alternative: filesystem path; namespace derived from basename

MEMORY_AUTO_CONTEXT_LIMIT

10

Max memories to surface on auto-context

MEMORY_DECAY_LAMBDA

0.01

Freshness decay rate in days⁻¹ (gentle; freshness is floored, so memories never fully fade)

MEMORY_EMBEDDINGS_ENABLED

true

Toggle semantic search. false falls back to rank-based BM25-only retrieval

MEMORY_EMBEDDINGS_BACKEND

fastembed

Embedding backend: fastembed (ONNX, offline) or ollama (delegates to local Ollama process)

MEMORY_EMBEDDINGS_MODEL

BAAI/bge-small-en-v1.5

fastembed model. First use downloads ~80MB to ~/.cache/fastembed

MEMORY_EMBEDDINGS_OLLAMA_MODEL

nomic-embed-text

Ollama model to use when MEMORY_EMBEDDINGS_BACKEND=ollama

MEMORY_EMBEDDINGS_OLLAMA_HOST

http://localhost:11434

Ollama host when MEMORY_EMBEDDINGS_BACKEND=ollama

MEMORY_W_RELEVANCE

0.45

Composite-score weight for FTS5 relevance

MEMORY_W_FRESHNESS

0.10

Composite-score weight for freshness (a soft recency tiebreaker)

MEMORY_W_ACCESS

0.10

Composite-score weight for access frequency

MEMORY_W_CONFIDENCE

0.35

Composite-score weight for confidence (trust β€” the dominant standalone signal)

MEMORY_CREDENTIALS_ENABLED

true

Expose the credential_* vault tools. Set false to run an instance without a secret vault (e.g. a shared/central server)

MEMORY_CONFIRM_DESTRUCTIVE

(unset: confirmations on)

Destructive tools (hard memory_forget, cascading namespace delete, memory_consolidate with keep_originals=false, overwriting memory_import, credential_delete) ask you to confirm through the client. off (also false, 0, no) skips the dialog, for automation. Applies to the server that executes the tool: with a remote brain, set it on the brain for memory tools and on the local proxy for the credential vault

MEMORY_GRANT

(unset)

Fence a stdio server like a scoped token: ns=read|write,... (e.g. my-project=read,minions=write). Unset is full access. A bad spec, an empty value, or *=write refuses to start. Used by a subagent's own brain server in its agent file. The client's hooks follow it too, whether it is set in the MCP server's env block (the server publishes it) or in the hooks' own environment: recall, tripwires and minion warm-up stay inside it, and an unusable value or a disagreement keeps them quiet

MEMORY_PERSONA

(unset)

This agent's own namespace (e.g. research). Recall, tripwires and the SessionStart contract load crow, then the persona, then the repo (recall and tripwires narrowed by MEMORY_GRANT when set). A malformed value is ignored

MEMORY_REMOTE_URL

(unset)

Use this remote brain for this client only (off forces local). A bare gingugu then relays to it; the credential vault stays local

MEMORY_MINION_FENCE

on

Set off to disable the subagent fence in gingugu hook tool (data-dir file/shell access and inherited-server writes)

MEMORY_SERVE_HOST

127.0.0.1

Bind host for gingugu serve (set 0.0.0.0 to accept remote connections)

MEMORY_SERVE_PORT

8765

Bind port for gingugu serve

MEMORY_SERVE_TOKEN

(unset)

The owner's Bearer token for gingugu serve (full access). If unset, a token is read from <db-dir>/serve_token, or generated and saved 0600 (its path is logged, never the token). Other clients get scoped tokens from gingugu token add

MEMORY_SERVE_TLS

auto

TLS for gingugu serve. auto: https on any non-loopback bind with a self-signed cert generated in <db-dir>/tls/, plain http on loopback. on: https on loopback too. off: plain http everywhere. Anything else refuses to start

MEMORY_SERVE_TLS_CERT / MEMORY_SERVE_TLS_KEY

(unset)

Your own PEM cert and key instead of the generated one (both or neither); turns TLS on. Never pinned by gingugu remote login

MEMORY_SERVE_TLS_SAN

(unset)

Extra comma-separated names (DNS or IP) for the generated cert. Delete <db-dir>/tls/ to regenerate it, then gingugu remote login again on each machine

MEMORY_DREAM_IDLE_MINUTES

20

How long the brain must go untouched before gingugu dream --if-idle will run. Also the threshold that cancels a run in progress when you come back

MEMORY_LOG_LEVEL

INFO

Logging verbosity (logs go to stderr β€” stdout is the MCP transport)

MEMORY_DEBUG

false

Convenience switch for DEBUG logging (MEMORY_LOG_LEVEL wins if also set)

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

memory_store

Save a new memory; provenance declares how you came to believe it (user-asserted / measured / file-derived / self-concluded), about what it is for in the user's words (searchable)

memory_recall

Search + retrieve (ranked by relevance Γ— freshness; one or many namespaces; optional compact mode; explain for a per-hit score breakdown)

memory_context

Auto-surface relevant memories (one or many namespaces, deduped; optional compact mode; explain for a per-hit score breakdown)

memory_update

Update content, type, confidence, metadata, provenance or about; resolve_claims reconciles a stale PR/MR claim without editing the prose

memory_relate

Create relationships between memories; one at a time or a batch (edges), all-or-nothing, idempotent

memory_edges

List edges with both endpoints' titles, namespaces, and degree; filter by namespace, type, or memory

memory_unrelate

Retype an edge in place, reverse a backwards one, or remove it; one at a time or a batch, with dry_run

memory_consolidate

Merge/summarize/deduplicate; call without ids for a read-only near-dupe scan. keep_originals=false deletes the originals, so you confirm it

memory_dream

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

memory_tripwire

Bind a memory to a tool call so it stops that call once, before it runs: add (a tool-name regex + an input regex), list, remove, and test (a dry run that never denies)

memory_forget

Deprecate or remove a memory (a hard delete asks you to confirm)

memory_namespaces

List/create/update/delete namespaces; default_repo sets what a bare "PR #12" means there ("" = not a repo). Deleting a non-empty namespace asks you to confirm

memory_export

Export memories + tags + relations to portable JSON

memory_import

Restore a JSON export (skip or replace on conflict; a replace that overwrites memories asks you to confirm)

memory_stats

Health overview (dormancy, counts, coverage, review sweep, the claims backlog, a relation-graph block whose orphan_sample names the memories no edge reaches - review_limit raises every sample's cap, a size block reporting the character cost of the store and of the always-loaded pinned tier, and query_log_rows, the count of recorded queries); a comma-separated namespace list returns the global block once plus each namespace's own stats

memory_search

Advanced filtered search (type, tags, confidence, dates; one or many namespaces; optional compact mode; fetch by exact ids; claims to work the reconciliation backlog or read refs the prose never resolved, orphans to work the graph backlog, pinned to enumerate the always-present tier; explain for a per-hit score breakdown)

memory_excerpt

Read inside ONE memory: find literal matches with their character offsets, line numbers and surrounding context, and/or slice an exact character range

credential_store

Store/update a service credential bundle

credential_get

Retrieve a credential bundle - secrets redacted; into writes one to a 0600 file, reveal shows it inline

credential_list

List services + expiry status (no secrets shown)

credential_delete

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 -v

Troubleshooting

Issue

Solution

DB locked

Expected under heavy concurrent writes β€” WAL mode supports multiple processes (many readers + one writer). The server retries with a busy_timeout; if it persists, a stuck process holds the write lock. See Concurrency above.

Slow search

Run memory_stats to check DB size; consolidate if bloated

Stale results

Use memory_update to confirm or deprecate old memories

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 tools
credential_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.

ParametersJSON Schema
NameRequiredDescriptionDefault
confirmNo
field_nameNo
service_nameYes
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.

ParametersJSON Schema
NameRequiredDescriptionDefault
intoNo
fieldsNo
revealNo
service_nameYes

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
check_expiryNo

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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}}.

ParametersJSON Schema
NameRequiredDescriptionDefault
fieldsYes
expires_atNo
descriptionNo
service_nameYes

TDQS

A4/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
strategyNomerge
namespaceNo
memory_idsNo
keep_originalsNo
min_similarityNo
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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
compactNo
explainNo
namespaceNo
task_hintNo

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 with namespace, or omit it to run over the whole store.

  • list - read the queue, strongest finding first. Filter by kind (edge/cluster/core), status, or namespace.

  • accept - approve proposal_id and apply it. This is the step that supplies what the math could not: an edge needs relation_type, a cluster needs tag (applied to every member), a core proposal pins the memory. Missing arguments are an error, not a default. Pass reverse=True on an edge to 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 - decline proposal_id permanently. 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
tagNo
kindNo
limitNo
actionNolist
statusNopending
reverseNo
namespaceNo
proposal_idNo
relation_typeNo

TDQS

A4.4/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
offsetNo
memory_idNo
namespaceNo
relation_typeNo

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 query for a literal, case-insensitive substring scan. Each match returns its start/end character offsets, its 1-indexed line, and an excerpt with context_chars of surrounding text on each side. total_matches is the true count even when max_matches caps what comes back, so you can tell "that was all of them" from "that was the first 10 of 300".

  • Slice: pass start and/or end character 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=...).

ParametersJSON Schema
NameRequiredDescriptionDefault
endNo
queryNo
startNo
memory_idYes
max_matchesNo
context_charsNo
case_sensitiveNo

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
namespaceNo
include_deprecatedNo

TDQS

A4.2/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
reasonNo
memory_idYes
hard_deleteNo
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.

ParametersJSON Schema
NameRequiredDescriptionDefault
dataYes
on_conflictNoskip
memory_namespaces

Manage namespaces. action is one of: list, create, update, delete.

  • list β€” all namespaces with their memory counts.

  • create β€” create (or fetch) name with optional path/description.

  • update β€” update name's path/description/default_repo (only provided fields).

  • delete β€” remove name; the default namespace is protected and a non-empty namespace requires cascade=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.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNo
pathNo
actionNolist
cascadeNo
descriptionNo
default_repoNo
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.

ParametersJSON Schema
NameRequiredDescriptionDefault
tagsNo
typeNo
limitNo
queryYes
compactNo
explainNo
namespaceNo
confidenceNo
include_relatedNo
include_deprecatedNo

TDQS

A4.7/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
edgesNo
source_idNo
target_idNo
relation_typeNo

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_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.

ParametersJSON Schema
NameRequiredDescriptionDefault
namespaceNo
flag_staleNo
review_limitNo

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
tagsNo
typeYes
aboutNo
titleYes
sourceNo
contentYes
metadataNo
namespaceNo
confidenceNoinferred
provenanceNo
dedupe_checkNo
relation_checkNo

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
toolNo
inputNo
actionYes
patternNo
memory_idNo
namespaceNo
tripwire_idNo

TDQS

A4.5/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
edgesNo
dry_runNo
reverseNo
source_idNo
target_idNo
relation_typeNo
new_relation_typeNo

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness3/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
tagsNo
typeNo
aboutNo
titleNo
pinnedNo
contentNo
metadataNo
memory_idYes
confidenceNo
provenanceNo
relation_checkNo
resolve_claimsNo

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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. 1 tool updatev0.25.0
    • Changedcredential_delete2 fields changed
      • addedInput schema / properties / confirm / default
        Added value: +true
      • changedInput schema / required
        Previous value: -[
        -  "service_name",
        -  "confirm"
        -]New value: +[
        +  "service_name"
        +]
  2. 8 tool updatesv0.19.0
    • Changedcredential_get2 fields changed
      • addedInput schema / properties / into
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Into"
        +}
      • addedInput schema / properties / reveal
        Added value: +{
        +  "default": false,
        +  "title": "Reveal",
        +  "type": "boolean"
        +}
    • Addedmemory_dream
    • Changedmemory_relate11 fields changed
      • addedInput schema / properties / edges
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {},
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Edges"
        +}
      • addedInput schema / properties / relation_type / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedInput schema / properties / relation_type / default
        Added value: +null
      • removedInput schema / properties / relation_type / type
        Removed value: -"string"
      • addedInput schema / properties / source_id / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedInput schema / properties / source_id / default
        Added value: +null
      • removedInput schema / properties / source_id / type
        Removed value: -"string"
      • addedInput schema / properties / target_id / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedInput schema / properties / target_id / default
        Added value: +null
      • removedInput schema / properties / target_id / type
        Removed value: -"string"
      • removedInput schema / required
        Removed value: -[
        -  "source_id",
        -  "target_id",
        -  "relation_type"
        -]
    • Changedmemory_search1 field changed
      • addedInput schema / properties / pinned
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "boolean"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Pinned"
        +}
    • Changedmemory_store2 fields changed
      • addedInput schema / properties / about
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "About"
        +}
      • addedInput schema / properties / provenance
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Provenance"
        +}
    • Addedmemory_tripwire
    • Changedmemory_unrelate1 field changed
      • changedInput schema / properties / edges / anyOf
        Previous value: -[
        -  {
        -    "items": {
        -      "additionalProperties": true,
        -      "type": "object"
        -    },
        -    "type": "array"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "items": {},
        +    "type": "array"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Changedmemory_update2 fields changed
      • addedInput schema / properties / about
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "About"
        +}
      • addedInput schema / properties / provenance
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Provenance"
        +}
  3. 4 tool updatesv0.18.0
    • Changedmemory_context1 field changed
      • addedInput schema / properties / explain
        Added value: +{
        +  "default": false,
        +  "title": "Explain",
        +  "type": "boolean"
        +}
    • Addedmemory_excerpt
    • Changedmemory_recall1 field changed
      • addedInput schema / properties / explain
        Added value: +{
        +  "default": false,
        +  "title": "Explain",
        +  "type": "boolean"
        +}
    • Changedmemory_search1 field changed
      • addedInput schema / properties / explain
        Added value: +{
        +  "default": false,
        +  "title": "Explain",
        +  "type": "boolean"
        +}
  4. 2 tool updatesv0.17.0
    • Changedmemory_search1 field changed
      • addedInput schema / properties / orphans
        Added value: +{
        +  "default": false,
        +  "title": "Orphans",
        +  "type": "boolean"
        +}
    • Changedmemory_unrelate1 field changed
      • addedInput schema / properties / reverse
        Added value: +{
        +  "default": false,
        +  "title": "Reverse",
        +  "type": "boolean"
        +}
  5. 4 tool updatesv0.16.0
    • Addedmemory_edges
    • Changedmemory_search1 field changed
      • addedInput schema / properties / claims
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Claims"
        +}
    • Addedmemory_unrelate
    • Changedmemory_update1 field changed
      • addedInput schema / properties / pinned
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "boolean"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Pinned"
        +}
  6. 4 tool updatesv0.13.0
    • Changedmemory_namespaces1 field changed
      • addedInput schema / properties / default_repo
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Default Repo"
        +}
    • Changedmemory_search1 field changed
      • addedInput schema / properties / ids
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Ids"
        +}
    • Changedmemory_stats1 field changed
      • addedInput schema / properties / review_limit
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Review Limit"
        +}
    • Changedmemory_update2 fields changed
      • addedInput schema / properties / resolve_claims
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Resolve Claims"
        +}
      • addedInput schema / properties / type
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Type"
        +}
  7. 2 tool updatesv0.6.0
    • Changedmemory_recall1 field changed
      • addedInput schema / properties / compact
        Added value: +{
        +  "default": false,
        +  "title": "Compact",
        +  "type": "boolean"
        +}
    • Changedmemory_search1 field changed
      • addedInput schema / properties / compact
        Added value: +{
        +  "default": false,
        +  "title": "Compact",
        +  "type": "boolean"
        +}
  8. 4 tool updatesv0.4.0
    • Changedmemory_consolidate6 fields changed
      • addedInput schema / properties / memory_ids / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedInput schema / properties / memory_ids / default
        Added value: +null
      • removedInput schema / properties / memory_ids / type
        Removed value: -"string"
      • addedInput schema / properties / min_similarity
        Added value: +{
        +  "default": 0.9,
        +  "title": "Min Similarity",
        +  "type": "number"
        +}
      • addedInput schema / properties / namespace
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Namespace"
        +}
      • removedInput schema / required
        Removed value: -[
        -  "memory_ids"
        -]
    • Changedmemory_context1 field changed
      • addedInput schema / properties / compact
        Added value: +{
        +  "default": false,
        +  "title": "Compact",
        +  "type": "boolean"
        +}
    • Changedmemory_store1 field changed
      • changedInput schema / properties / metadata / anyOf
        Previous value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "additionalProperties": true,
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Changedmemory_update1 field changed
      • changedInput schema / properties / metadata / anyOf
        Previous value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "additionalProperties": true,
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
  9. 2 tool updatesv0.3.8
    • Changedmemory_store2 fields changed
      • addedInput schema / properties / dedupe_check
        Added value: +{
        +  "default": true,
        +  "title": "Dedupe Check",
        +  "type": "boolean"
        +}
      • addedInput schema / properties / relation_check
        Added value: +{
        +  "default": true,
        +  "title": "Relation Check",
        +  "type": "boolean"
        +}
    • Changedmemory_update1 field changed
      • addedInput schema / properties / relation_check
        Added value: +{
        +  "default": true,
        +  "title": "Relation Check",
        +  "type": "boolean"
        +}
  10. 16 tool updatesv0.3.1
    • First observedcredential_delete
    • First observedcredential_get
    • First observedcredential_list
    • First observedcredential_store
    • First observedmemory_consolidate
    • First observedmemory_context
    • First observedmemory_export
    • First observedmemory_forget
    • First observedmemory_import
    • First observedmemory_namespaces
    • First observedmemory_recall
    • First observedmemory_relate
    • First observedmemory_search
    • First observedmemory_stats
    • First observedmemory_store
    • First observedmemory_update

TDQS

A4.4/5.0

Scored across 21 tools

Disambiguation4/5

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.

Naming Consistency5/5

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.

Tool Count3/5

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.

Completeness5/5

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

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    A 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 npm
    64
    MIT
  • A
    license
    B
    quality
    C
    maintenance
    Persistent 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.
    10
    23
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Persistent 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.
    54
    Apache 2.0
  • F
    license
    Not graded
    quality
    C
    maintenance
    Local-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.
    -