| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- import pytest
- from agent.core.presets import AgentPreset, register_preset
- from agent.core.runner import AgentRunner, RunConfig
- from agent.orchestration.models import AgentRole, CompletionPolicy
- from agent.tools.models import ToolCapability, ToolResult
- from agent.tools.registry import ToolRegistry
- from agent.trace.models import Trace
- def _schema(name):
- return {
- "type": "function",
- "function": {"name": name, "description": name, "parameters": {"type": "object", "properties": {}}},
- }
- @pytest.mark.asyncio
- async def test_terminal_tool_completes_without_final_assistant_text(tmp_path):
- registry = ToolRegistry()
- async def finish():
- return ToolResult(
- title="done", output="submitted", terminate_run=True, result_summary="structured-result"
- )
- registry.register(finish, schema=_schema("finish"), groups=["test"])
- calls = 0
- async def llm_call(**kwargs):
- nonlocal calls
- calls += 1
- return {
- "content": "",
- "tool_calls": [{"id": "call-1", "type": "function", "function": {"name": "finish", "arguments": "{}"}}],
- "finish_reason": "tool_calls",
- }
- from agent.trace.store import FileSystemTraceStore
- runner = AgentRunner(
- trace_store=FileSystemTraceStore(str(tmp_path)),
- tool_registry=registry,
- llm_call=llm_call,
- )
- result = await runner.run_result(
- [{"role": "user", "content": "finish"}],
- RunConfig(tools=["finish"], tool_groups=[], knowledge=_disabled_knowledge()),
- )
- assert calls == 1
- assert result["status"] == "completed"
- assert result["summary"] == "structured-result"
- @pytest.mark.asyncio
- async def test_execution_layer_rejects_forged_worker_tool_call():
- registry = ToolRegistry()
- executed = False
- async def agent():
- nonlocal executed
- executed = True
- return "bad"
- registry.register(agent, schema=_schema("agent"))
- register_preset(
- "test_worker_policy",
- AgentPreset(role=AgentRole.WORKER, allowed_tools=["agent"], max_iterations=5, skills=[]),
- )
- runner = AgentRunner(tool_registry=registry, llm_call=lambda **_: None, task_coordinator=object())
- config = RunConfig(
- agent_type="test_worker_policy",
- completion_policy=CompletionPolicy.EXPLICIT_VALIDATION,
- tools=["agent"],
- tool_groups=None,
- )
- trace = Trace(trace_id="worker-trace", mode="agent", agent_role="worker", context={})
- result = await runner._execute_authorized_tool(
- "agent", {}, "call", config, trace, None, 1
- )
- assert result["error"] == "unauthorized_tool"
- assert executed is False
- @pytest.mark.asyncio
- async def test_framework_context_overrides_host_spoofing():
- registry = ToolRegistry()
- register_preset(
- "test_worker_context",
- AgentPreset(role=AgentRole.WORKER, allowed_tools=[], max_iterations=5, skills=[]),
- )
- runner = AgentRunner(tool_registry=registry, llm_call=lambda **_: None, task_coordinator="real")
- config = RunConfig(
- agent_type="test_worker_context",
- completion_policy=CompletionPolicy.EXPLICIT_VALIDATION,
- context={"role": "planner", "task_id": "spoofed", "coordinator": "fake"},
- )
- trace = Trace(
- trace_id="trace", mode="agent", agent_role="worker",
- context={"root_trace_id": "root", "task_id": "real-task", "attempt_id": "attempt"},
- )
- context = runner._build_protected_tool_context(config, trace, None, 1)
- assert context["role"] == "worker"
- assert context["task_id"] == "real-task"
- assert context["coordinator"] == "real"
- @pytest.mark.asyncio
- async def test_validator_capabilities_hide_and_reject_arbitrary_write_tool():
- registry = ToolRegistry()
- executed = False
- async def db_modify():
- nonlocal executed
- executed = True
- return "modified"
- async def db_lookup():
- return "row"
- registry.register(
- db_modify,
- schema=_schema("db_modify"),
- capabilities=[ToolCapability.WRITE],
- )
- registry.register(
- db_lookup,
- schema=_schema("db_lookup"),
- capabilities=[ToolCapability.READ],
- )
- register_preset(
- "test_validator_capabilities",
- AgentPreset(
- role=AgentRole.VALIDATOR,
- allowed_tools=["db_modify", "db_lookup"],
- max_iterations=5,
- skills=[],
- ),
- )
- runner = AgentRunner(tool_registry=registry, llm_call=lambda **_: None, task_coordinator=object())
- config = RunConfig(
- agent_type="test_validator_capabilities",
- completion_policy=CompletionPolicy.EXPLICIT_VALIDATION,
- tools=["db_modify", "db_lookup"],
- )
- names = {
- item["function"]["name"] for item in runner._get_run_tool_schemas(config)
- }
- assert names == {"db_lookup"}
- trace = Trace(trace_id="validator", mode="agent", agent_role="validator", context={})
- result = await runner._execute_authorized_tool(
- "db_modify", {}, "forged", config, trace, None, 1
- )
- assert result["error"] == "unauthorized_tool"
- assert executed is False
- @pytest.mark.asyncio
- async def test_worker_rejects_agent_spawn_capability_with_unrelated_name():
- registry = ToolRegistry()
- executed = False
- async def spawn_helper():
- nonlocal executed
- executed = True
- return "spawned"
- registry.register(
- spawn_helper,
- schema=_schema("spawn_helper"),
- capabilities=[ToolCapability.AGENT_SPAWN],
- )
- register_preset(
- "test_worker_spawn_capability",
- AgentPreset(
- role=AgentRole.WORKER,
- allowed_tools=["spawn_helper"],
- max_iterations=5,
- skills=[],
- ),
- )
- runner = AgentRunner(tool_registry=registry, llm_call=lambda **_: None, task_coordinator=object())
- config = RunConfig(
- agent_type="test_worker_spawn_capability",
- completion_policy=CompletionPolicy.EXPLICIT_VALIDATION,
- tools=["spawn_helper"],
- )
- assert runner._get_run_tool_schemas(config) == []
- trace = Trace(trace_id="worker", mode="agent", agent_role="worker", context={})
- result = await runner._execute_authorized_tool(
- "spawn_helper", {}, "forged", config, trace, None, 1
- )
- assert result["error"] == "unauthorized_tool"
- assert executed is False
- @pytest.mark.asyncio
- async def test_unclassified_tool_fails_closed_only_in_explicit_mode():
- registry = ToolRegistry()
- calls = 0
- async def mystery_tool():
- nonlocal calls
- calls += 1
- return "ok"
- registry.register(mystery_tool, schema=_schema("mystery_tool"))
- register_preset(
- "test_unclassified_worker",
- AgentPreset(
- role=AgentRole.WORKER,
- allowed_tools=["mystery_tool"],
- max_iterations=5,
- skills=[],
- ),
- )
- runner = AgentRunner(tool_registry=registry, llm_call=lambda **_: None, task_coordinator=object())
- explicit = RunConfig(
- agent_type="test_unclassified_worker",
- completion_policy=CompletionPolicy.EXPLICIT_VALIDATION,
- tools=["mystery_tool"],
- )
- worker_trace = Trace(trace_id="worker", mode="agent", agent_role="worker", context={})
- rejected = await runner._execute_authorized_tool(
- "mystery_tool", {}, "explicit", explicit, worker_trace, None, 1
- )
- assert rejected["error"] == "unauthorized_tool"
- assert calls == 0
- legacy = RunConfig(tools=["mystery_tool"], tool_groups=[])
- legacy_trace = Trace(trace_id="legacy", mode="agent", agent_role="legacy", context={})
- assert {x["function"]["name"] for x in runner._get_run_tool_schemas(legacy)} == {"mystery_tool"}
- assert await runner._execute_authorized_tool(
- "mystery_tool", {}, "legacy", legacy, legacy_trace, None, 1
- ) == "ok"
- assert calls == 1
- def _disabled_knowledge():
- from agent.tools.builtin.knowledge import KnowledgeConfig
- return KnowledgeConfig(enable_extraction=False, enable_completion_extraction=False, enable_injection=False)
|