summaryrefslogtreecommitdiff
path: root/src/Agent.Core/Tools/ToolUseGuard.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Agent.Core/Tools/ToolUseGuard.cs')
-rw-r--r--src/Agent.Core/Tools/ToolUseGuard.cs22
1 files changed, 22 insertions, 0 deletions
diff --git a/src/Agent.Core/Tools/ToolUseGuard.cs b/src/Agent.Core/Tools/ToolUseGuard.cs
new file mode 100644
index 0000000..633d34c
--- /dev/null
+++ b/src/Agent.Core/Tools/ToolUseGuard.cs
@@ -0,0 +1,22 @@
+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<string, int> _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, "Required external effect was not completed after repeated identical failures.", count);
+ return new(ToolUseGuardDecision.Retry, "No verified external effect occurred; perform the required operation using an internal tool.", count);
+ }
+}