namespace Agent.Core.Tools; public enum ToolUseGuardDecision { Complete, Retry, Fail } public sealed record ToolUseGuardResult(ToolUseGuardDecision Decision, string Observation, int IdenticalFailureCount); public sealed class ToolUseGuard(int sameFailureLimit = 5) { private readonly Dictionary _failures = new(StringComparer.Ordinal); public int SameFailureLimit { get; } = sameFailureLimit > 0 ? sameFailureLimit : throw new ArgumentOutOfRangeException(nameof(sameFailureLimit)); public ToolUseGuardResult Evaluate(bool requiresExternalEffect, bool hadToolCall, bool hasExternalEffectEvidence, string? failureFingerprint = null) { if (!requiresExternalEffect) return new(ToolUseGuardDecision.Complete, string.Empty, 0); if (hasExternalEffectEvidence) return new(ToolUseGuardDecision.Complete, string.Empty, 0); string fingerprint = string.IsNullOrWhiteSpace(failureFingerprint) ? "missing-external-effect" : failureFingerprint; int count = _failures.TryGetValue(fingerprint, out int prior) ? prior + 1 : 1; _failures[fingerprint] = count; if (count >= SameFailureLimit) return new(ToolUseGuardDecision.Fail, "The required operation was not verified after repeated identical failures.", count); return new(ToolUseGuardDecision.Retry, "Required Tool evidence is missing; perform the current operation using the required Internal Tool.", count); } }