Der offene Rest aus #682: Jeder Lauf pruefte das Budget einzeln gegen den
bisher erfassten Verbrauch. Laufende Laeufe waren unsichtbar, gleichzeitig
startende passierten die Pruefung gemeinsam und ueberzogen gemeinsam.
- BudgetGuard.ReserveAsync prueft und reserviert unter einer Sperre. Die
Reservierung ist der Mittelwert der letzten 20 Laeufe des Agenten (Laeufe
ohne Verbrauch zaehlen nicht mit) und gilt, bis der Verbrauch des Laufs
erfasst ist.
- Gegen die Grenze zaehlt: verbraucht + in laufenden Laeufen reserviert. Die
eigene Schaetzung eines Laufs zaehlt bewusst nicht mit; ein einzelner Lauf
wird weiter nur abgelehnt, wenn das Budget schon verbraucht ist. Sonst fiele
eine kleine Aufgabe wegen eines grossen Durchschnitts aus.
- Gilt fuer Kosten- und Tokengrenze, je Agent und je Instanz.
- Die Ablehnungsmeldung nennt den reservierten Anteil, sonst wirkte "0,60 von
1,00 USD verbraucht" als Ablehnungsgrund falsch.
- IUsageRepository.GetRecentForAgentAsync, mit leerer Vorgabe im Interface,
damit bestehende Attrappen unberuehrt bleiben.
Tests: BudgetReservationTests (8 neu), darunter zehn gleichzeitige Starts, von
denen genau vier passieren. 1.246 gruen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>