mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:20:43 +00:00
fix(agents): address review feedback on post-compaction loop guard
- Add PostCompactionLoopPersistedError.fromVerdict factory. - Add unit tests for the error class + fromVerdict adapter. - Disabled guard is now truly dormant (no state mutation when enabled=false). - Tighten help text for postCompactionGuard.enabled. Refs #77474
This commit is contained in:
committed by
Peter Steinberger
parent
96e7461c81
commit
5b863c719e
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
createPostCompactionLoopGuard,
|
||||
PostCompactionLoopPersistedError,
|
||||
type PostCompactionLoopGuard,
|
||||
} from "./post-compaction-loop-guard.js";
|
||||
|
||||
@@ -111,3 +112,37 @@ describe("createPostCompactionLoopGuard", () => {
|
||||
expect(guard.snapshot().remainingAttempts).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PostCompactionLoopPersistedError", () => {
|
||||
it("captures the detector, count, toolName, and message", () => {
|
||||
const err = new PostCompactionLoopPersistedError("loop persisted", {
|
||||
detector: "compaction_loop_persisted",
|
||||
count: 4,
|
||||
toolName: "gateway",
|
||||
});
|
||||
expect(err).toBeInstanceOf(Error);
|
||||
expect(err).toBeInstanceOf(PostCompactionLoopPersistedError);
|
||||
expect(err.name).toBe("PostCompactionLoopPersistedError");
|
||||
expect(err.message).toBe("loop persisted");
|
||||
expect(err.detector).toBe("compaction_loop_persisted");
|
||||
expect(err.count).toBe(4);
|
||||
expect(err.toolName).toBe("gateway");
|
||||
});
|
||||
|
||||
it("can be built from a guard verdict via fromVerdict", () => {
|
||||
const guard = createPostCompactionLoopGuard({ windowSize: 2 });
|
||||
guard.armPostCompaction();
|
||||
guard.observe(callOutcome("read", { path: "/x" }, "r1"));
|
||||
const verdict = guard.observe(callOutcome("read", { path: "/x" }, "r1"));
|
||||
expect(verdict.shouldAbort).toBe(true);
|
||||
if (!verdict.shouldAbort) {
|
||||
throw new Error("verdict was expected to abort");
|
||||
}
|
||||
const err = PostCompactionLoopPersistedError.fromVerdict(verdict);
|
||||
expect(err).toBeInstanceOf(PostCompactionLoopPersistedError);
|
||||
expect(err.detector).toBe(verdict.detector);
|
||||
expect(err.count).toBe(verdict.count);
|
||||
expect(err.toolName).toBe(verdict.toolName);
|
||||
expect(err.message).toBe(verdict.message);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,6 +62,9 @@ export function createPostCompactionLoopGuard(
|
||||
};
|
||||
|
||||
const observe = (call: PostCompactionGuardObservation): PostCompactionGuardVerdict => {
|
||||
if (!state.enabled) {
|
||||
return { shouldAbort: false, armed: false, remainingAttempts: 0 };
|
||||
}
|
||||
if (state.remainingAttempts <= 0) {
|
||||
return { shouldAbort: false, armed: false, remainingAttempts: 0 };
|
||||
}
|
||||
@@ -69,10 +72,6 @@ export function createPostCompactionLoopGuard(
|
||||
state.history.push(call);
|
||||
const armedAfter = state.remainingAttempts > 0;
|
||||
|
||||
if (!state.enabled) {
|
||||
return { shouldAbort: false, armed: armedAfter, remainingAttempts: state.remainingAttempts };
|
||||
}
|
||||
|
||||
const matches = state.history.filter(
|
||||
(entry) =>
|
||||
entry.toolName === call.toolName &&
|
||||
@@ -125,4 +124,14 @@ export class PostCompactionLoopPersistedError extends Error {
|
||||
this.count = details.count;
|
||||
this.toolName = details.toolName;
|
||||
}
|
||||
|
||||
static fromVerdict(
|
||||
verdict: Extract<PostCompactionGuardVerdict, { shouldAbort: true }>,
|
||||
): PostCompactionLoopPersistedError {
|
||||
return new PostCompactionLoopPersistedError(verdict.message, {
|
||||
detector: verdict.detector,
|
||||
count: verdict.count,
|
||||
toolName: verdict.toolName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,7 +659,7 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"Enable known poll tool no-progress loop detection (default: true).",
|
||||
"tools.loopDetection.detectors.pingPong": "Enable ping-pong loop detection (default: true).",
|
||||
"tools.loopDetection.postCompactionGuard.enabled":
|
||||
"Enable the post-compaction loop guard (default: true). When the runner has just retried a prompt after auto-compaction, this guard aborts the run if the agent emits the same (tool, args, result) windowSize times. Targets the failure mode where context-overflow + compaction does not break a tool-call loop.",
|
||||
"Enable the post-compaction loop guard that aborts the run when the agent repeats the same (tool, args, result) triple windowSize times immediately after auto-compaction-retry (default: true).",
|
||||
"tools.loopDetection.postCompactionGuard.windowSize":
|
||||
"Number of post-compaction attempts during which the guard stays armed (default: 3). Lower values are stricter; higher values give the agent more attempts before abort.",
|
||||
"tools.exec.notifyOnExit":
|
||||
|
||||
Reference in New Issue
Block a user