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:
firat.sertgoz
2026-04-23 08:50:35 +03:00
committed by GitHub
parent 009d3cd82f
commit b5ba7496f0
2 changed files with 133 additions and 1 deletions

View File

@@ -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):

View File

@@ -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