Ga naar inhoud

Toolcontext en promptcaching

Voor elk verzoek dat de ingebouwde harness doet, geldt twee dingen: de tooldefinities zijn er de grootste vaste kostenpost, en bijna geen ervan wordt gebruikt. Deze pagina legt uit wat Control Center daaraan doet, en waarom het antwoord niet simpelweg “stuur minder tools” is.

Control Center registreert ongeveer 126 tools. Volledig meegestuurd kosten hun definities grofweg 23.000 tokens bij elk verzoek — een kostenpost waar geen enkele compactie iets aan kan veranderen, omdat tooldefinities in een ander deel van het verzoek meereizen dan het gesprek.

De tokenrekening is de voor de hand liggende klacht, en met promptcaching is die ook de kleinere. De echte kosten zijn de selectienauwkeurigheid. Gepubliceerde evaluaties van tool-aanroepen zijn het erover eens dat modellen de juiste tool betrouwbaar kiezen tot ergens rond de 30–50 tools in de context, en daarboven geleidelijk slechter worden. De genoemde oorzaak is niet het aantal tokens maar semantische overlap: zodra een catalogus meerdere tools bevat waarvan de beschrijvingen op elkaar lijken, kiest het model tussen dubbelgangers in plaats van zich een capaciteit te herinneren.

Bij 126 tools zit een agent bij elke afzonderlijke beurt ruim voorbij die band — ook in de vele beurten waarin hij alleen maar een bestand hoefde te lezen.

Een uitvoering krijgt daarom twee dingen te zien:

  • een residente set — ongeveer twee dozijn tools, die bij elk verzoek met volledige schema’s wordt meegestuurd;
  • een naamindex van al het overige, gegroepeerd onder kopjes als “Tickets and projects” of “Memory and knowledge”, die voor de hele catalogus een paar honderd tokens kost.

Gemeten op de huidige catalogus brengt dat het toolblok van ~23,1k tokens naar ~5,0k, ongeveer 78% kleiner, en zet het de altijd zichtbare set binnen de band waarin de selectie betrouwbaar is.

De residente set bestaat uit de tools die een uitvoering in de meeste uitvoeringen gebruikt: het bestands- en shellvocabulaire (read, write, edit, search, bash, …), de discovery-tools, de takenchecklist, geheugenzoekopdrachten, en welk werkwoord de huidige modus ook moet aanroepen om zijn output te leveren. Het eigen outputwerkwoord van een modus is altijd resident — een uitvoering mag nooit hoeven zoeken naar de ene aanroep die zijn resultaat oplevert.

Dit is het deel dat het ontwerp veilig maakt in plaats van alleen maar goedkoop. Een uitgestelde tool is vanaf de eerste beurt aanroepbaar. Alleen het schema wordt achtergehouden.

De agent ziet de naam in zijn prompt. Als hij die naam rechtstreeks aanroept, laadt de harness het schema en voert hij de tool in dezelfde stap uit — geen fout, geen extra verzoek. Het model hoeft niet te weten dat de tool ooit “uitgeladen” is geweest.

Wanneer de agent de taak kent maar de naam niet, neemt search_tools een beschrijving in gewone taal (“wijs een ticket aan iemand toe”), rangschikt het hele oppervlak van de uitvoering en geeft het de treffers terug met hun schema’s al geladen, zodat de eerstvolgende beurt er een kan aanroepen. Als een zoekopdracht niets vindt, zegt die dat duidelijk en stelt die een andere formulering of een bredere zoekopdracht voor — bewust, want een agent die een lege zoekopdracht leest als “deze capaciteit bestaat niet” verzint een vervanging in plaats van nog eens te kijken.

search_tools bestaat alleen in de ingebouwde harness. Externe MCP-clients hebben in plaats daarvan search_tool_bm25, dat dezelfde index rangschikt maar treffers alleen beschrijft — die clients hebben elk schema al via tools/list ontvangen, dus er valt niets te laden.

Uitstelling wordt na het toolfilter van de modus toegepast, nooit in plaats daarvan. Ze verbergt een schema; ze kan geen tool tonen die de modus heeft geweigerd. Een geactiveerde tool doorloopt nog steeds de goedkeuringsprompt en de actiebeveiligingen precies zoals een residente dat zou doen, en een tool kan geen naam laden die niet vanaf het begin op het oppervlak van de uitvoering stond.

Het MCP-oppervlak dat externe clients zien, blijft onaangetast. tools/list geeft nog steeds de volledige catalogus terug. Een eerdere poging om die in te korten legde agenten volledig plat: MCP-clients valideren toolnamen tegen hun eigen gecachte lijst en weigeren alles wat daar niet op staat, zonder ooit de server te vragen, dus een “verborgen maar aanroepbare” tool was simpelweg onbereikbaar. Uitstelling zit in de harness, waar het laden van een tool midden in een uitvoering iets is dat de harness daadwerkelijk kan doen.

--tool-deferral=off (of CC_SERVER_TOOL_DEFERRAL=off) maakt elke toegelaten tool weer resident en produceert precies de verzoeken die de server deed voordat uitstelling bestond. Zie de cc_server CLI-referentie.

De werklast van agents is scheef verdeeld: ze sturen enorme prompts en ontvangen kleine antwoorden, beurt na beurt, met elke keer hetzelfde voorvoegsel. Providers cachen dat voorvoegsel, en een lezing uit de cache kost ongeveer een tiende van een verse. Het cachepercentage is daarom een eersteklas eigenschap van de harness in plaats van een optimalisatie — en bij Anthropic levert het ook doorvoer op, omdat gecachte tokens niet meetellen voor de invoerlimiet per minuut.

Caching werkt op een exacte overeenkomst van het voorvoegsel, wat één overkoepelende regel oplevert.

Elke byte die verandert, maakt de cache vanaf dat punt ongeldig. Daarom doet de harness alleen maar toevoegen: nieuwe berichten aan het einde, en nieuw geactiveerde toolschema’s na het gecachte toolblok in plaats van erin opgenomen. Die ordening is de reden dat activering goedkoop is — het gecachte voorvoegsel blijft identiek en de uitvoering betaalt alleen voor de schema’s die ze net heeft geladen.

De lus kort ook verouderde tooluitvoer uit oudere beurten in om context terug te winnen. Hij wacht tot er genoeg terug te winnen valt om het herschrijven waard te zijn, zodat een inkorting een zeldzame diepe herschrijving is in plaats van een voortdurende cachemiss.

Een provider staat een beperkt aantal cachemarkeringen toe, en Control Center besteedt die met opzet:

Markering Dekt Levensduur
Laatste residente tool Het toolblok 1 uur
Laatste systeemblok Tools + systeemprompt 1 uur
Staart van de vorige beurt Geschiedenis tot het laatste verzoek 5 min
Staart van de huidige beurt De volledige geschiedenis 5 min

De laatste twee zijn een rollend paar, en de reden voor dat paar is subtiel: een cache-opzoeking zoekt een beperkt aantal blokken terug naar iets wat een eerder verzoek heeft opgeslagen. Een enkele beurt die veel tools parallel uitvoert, kan meer blokken uitzenden dan dat venster, en dan mist één staartmarkering stilletjes en betaalt het verzoek de volle prijs, zonder dat iets in het antwoord dat zegt. Door exact de plek te markeren waar het vorige verzoek stopte, wordt de treffer structureel in plaats van een race.

Het voorvoegsel van tools en systeem krijgt de lange levensduur omdat het constant is gedurende de hele uitvoering, gedeeld wordt door elke uitvoering en subagent met dezelfde vorm, en omdat de klok begint te lopen wanneer een verzoek begint — een beurt die enkele minuten streamt, heeft het grootste deel van een kort venster al verbruikt voordat het volgende verzoek ontstaat.

Subagenten van hetzelfde type produceren byte-identieke promptvoorvoegsels, dus zouden ze één cache-item moeten lezen in plaats van dat elk hun eigen item schrijft. Maar een item wordt pas leesbaar zodra het verzoek dat het heeft geschreven begint te antwoorden, dus bij een fan-out die in één keer wordt gestart, betaalt elk kind de premie voor een voorvoegsel dat ze allemaal delen.

De harness laat het eerste kind van een bepaalde vorm starten en laat de andere kinderen pas los zodra het begint te streamen. De wachttijd is begrensd: een traag eerste kind kost een cachehit, nooit de fan-out zelf. Om dezelfde reden krijgt elke subagent dezelfde residente toolset, ongeacht zijn profiel — die per profiel variëren zou het gedeelde voorvoegsel versnipperen zonder enig voordeel, aangezien het profiel al heeft bepaald wat het kind mag aanroepen.

Beide helften zijn waarneembaar in plaats van aangenomen:

  • Context bij een gesprek toont de residente tools, de uitgestelde index en wat elk achtergehouden schema zou kosten, opgebouwd uit hetzelfde codepad dat een echte uitvoering gebruikt.
  • Elke uitvoering legt vast welke uitgestelde tools ze heeft geladen en wat de aanleiding was, zodat een zoekopdracht zonder treffer zichtbaar blijft als een gemiste ophaling in plaats van te verdwijnen in een agent die stilletjes iets aanverwants doet.
  • Observeerbaarheid rapporteert het Cache-percentage als het aantal cachelezingen gedeeld door alle verwerkte prompttokens, cache-schrijfacties inbegrepen — een uitvoering die haar voorvoegsel steeds opnieuw opbouwt, scoort laag, in plaats van te scoren als een uitvoering die nooit een cache nodig had.