fix(agent): stop on workspace violations from tool errors

Treat workspace and safety guard failures as fatal regardless of whether they arrive from tool preparation, returned tool output, or raised exceptions.

Made-with: Cursor
This commit is contained in:
Xubin Ren
2026-04-28 15:13:27 +08:00
committed by Xubin Ren
parent f19d767b0f
commit 48f3cc6390
2 changed files with 58 additions and 0 deletions
+40
View File
@@ -312,6 +312,46 @@ async def test_runner_returns_structured_tool_error():
]
@pytest.mark.asyncio
async def test_runner_stops_on_workspace_violation_without_fail_on_tool_error():
from nanobot.agent.runner import AgentRunSpec, AgentRunner
provider = MagicMock()
provider.chat_with_retry = AsyncMock(side_effect=[
LLMResponse(
content="working",
tool_calls=[ToolCallRequest(id="call_1", name="read_file", arguments={"path": "/tmp/outside.md"})],
),
LLMResponse(content="should not continue", tool_calls=[]),
])
tools = MagicMock()
tools.get_definitions.return_value = []
tools.execute = AsyncMock(
side_effect=PermissionError("Path /tmp/outside.md is outside allowed directory /workspace")
)
runner = AgentRunner(provider)
result = await runner.run(AgentRunSpec(
initial_messages=[],
tools=tools,
model="test-model",
max_iterations=2,
max_tool_result_chars=_MAX_TOOL_RESULT_CHARS,
))
assert provider.chat_with_retry.await_count == 1
assert result.stop_reason == "tool_error"
assert "outside allowed directory" in (result.error or "")
assert result.tool_events == [
{
"name": "read_file",
"status": "error",
"detail": "workspace_violation: Path /tmp/outside.md is outside allowed directory /workspace",
}
]
@pytest.mark.asyncio
async def test_runner_persists_large_tool_results_for_follow_up_calls(tmp_path):
from nanobot.agent.runner import AgentRunSpec, AgentRunner