Files
PolyTraderSharp/src/PolyTrader.Core/Agents/LoopGuard.cs
T
RichardandClaude Opus 4.8 bb103a578a Agenten-Kostendeckel: LoopGuard + PermissionGate aus ClawdDotNet uebernommen
- Neu PolyTrader.Core/Agents: AgentBudget (Steps/Tokens/Timeout), LoopGuard
  (thread-safe, AgentBudgetExceededException mit Kind), PermissionGate
  (Tool-Allow-List, null = alle erlaubt). Aus ClawdDotNet portiert, NICHT als
  Abhaengigkeit (.NET 10 vs 8).
- SupervisorAgent nutzt LoopGuard (Default-Steps = MaxIterations=8, rueckwaerts-
  kompatibel) + PermissionGate (Allow-List = angebotene Tools; nicht freigegebene
  Calls liefern Fehlertext statt Ausfuehrung). SupervisorProfile.Budget ueber-
  schreibt den Deckel. Abbruch graceful mit Grund (Steps/Tokens/Zeit).
- Tests: AgentGuardTests (LoopGuard/PermissionGate) + Token-Abbruch am Agenten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 20:29:47 +02:00

72 lines
2.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Diagnostics;
using System.Threading;
namespace PolyTrader.Core.Agents
{
/// <summary>Welche Budget-Dimension überschritten wurde.</summary>
public enum AgentBudgetKind
{
Steps,
Tokens,
Time
}
/// <summary>Wird geworfen, sobald eine Budget-Dimension eines Agenten-Laufs überschritten ist.</summary>
public sealed class AgentBudgetExceededException : Exception
{
public AgentBudgetKind Kind { get; }
public AgentBudgetExceededException(AgentBudgetKind kind, string message) : base(message)
{
Kind = kind;
}
}
/// <summary>
/// Erzwingt ein <see cref="AgentBudget"/> für EINEN Agenten-Lauf (nicht wiederverwenden pro Lauf neu
/// instanziieren). Aus ClawdDotNet übernommen und um ein Zeitbudget ergänzt. Thread-safe (Interlocked),
/// damit auch parallele Tool-Aufrufe sauber gezählt werden.
/// </summary>
public sealed class LoopGuard
{
private readonly AgentBudget _budget;
private readonly Stopwatch _clock = Stopwatch.StartNew();
private int _steps;
private int _tokens;
public LoopGuard(AgentBudget budget) => _budget = budget ?? AgentBudget.Default;
public int Steps => Volatile.Read(ref _steps);
public int Tokens => Volatile.Read(ref _tokens);
public TimeSpan Elapsed => _clock.Elapsed;
/// <summary>Zählt eine Modell-Runde. Wirft bei Überschreitung der Schrittzahl oder des Zeitbudgets.</summary>
public void RecordStep()
{
if (Interlocked.Increment(ref _steps) > _budget.MaxSteps)
throw new AgentBudgetExceededException(AgentBudgetKind.Steps,
$"Maximale Schrittzahl ({_budget.MaxSteps}) überschritten.");
ThrowIfExpired();
}
/// <summary>Addiert verbrauchte Tokens. Wirft bei Überschreitung des Token-Budgets.</summary>
public void RecordTokens(int count)
{
if (count <= 0) return;
int total = Interlocked.Add(ref _tokens, count);
if (total > _budget.MaxTokens)
throw new AgentBudgetExceededException(AgentBudgetKind.Tokens,
$"Maximales Token-Budget ({_budget.MaxTokens}) überschritten (verbraucht: {total}).");
}
/// <summary>Wirft, wenn das Zeitbudget abgelaufen ist.</summary>
public void ThrowIfExpired()
{
if (_clock.Elapsed > _budget.Timeout)
throw new AgentBudgetExceededException(AgentBudgetKind.Time,
$"Zeitbudget ({_budget.Timeout.TotalSeconds:0}s) überschritten.");
}
}
}