feat(jobs): exempt polling tools from loop tracker and hint on unchanged checks

This commit is contained in:
2026-08-25 18:17:15 -06:00
parent caabf41b65
commit 24ed674952
4 changed files with 171 additions and 3 deletions
+60
View File
@@ -2409,6 +2409,19 @@ fn polyfill_cmd_name<T: AsRef<Path>>(cmd_name: &str, bin_dir: &[T]) -> String {
cmd_name
}
// Polling tools are expected to repeat; recording them would also let them
// break up detection of a real loop in the calls they interleave with.
const LOOP_TRACKER_EXEMPT_TOOLS: [&str; 4] = [
"job__check",
"job__list",
"agent__check",
"agent__list_running",
];
fn is_loop_tracker_exempt(name: &str) -> bool {
LOOP_TRACKER_EXEMPT_TOOLS.contains(&name)
}
#[derive(Debug, Clone)]
pub struct ToolCallTracker {
last_calls: VecDeque<ToolCall>,
@@ -2430,6 +2443,9 @@ impl ToolCallTracker {
}
pub fn check_loop(&self, new_call: &ToolCall) -> Option<String> {
if is_loop_tracker_exempt(&new_call.name) {
return None;
}
if self.last_calls.len() < self.max_repeats {
return None;
}
@@ -2496,6 +2512,9 @@ impl ToolCallTracker {
}
pub fn record_call(&mut self, call: ToolCall) {
if is_loop_tracker_exempt(&call.name) {
return;
}
if self.last_calls.len() >= self.chain_len * self.max_repeats {
self.last_calls.pop_front();
}
@@ -2663,6 +2682,7 @@ mod tests {
})),
output_buf: Arc::new(parking_lot::Mutex::new(jobs::RingBuf::default())),
no_change_checks: 0,
last_check_state: None,
};
let mut sup = crate::supervisor::Supervisor::new(0, 3).with_max_concurrent_jobs(4);
sup.register(handle).unwrap();
@@ -3105,6 +3125,46 @@ mod tests {
assert!(msg.contains("repeat_tool"));
}
#[test]
fn tracker_exempt_tools_never_trip() {
for name in LOOP_TRACKER_EXEMPT_TOOLS {
let mut tracker = ToolCallTracker::default();
let exempt = call_with_args(name, json!({"id": "j1"}));
tracker.record_call(exempt.clone());
tracker.record_call(exempt.clone());
assert!(tracker.check_loop(&exempt).is_none());
let other = call_with_args("execute_command", json!({"command": "ls"}));
tracker.record_call(other.clone());
assert!(
tracker.check_loop(&other).is_none(),
"exempt calls must not count toward the repeat threshold"
);
tracker.record_call(other.clone());
assert!(tracker.check_loop(&other).is_some());
}
}
#[test]
fn tracker_exempt_interleave_does_not_mask_real_loop() {
let mut tracker = ToolCallTracker::default();
let x = call_with_args("execute_command", json!({"command": "ls"}));
tracker.record_call(call_with_args("job__check", json!({"id": "j1"})));
tracker.record_call(x.clone());
tracker.record_call(call_with_args("job__check", json!({"id": "j1"})));
tracker.record_call(x.clone());
assert!(tracker.check_loop(&x).is_some());
}
#[test]
fn tracker_non_exempt_behavior_unchanged() {
let mut tracker = ToolCallTracker::default();
let c = call_with_args("fs_cat", json!({"path": "a.txt"}));
tracker.record_call(c.clone());
tracker.record_call(c.clone());
assert!(tracker.check_loop(&c).is_some());
}
#[test]
fn prefix_constants_are_correct() {
assert_eq!(TODO_FUNCTION_PREFIX, "todo__");