Files
strix/tests/test_safety_prompt.py
T
oyasumiandClaude Opus 5 d45b99e551 fix(safety): gate browser safety guidance on the active mode, and close test gaps
The `agent_browser` skill is always loaded, so its safety paragraph shipped to
`off`-mode agents. Its prohibitions do not hold there — Strix only assigns a
browser session in a safety mode, while multi-session browsing is a normal
documented workflow — so the paragraph misdescribed the tools those agents
have. Move it into the already mode-gated block in the system prompt, and pin
the gating in both directions.

Test changes:

- `test_observe_mode_blocks_browser_click` asserted nothing about observe mode.
  The same call blocks in guarded for a different reason (no prior snapshot),
  so the observe rule was never reached. Give it a snapshot and assert the
  block's source and category, plus the passive-read inverse.
- Neither workspace-epoch bump was pinned; removing either left the suite
  green. Both are now covered, along with the read-only case that must not
  bump, and an end-to-end pairing where a patch during review invalidates a
  script decision.
- Cover `invoke_mutating_tool`'s observe-block and off-mode paths, the
  reviewer's low-confidence, block, missing-model and failed-inspection rules,
  the inline `bash -c` source path, and the two dependency-budget guards.
- Assert browser sessions are disjoint across agents rather than freezing one
  agent's command string.
- Fold the compound-separator and safety-config tests into the parametrized
  cases that already covered them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 00:47:21 +00:00

48 lines
1.6 KiB
Python

"""Action-safety guidance reaches the agent only when a safety mode is active."""
from __future__ import annotations
import pytest
from strix.agents.prompt import render_system_prompt
# Phrased as prohibitions, so they misdescribe the tools an `off`-mode agent actually has.
_SAFETY_ONLY_PHRASES = [
"ACTION SAFETY POLICY",
"do not override",
"blocked as stale",
"must be split into a creation call",
]
@pytest.mark.parametrize("phrase", _SAFETY_ONLY_PHRASES)
@pytest.mark.parametrize("context", [None, {}, {"safety_mode": "off"}])
def test_safety_guidance_is_absent_without_a_safety_mode(
phrase: str,
context: dict[str, str] | None,
) -> None:
assert phrase not in render_system_prompt(system_prompt_context=context)
@pytest.mark.parametrize("phrase", _SAFETY_ONLY_PHRASES)
@pytest.mark.parametrize("mode", ["guarded", "observe"])
def test_safety_guidance_is_present_in_a_safety_mode(phrase: str, mode: str) -> None:
assert phrase in render_system_prompt(system_prompt_context={"safety_mode": mode})
def test_browser_skill_carries_no_safety_prohibitions() -> None:
"""The browser skill is always loaded, so mode-specific rules do not belong in it."""
prompt = render_system_prompt(skills=["agent_browser"], system_prompt_context={})
assert "agent-browser snapshot" in prompt
for phrase in _SAFETY_ONLY_PHRASES:
assert phrase not in prompt
def test_observe_mode_states_its_passive_only_contract() -> None:
prompt = render_system_prompt(system_prompt_context={"safety_mode": "observe"})
assert "passive target interaction only" in prompt
assert "Guarded mode permits" not in prompt