mirror of
https://github.com/nearai/ironclaw.git
synced 2026-09-02 23:56:24 +08:00
fix(engine): enforce tool use for stop/pause/cancel commands (#2814)
* fix(engine): enforce tool use for stop/pause/cancel commands (#2808) The LLM was narrating about calling mission_pause/mission_list instead of actually executing them because neither the tool-intent nudge nor the execution obligation recognized stop/pause/cancel as action commands. - Add stop/pause/cancel/halt/disable to signals_tool_intent ACTION_VERBS so the nudge fires when the LLM says "I'll pause the mission" - Add stop/pause/cancel phrases to signals_execution_intent EXEC_PHRASES so the obligation system forces tool calls for "stop it", "pause the X" - Add bare imperative detection (startswith) for "stop", "stop pinging", "pause", "cancel" — avoids false positives like "I can't stop" - Add 5 regression tests covering true positives and false negatives Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review findings (iteration 1) - Add missing "please halt " to EXEC_PHRASES for consistency with please stop/pause/cancel - Strip trailing punctuation from bare commands so "Stop." and "cancel!" are detected - Add 2 regression tests covering both fixes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): address gemini-code-assist review — halt/disable consistency (#2814) - Add "halt it/that/this/the" and "disable it/that/this/the" to EXEC_PHRASES for consistency with signals_tool_intent - Add "please disable " to polite execution phrases - Add "disable" to BARE_COMMANDS and IMPERATIVE_STARTS - Add regression test for halt/disable execution intent phrases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -126,6 +126,7 @@ def signals_tool_intent(text):
|
||||
"read the", "write the", "create", "run the", "execute",
|
||||
"query", "retrieve", "add it", "add the", "add this",
|
||||
"add that", "update the", "delete", "remove the", "look into",
|
||||
"stop", "pause", "cancel", "halt", "disable",
|
||||
]
|
||||
|
||||
for prefix in PREFIXES:
|
||||
@@ -161,10 +162,33 @@ def signals_execution_intent(text):
|
||||
"ship it", "deploy it", "deploy that", "deploy this", "deploy the ",
|
||||
"send it", "send that", "send the ",
|
||||
"fetch it", "fetch that", "fetch the ",
|
||||
"stop it", "stop that", "stop this", "stop the ",
|
||||
"pause it", "pause that", "pause this", "pause the ",
|
||||
"cancel it", "cancel that", "cancel this", "cancel the ",
|
||||
"halt it", "halt that", "halt this", "halt the ",
|
||||
"disable it", "disable that", "disable this", "disable the ",
|
||||
"please run ", "please execute ", "please fetch ",
|
||||
"please send ", "please deploy ",
|
||||
"please stop ", "please pause ", "please cancel ",
|
||||
"please halt ", "please disable ",
|
||||
]
|
||||
return any(phrase in lower for phrase in EXEC_PHRASES)
|
||||
if any(phrase in lower for phrase in EXEC_PHRASES):
|
||||
return True
|
||||
|
||||
# Bare imperative commands at the start of the message.
|
||||
# "stop pinging", "stop", "pause", "cancel" are unambiguous commands
|
||||
# that don't match the "verb + pronoun/article" pattern above.
|
||||
# Checking startswith avoids false positives like "I can't stop".
|
||||
# Strip trailing punctuation so "Stop." and "cancel!" still match.
|
||||
trimmed = lower.strip().rstrip(".,!?;:")
|
||||
IMPERATIVE_STARTS = ["stop ", "pause ", "cancel ", "halt ", "disable "]
|
||||
BARE_COMMANDS = ["stop", "pause", "cancel", "halt", "disable"]
|
||||
if trimmed in BARE_COMMANDS:
|
||||
return True
|
||||
if any(trimmed.startswith(s) for s in IMPERATIVE_STARTS):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def format_output(result, max_chars=8000):
|
||||
|
||||
@@ -3096,6 +3096,114 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
// ── Stop / pause / cancel intent (mission lifecycle) ─────────
|
||||
|
||||
#[test]
|
||||
fn signals_tool_intent_stop_pause_cancel() {
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_tool_intent("I'll stop the mission now.")"#
|
||||
));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_tool_intent("Let me pause the ticker.")"#
|
||||
));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_tool_intent("I'll cancel the monitoring.")"#
|
||||
));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_tool_intent("I'm going to halt the recurring task.")"#
|
||||
));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_tool_intent("I will disable the mission.")"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signals_tool_intent_no_false_positive_stop_discussion() {
|
||||
// "let me explain" is in EXCLUSIONS — blocks the entire text
|
||||
assert!(!eval_python_bool(
|
||||
r#"signals_tool_intent("Let me explain how to stop the mission.")"#
|
||||
));
|
||||
// Past tense should not trigger
|
||||
assert!(!eval_python_bool(
|
||||
r#"signals_tool_intent("I already stopped the mission.")"#
|
||||
));
|
||||
}
|
||||
|
||||
// ── Execution intent: stop / pause / cancel ────────────────
|
||||
|
||||
#[test]
|
||||
fn signals_execution_intent_stop_pause_cancel() {
|
||||
assert!(eval_python_bool(r#"signals_execution_intent("stop it")"#));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_execution_intent("pause the mission")"#
|
||||
));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_execution_intent("cancel that")"#
|
||||
));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_execution_intent("please stop the ticker")"#
|
||||
));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_execution_intent("please pause everything")"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signals_execution_intent_bare_stop() {
|
||||
// Bare imperative commands — the exact user messages from #2808
|
||||
assert!(eval_python_bool(r#"signals_execution_intent("stop")"#));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_execution_intent("stop pinging")"#
|
||||
));
|
||||
assert!(eval_python_bool(r#"signals_execution_intent("pause")"#));
|
||||
assert!(eval_python_bool(r#"signals_execution_intent("cancel")"#));
|
||||
assert!(eval_python_bool(r#"signals_execution_intent("halt")"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signals_execution_intent_no_false_positive_stop_in_sentence() {
|
||||
// "stop" mid-sentence should NOT trigger — only at the start
|
||||
assert!(!eval_python_bool(
|
||||
r#"signals_execution_intent("I can't stop thinking about it")"#
|
||||
));
|
||||
assert!(!eval_python_bool(
|
||||
r#"signals_execution_intent("how do I stop a mission?")"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signals_execution_intent_halt_disable_phrases() {
|
||||
// "halt/disable" pronoun+article phrases and "please halt/disable"
|
||||
assert!(eval_python_bool(r#"signals_execution_intent("halt that")"#));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_execution_intent("halt the mission")"#
|
||||
));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_execution_intent("disable it")"#
|
||||
));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_execution_intent("disable the ticker")"#
|
||||
));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_execution_intent("please halt the mission")"#
|
||||
));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_execution_intent("please disable the routine")"#
|
||||
));
|
||||
// Bare "disable" command
|
||||
assert!(eval_python_bool(r#"signals_execution_intent("disable")"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signals_execution_intent_bare_stop_with_punctuation() {
|
||||
// Bare commands with trailing punctuation must still match
|
||||
assert!(eval_python_bool(r#"signals_execution_intent("stop.")"#));
|
||||
assert!(eval_python_bool(r#"signals_execution_intent("cancel!")"#));
|
||||
assert!(eval_python_bool(
|
||||
r#"signals_execution_intent("stop pinging.")"#
|
||||
));
|
||||
}
|
||||
|
||||
// ── Skill activation: smart-quote / autocorrect resilience ───
|
||||
//
|
||||
// Regression for the ceo-setup non-activation report. iOS / macOS / most
|
||||
|
||||
Reference in New Issue
Block a user