Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions agent/system_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,10 +251,21 @@ def build_system_prompt_parts(agent: Any, system_message: Optional[str] = None)
stable_parts.append(GOOGLE_MODEL_OPERATIONAL_GUIDANCE)
# OpenAI GPT/Codex execution discipline (tool persistence,
# prerequisite checks, verification, anti-hallucination).
# Also applied to xAI Grok — same failure modes (claims completion
# without tool calls, suggests workarounds instead of using
# existing tools, replies with plans instead of executing).
if "gpt" in _model_lower or "codex" in _model_lower or "grok" in _model_lower:
# Also applied to xAI Grok and the broader non-Google tool-use-
# enforcement set — the same failure modes appear on GLM,
# DeepSeek, Qwen (claims completion without tool calls, suggests
# workarounds instead of using existing tools, replies with plans
# or plain-text ``[TOOL_CALL]`` markers instead of executing via
# the structured tool_calls channel). The OPENAI_ prefix
# reflects origin, not exclusivity — see the comment in
# ``prompt_builder.OPENAI_MODEL_EXECUTION_GUIDANCE``. Google
# models are skipped because they get the more specific
# ``GOOGLE_MODEL_OPERATIONAL_GUIDANCE`` block above.
_openai_exec_families = tuple(
m for m in TOOL_USE_ENFORCEMENT_MODELS
if m not in ("gemini", "gemma")
)
if any(fam in _model_lower for fam in _openai_exec_families):
stable_parts.append(OPENAI_MODEL_EXECUTION_GUIDANCE)

has_skills_tools = any(name in agent.valid_tool_names for name in ['skills_list', 'skill_view', 'skill_manage'])
Expand Down
70 changes: 70 additions & 0 deletions tests/agent/test_system_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,73 @@ def test_absent_without_tools(self, monkeypatch, tmp_path):
monkeypatch.setenv("TERMINAL_CWD", str(tmp_path))
agent = _make_agent(valid_tool_names=[], platform="cli")
assert "coding agent" not in _stable_prompt(agent)


class TestOpenAIExecutionGuidanceInjection:
"""Regression tests for the tool_use enforcement / OPENAI execution
discipline injection block in ``build_system_prompt_parts``.

Background — 2026-06-27 Telegram stall: GLM-5.2 replied with a
plain-text ``[TOOL_CALL]...[/TOOL_CALL]`` marker instead of a
structured ``tool_calls`` JSON block, so the runtime saw
``tool_calls=None`` and finished with ``finish_reason=stop`` after
one assistant turn. Root cause: the system prompt injector only
added ``OPENAI_MODEL_EXECUTION_GUIDANCE`` for ``gpt/codex/grok``
substrings, so non-Google TOOL_USE_ENFORCEMENT_MODELS families
(``glm``, ``qwen``, ``deepseek``) got the lighter ``TOOL_USE_
ENFORCEMENT_GUIDANCE`` block but not the execution-discipline
block (tool persistence, anti-fabrication, mandatory_tool_use).
"""

def _prompt(self, model, *, valid_tool_names=("terminal", "read_file"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This default forces the outer enforcement branch, so the new cases do not exercise the production auto route through TOOL_USE_ENFORCEMENT_MODELS. Please add at least one GLM/Qwen/DeepSeek case using tool_use_enforcement="auto" and assert both guidance blocks.

tool_use_enforcement=True):
agent = _make_agent(
valid_tool_names=list(valid_tool_names),
model=model,
_tool_use_enforcement=tool_use_enforcement,
)
return _stable_prompt(agent)

def test_glm_5_2_receives_openai_execution_guidance(self):
# The exact model from the 2026-06-27 Telegram stall reproduction.
stable = self._prompt("z-ai/glm-5.2")
assert "Execution discipline" in stable
assert "<tool_persistence>" in stable
assert "<mandatory_tool_use>" in stable

def test_deepseek_receives_openai_execution_guidance(self):
stable = self._prompt("deepseek/deepseek-v4-pro")
assert "Execution discipline" in stable

def test_qwen_receives_openai_execution_guidance(self):
stable = self._prompt("qwen/qwen-3-max")
assert "Execution discipline" in stable

def test_gpt_still_receives_openai_execution_guidance(self):
# No regression on the original coverage.
stable = self._prompt("openai/gpt-5.5")
assert "Execution discipline" in stable

def test_grok_still_receives_openai_execution_guidance(self):
stable = self._prompt("xai/grok-4")
assert "Execution discipline" in stable

def test_anthropic_opus_does_not_receive_openai_execution_guidance(self):
# Claude has its own anthropic-transport guidance; OPENAI_ block
# is body-agnostic but conceptually targeted at the families that
# share GPT/Grok/GLM failure modes. Don't double-inject.
stable = self._prompt("anthropic/claude-opus-4.8")
assert "Execution discipline" not in stable

def test_google_gemini_does_not_receive_openai_execution_guidance(self):
# Google gets the more specific GOOGLE_MODEL_OPERATIONAL block
# instead. Adding OPENAI_ would duplicate the parallel-call steer.
stable = self._prompt("google/gemini-2.5-pro")
assert "Execution discipline" not in stable
assert "Google model operational directives" in stable

def test_disabled_when_enforcement_off(self):
stable = self._prompt("z-ai/glm-5.2", tool_use_enforcement=False)
# When the whole block is off neither guidance should land.
assert "Execution discipline" not in stable
assert "Tool-use enforcement" not in stable