Skip to main content

Cassandra

Remembers what already failed, and says so before your agent tries it again.

Inside one context an agent can still see its own failure. After a compaction, in a new session, or in a freshly spawned subagent it is gone, and the whole thing starts over.

What it does

Cassandra is a Claude Code plugin that watches the tool-call lifecycle. When a Bash or mcp__* call fails, it records it. When your agent later runs that same call again with nothing in the project changed, the model gets one line to read: this was tried before, and here is what came back.

Recognition works on a fingerprint, not on prose. The hash covers tool_name and tool_input, both structured JSON. A Bash command is trimmed and runs of whitespace are collapsed, and nothing else: no path canonicalisation, no flag reordering, no stripping of trailing pipes. Every one of those steps can quietly merge two different commands into one.

Edit and Write are left out. Those payloads never repeat byte for byte, even when the edit is functionally the same fix twice, so indexing them would build an index that never matches.

Where it earns its place

Inside one intact context this is close to redundant, and the plugin says so itself. The model can see its own failure a few thousand tokens back in the transcript and corrects course unaided. Cassandra is built for the three boundaries where that transcript is gone:

  • After a compaction. The failed call drops out of context. The record does not.
  • In a new session. Yesterday's dead end is invisible today.
  • In a subagent. It has no idea the main agent already burned four calls on this.

Outside those boundaries it deliberately stays quiet.

Getting started

bash
claude plugin marketplace add https://aranea-development.nl/plugins/marketplace.json
claude plugin install cassandra@aranea

Hooks bind when a session starts, and the hook binary is not in the repository. The first session after install builds it in the background and says so; from the second session onward the plugin is active. That binary is around 79 MB, because bun build --compile embeds the Bun runtime, which is exactly why it is built on first use instead of shipped. Without Bun on your PATH the plugin stays inert and says so once, at session start.

How it decides whether to warn

Three things happen on PreToolUse. The call is fingerprinted and the hash looked up; a miss, which is the overwhelming majority, exits silently. On a hit the freshness probe runs. If the workspace has moved since the failure, the retry is legitimate and Cassandra stays silent. Only when nothing has changed does that one line appear.

In a git repository the probe is a hash of HEAD plus git status --porcelain. Outside git it falls back to a bounded walk over the modification times of the working directory. If it cannot tell either way it stays silent instead of guessing, because a wrong warning is worse than a missed one.

There are two gaps and the plugin does not hide them. A file rewritten to different content of the same length, with its modification time restored afterwards, escapes the mtime path. And the probe only ever sees the project: a fix that lands somewhere else, a package installed globally, an environment variable, leaves the stamp identical. So the warning names what was actually checked, "nothing in this repository has changed", which keeps the claim true even where the probe is blind.

What it costs, and what you get for it

Roughly 12ms per hook invocation on an idle machine, 17ms under load, against a design budget of 20ms. But a tool call is not one hook invocation: PreToolUse and PostToolUse hang off the same matcher, so a successful call spawns the binary twice, about 25ms together. Measured per invocation that fits the budget. Measured per successful call it does not.

That second invocation is a choice. PostToolUse resolves the pending marker, and the marker is the only way Cassandra can see that a call it warned about went on to succeed anyway. Remove the hook and the false-positive rate in cassandra stats stops existing, which is the very number that tells you whether the freshness probe is any good.

cassandra stats reports two figures. The false-positive rate says whether the probe needs work. The share of warnings where no boundary was crossed at all says something else: there Cassandra is telling the model what it could already see, and the signal is to uninstall it rather than tune it. A tool that tells you when you do not need it is rare enough to point out.

What it does not do

It blocks nothing. It denies nothing, rewrites nothing, and the only thing it can add is one line of additionalContext. It reads neither your prompts nor the model's prose.

It does keep one piece of free text, and that is worth knowing about: a 240-character excerpt of the tool's own error message or denial reason, so the warning shows you why the call died last time. That excerpt is output from a command, not from you and not from the model, and it is treated as untrusted: control characters are stripped before it is stored, and in the warning it sits fenced and labelled as tool output rather than as an instruction.

Beyond that nothing leaves your machine. No network traffic, no telemetry, no API key, and nothing outside its own data directory.