claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
71 lines • 2.99 kB
JavaScript
/**
* Tool-loop circuit breaker (hermes-agent tool_guardrails pattern).
*
* Detects an agent stuck repeating the same failing command — a real silent
* failure mode where a loop burns turns with no signal. This is ORTHOGONAL to
* the security `tool-output-guardrail` (which detects indirect prompt injection
* in tool *output*); this one watches the agent's own *call* pattern.
*
* Design (deterministic, in-memory, bounded):
* - `recordCommandOutcome` (called by post-command) appends (command, success)
* to a small ring buffer.
* - `checkCommandLoop` (called by pre-command) returns a verdict for a command
* about to run, based on its recent consecutive-failure streak.
* - Verdict is advisory by default (`warn`); callers decide whether to block.
* An orchestration layer that doesn't own the UI should not hard-stop.
*/
const MAX_HISTORY = 64;
const history = [];
/** Consecutive-failure thresholds for the same command (exact match). */
const WARN_AT = 3;
const BLOCK_AT = 5;
function normalize(command) {
return command.trim().replace(/\s+/g, ' ');
}
/** Record a command's outcome. Called from the post-command hook. */
export function recordCommandOutcome(command, success) {
history.push({ command: normalize(command), success, at: history.length });
if (history.length > MAX_HISTORY)
history.shift();
}
/** Count the trailing run of consecutive failures of `command` (exact match),
* stopping at the first success of that command or a gap. */
function consecutiveFailures(command) {
const norm = normalize(command);
let streak = 0;
for (let i = history.length - 1; i >= 0; i--) {
if (history[i].command !== norm)
continue; // ignore interleaved other commands
if (history[i].success)
break; // a success breaks the streak
streak++;
}
return streak;
}
/**
* Verdict for a command about to execute. Called from the pre-command hook.
* Returns the verdict, the failure streak, and a recovery hint when looping.
*/
export function checkCommandLoop(command) {
const fails = consecutiveFailures(command);
if (fails >= BLOCK_AT) {
return {
verdict: 'block',
consecutiveFailures: fails,
hint: `This exact command has failed ${fails}× in a row. Stop repeating it — inspect state first (e.g. \`pwd && ls -la\`), change the approach, or ask for help.`,
};
}
if (fails >= WARN_AT) {
return {
verdict: 'warn',
consecutiveFailures: fails,
hint: `This command has failed ${fails}× in a row. Before retrying, verify preconditions (paths, args, prior step output) rather than re-running the same call.`,
};
}
return { verdict: 'allow', consecutiveFailures: fails };
}
/** Test/reset helper — clears the in-memory history. */
export function _resetLoopHistory() {
history.length = 0;
}
//# sourceMappingURL=tool-loop-guardrail.js.map