Naar inhoud

Cassandra

Onthoudt wat al mislukte, en zegt het voordat je agent het nog eens probeert.

Binnen één context ziet een agent zijn eigen mislukking nog staan. Na een compactie, in een nieuwe sessie of in een verse subagent is die weg, en begint het opnieuw.

Wat het doet

Cassandra is een Claude Code-plugin die meekijkt op de levensloop van een tool-aanroep. Mislukt een Bash- of mcp__*-aanroep, dan legt ze die vast. Draait je agent later dezelfde aanroep opnieuw terwijl er in het project niets veranderd is, dan krijgt het model één regel te lezen: dit is eerder geprobeerd, dit kwam eruit.

Herkennen gaat op een vingerafdruk, niet op tekst. De hash gaat over tool_name en tool_input, allebei gestructureerde JSON. Een Bash-commando wordt getrimd en opeenvolgende spaties worden samengevouwen, verder niets: geen paden normaliseren, geen vlaggen herschikken, geen afsluitende pipes weghalen. Elk van die stappen kan twee verschillende commando's stilletjes tot één maken.

Edit en Write doet ze niet. Die payloads herhalen zich nooit byte voor byte, ook niet als het functioneel dezelfde correctie is, dus indexeren zou een index opleveren die nooit raak schiet.

Waar ze haar plek verdient

Binnen één intacte context is dit bijna overbodig, en dat zegt de plugin zelf ook. Het model ziet zijn eigen mislukking een paar duizend tokens terug in het transcript staan en corrigeert zonder hulp. Cassandra is gebouwd voor de drie grenzen waar dat transcript verdwenen is:

  • Na een compactie. De mislukte aanroep valt uit de context, het record niet.
  • In een nieuwe sessie. De doodlopende weg van gisteren is vandaag onzichtbaar.
  • In een subagent. Die weet niet dat de hoofdagent er al vier aanroepen aan verspild heeft.

Buiten die grenzen houdt ze bewust haar mond.

Snel starten

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

Hooks binden bij het starten van een sessie, en de hook-binary staat niet in de repository. De eerste sessie na installatie bouwt haar op de achtergrond en zegt dat; vanaf de tweede sessie is de plugin actief. Die binary is ongeveer 79 MB, omdat bun build --compile de Bun-runtime meebakt, en dat is precies waarom ze gebouwd wordt in plaats van meegeleverd. Zonder Bun op je PATH blijft de plugin inert en meldt dat één keer, bij de start van de sessie.

Loopt de installatie vast op ssh: connect to host github.com port 22, dan haalt git de plugin uit de GitHub-repository over SSH en lukt dat op die machine niet. De melding wijst naar toegangsrechten, maar de repository is openbaar. Het transport is het probleem. Eén regel zet git op HTTPS, daarna kun je opnieuw installeren:

bash
git config --global --add url."https://github.com/".insteadOf "git@github.com:"

In de README staat de uitgebreide versie, inclusief hoe je het terugdraait.

Hoe ze beslist of ze waarschuwt

Bij PreToolUse gebeuren er drie dingen. De aanroep krijgt een vingerafdruk en die wordt opgezocht; een misser, wat de overgrote meerderheid is, eindigt zwijgend. Bij een treffer draait de versheidstoets. Is er sinds de mislukking iets veranderd, dan is de herhaling terecht en blijft Cassandra stil. Pas als er niets veranderd is, komt die ene regel.

Die versheidstoets is in een git-repository een hash van HEAD plus git status --porcelain. Daarbuiten valt ze terug op een begrensde wandeling over de wijzigingstijden van de werkmap. Kan ze het niet uitmaken, dan zwijgt ze in plaats van te gokken, want een onterechte waarschuwing is erger dan een gemiste.

Er zijn twee gaten en de plugin verzwijgt ze niet. Een bestand dat herschreven wordt naar andere inhoud van dezelfde lengte, met de wijzigingstijd achteraf teruggezet, ontsnapt aan de mtime-variant. En de toets kijkt alleen naar het project: een oplossing die ergens anders landt, een pakket dat globaal geïnstalleerd wordt, een omgevingsvariabele, laat de stempel identiek. Daarom noemt de waarschuwing wat er werkelijk gecontroleerd is, "er is niets veranderd in deze repository", zodat de bewering waar blijft ook waar de toets blind is.

Wat het kost, en wat je ervoor terugkrijgt

Ongeveer 12 ms per hook-aanroep op een rustige machine, 17 ms onder belasting, tegen een ontworpen budget van 20 ms. Maar een tool-aanroep is geen enkele hook-aanroep: PreToolUse en PostToolUse hangen aan dezelfde matcher, dus een geslaagde aanroep start de binary twee keer, samen ongeveer 25 ms. Per aanroep gemeten past dat binnen het budget, per geslaagde tool-aanroep niet.

Die tweede aanroep is een keuze. PostToolUse lost de openstaande markering op, en dat is de enige manier waarop Cassandra kan zien dat een aanroep waarvoor ze waarschuwde daarna gewoon slaagde. Haal die hook weg en het percentage onterechte waarschuwingen in cassandra stats bestaat niet meer, en dat is nu juist het getal waaraan je afleest of de versheidstoets deugt.

cassandra stats toont twee getallen. Het aandeel onterechte waarschuwingen zegt of de toets verbetering nodig heeft. Het aandeel waarschuwingen waarbij er helemaal geen grens gepasseerd werd, zegt iets anders: dan vertelt Cassandra het model dingen die het zelf al kon zien, en het signaal is om haar te verwijderen, niet om haar af te stellen. Een tool die zegt wanneer je haar niet nodig hebt is zeldzaam genoeg om te noemen.

Wat het niet doet

Ze blokkeert niets. Ze weigert niets, herschrijft niets, en het enige wat ze kan toevoegen is één regel additionalContext. Ze leest je prompts niet en het proza van het model evenmin.

Eén stuk vrije tekst bewaart ze wel, en dat is het waard om te weten: een fragment van 240 tekens uit de foutmelding of weigeringsreden van de tool zelf, zodat je in de waarschuwing terugziet waarom de aanroep de vorige keer stierf. Dat fragment komt uit een commando, niet uit jou en niet uit het model, en wordt als onvertrouwd behandeld: stuurtekens gaan eruit voordat het wordt opgeslagen, en in de waarschuwing staat het tussen hekjes met het label tool-uitvoer, niet als instructie.

Verder verlaat er niets je machine. Geen netwerkverkeer, geen telemetrie, geen API-sleutel, en niets buiten haar eigen datamap.