|
|
@@ -13,6 +13,7 @@ from cyber_agent.core.resource_budget import (
|
|
|
)
|
|
|
from cyber_agent.core.runner import AgentRunner, RunConfig
|
|
|
from cyber_agent.core.task_protocol import ensure_task_protocol, new_task_protocol
|
|
|
+from cyber_agent.core.context_policy import persist_root_task_anchor
|
|
|
from cyber_agent.tools import get_tool_registry
|
|
|
from cyber_agent.tools.builtin.knowledge import KnowledgeConfig
|
|
|
from cyber_agent.tools.builtin.subagent import agent
|
|
|
@@ -22,6 +23,11 @@ from cyber_agent.trace.store import FileSystemTraceStore
|
|
|
|
|
|
|
|
|
ROOT_CRITERIA = ["return a checked final answer"]
|
|
|
+ROOT_ANCHOR = {
|
|
|
+ "objective": "return a checked final answer",
|
|
|
+ "completion_criteria": ROOT_CRITERIA,
|
|
|
+ "constraints": ["do not invent evidence"],
|
|
|
+}
|
|
|
CHILD_BRIEF = {
|
|
|
"objective": "check one concrete fact",
|
|
|
"reason": "the root needs independently checked evidence",
|
|
|
@@ -39,6 +45,11 @@ def knowledge_disabled():
|
|
|
)
|
|
|
|
|
|
|
|
|
+def with_root_anchor(context):
|
|
|
+ persist_root_task_anchor(context, ROOT_ANCHOR)
|
|
|
+ return context
|
|
|
+
|
|
|
+
|
|
|
def recursive_env(**overrides):
|
|
|
values = {
|
|
|
"AGENT_MODE": "recursive",
|
|
|
@@ -102,7 +113,7 @@ class RootValidationIntegrationTest(unittest.IsolatedAsyncioTestCase):
|
|
|
"tools": [],
|
|
|
"tool_groups": [],
|
|
|
"enable_research_flow": False,
|
|
|
- "root_completion_criteria": ROOT_CRITERIA,
|
|
|
+ "root_task_anchor": ROOT_ANCHOR,
|
|
|
"knowledge": knowledge_disabled(),
|
|
|
}
|
|
|
values.update(overrides)
|
|
|
@@ -308,16 +319,16 @@ class RootValidationIntegrationTest(unittest.IsolatedAsyncioTestCase):
|
|
|
for item in trajectory
|
|
|
))
|
|
|
|
|
|
- async def test_new_recursive_root_requires_explicit_completion_criteria(self):
|
|
|
+ async def test_new_recursive_root_requires_explicit_task_anchor(self):
|
|
|
async def llm_call(**_kwargs):
|
|
|
raise AssertionError("LLM must not be called")
|
|
|
|
|
|
runner = AgentRunner(trace_store=self.store, llm_call=llm_call)
|
|
|
with recursive_env():
|
|
|
- with self.assertRaisesRegex(ValueError, "root_completion_criteria"):
|
|
|
+ with self.assertRaisesRegex(ValueError, "root_task_anchor"):
|
|
|
await runner.run_result(
|
|
|
messages=[{"role": "user", "content": "missing criteria"}],
|
|
|
- config=self.config(root_completion_criteria=[]),
|
|
|
+ config=self.config(root_task_anchor=None),
|
|
|
)
|
|
|
self.assertEqual([], await self.store.list_traces(limit=10))
|
|
|
|
|
|
@@ -346,12 +357,12 @@ class RootValidationIntegrationTest(unittest.IsolatedAsyncioTestCase):
|
|
|
agent_type="validator",
|
|
|
tools=[],
|
|
|
status="completed",
|
|
|
- context={
|
|
|
+ context=with_root_anchor({
|
|
|
"created_by_tool": "validator",
|
|
|
"agent_mode": "recursive",
|
|
|
"agent_mode_revision": 2,
|
|
|
"root_trace_id": "root",
|
|
|
- },
|
|
|
+ }),
|
|
|
))
|
|
|
calls = 0
|
|
|
|
|
|
@@ -413,7 +424,7 @@ class RecursiveBudgetIntegrationTest(unittest.IsolatedAsyncioTestCase):
|
|
|
tools=["dangerous"],
|
|
|
tool_groups=[],
|
|
|
enable_research_flow=False,
|
|
|
- root_completion_criteria=ROOT_CRITERIA,
|
|
|
+ root_task_anchor=ROOT_ANCHOR,
|
|
|
knowledge=knowledge_disabled(),
|
|
|
)
|
|
|
with recursive_env(AGENT_MAX_TOTAL_TOKENS="10"):
|
|
|
@@ -472,7 +483,7 @@ class RecursiveBudgetIntegrationTest(unittest.IsolatedAsyncioTestCase):
|
|
|
tools=["agent"],
|
|
|
tool_groups=[],
|
|
|
enable_research_flow=False,
|
|
|
- root_completion_criteria=ROOT_CRITERIA,
|
|
|
+ root_task_anchor=ROOT_ANCHOR,
|
|
|
knowledge=knowledge_disabled(),
|
|
|
)
|
|
|
with recursive_env(AGENT_MAX_TOTAL_AGENTS="1"):
|
|
|
@@ -502,15 +513,14 @@ class RecursiveBudgetIntegrationTest(unittest.IsolatedAsyncioTestCase):
|
|
|
mode="agent",
|
|
|
uid="user-1",
|
|
|
model="fake",
|
|
|
- context={
|
|
|
+ context=with_root_anchor({
|
|
|
"agent_mode": "recursive",
|
|
|
"agent_mode_revision": 2,
|
|
|
"agent_depth": 0,
|
|
|
"root_trace_id": root_id,
|
|
|
- "root_completion_criteria": ROOT_CRITERIA,
|
|
|
RESOURCE_BUDGET_CONTEXT_KEY: budget.to_dict(),
|
|
|
"task_protocol": new_task_protocol(),
|
|
|
- },
|
|
|
+ }),
|
|
|
))
|
|
|
await ResourceBudgetController(self.store).initialize(root_id, budget)
|
|
|
original_update_goal_tree = self.store.update_goal_tree
|
|
|
@@ -557,15 +567,14 @@ class RecursiveBudgetIntegrationTest(unittest.IsolatedAsyncioTestCase):
|
|
|
mode="agent",
|
|
|
uid="user-1",
|
|
|
model="fake",
|
|
|
- context={
|
|
|
+ context=with_root_anchor({
|
|
|
"agent_mode": "recursive",
|
|
|
"agent_mode_revision": 2,
|
|
|
"agent_depth": 0,
|
|
|
"root_trace_id": root_id,
|
|
|
- "root_completion_criteria": ROOT_CRITERIA,
|
|
|
RESOURCE_BUDGET_CONTEXT_KEY: budget.to_dict(),
|
|
|
"task_protocol": new_task_protocol(),
|
|
|
- },
|
|
|
+ }),
|
|
|
))
|
|
|
await ResourceBudgetController(self.store).initialize(root_id, budget)
|
|
|
runner = AgentRunner(
|
|
|
@@ -605,15 +614,14 @@ class RecursiveBudgetIntegrationTest(unittest.IsolatedAsyncioTestCase):
|
|
|
async def test_approved_action_write_failure_closes_precreated_child(self):
|
|
|
root_id = "approved-action-write-root"
|
|
|
budget = ResourceBudget()
|
|
|
- context = {
|
|
|
+ context = with_root_anchor({
|
|
|
"agent_mode": "recursive",
|
|
|
"agent_mode_revision": 2,
|
|
|
"agent_depth": 0,
|
|
|
"root_trace_id": root_id,
|
|
|
- "root_completion_criteria": ROOT_CRITERIA,
|
|
|
RESOURCE_BUDGET_CONTEXT_KEY: budget.to_dict(),
|
|
|
"task_protocol": new_task_protocol(),
|
|
|
- }
|
|
|
+ })
|
|
|
state = ensure_task_protocol(context)
|
|
|
state["next_actions"] = [{
|
|
|
"decision": "ACCEPT_REFINE",
|
|
|
@@ -783,7 +791,7 @@ class RecursiveLifecycleIntegrationTest(unittest.IsolatedAsyncioTestCase):
|
|
|
tools=["agent", "submit_task_report", "review_task_result"],
|
|
|
tool_groups=[],
|
|
|
enable_research_flow=False,
|
|
|
- root_completion_criteria=ROOT_CRITERIA,
|
|
|
+ root_task_anchor=ROOT_ANCHOR,
|
|
|
knowledge=knowledge_disabled(),
|
|
|
)
|
|
|
with recursive_env():
|
|
|
@@ -840,29 +848,28 @@ class RecursiveLifecycleIntegrationTest(unittest.IsolatedAsyncioTestCase):
|
|
|
trace_id=root_id,
|
|
|
mode="agent",
|
|
|
model="fake-model",
|
|
|
- context={
|
|
|
+ context=with_root_anchor({
|
|
|
"agent_mode": "recursive",
|
|
|
"agent_mode_revision": 2,
|
|
|
"agent_depth": 0,
|
|
|
"root_trace_id": root_id,
|
|
|
- "root_completion_criteria": ROOT_CRITERIA,
|
|
|
RESOURCE_BUDGET_CONTEXT_KEY: budget.to_dict(),
|
|
|
"task_protocol": new_task_protocol(),
|
|
|
- },
|
|
|
+ }),
|
|
|
))
|
|
|
await self.store.create_trace(Trace(
|
|
|
trace_id=child_id,
|
|
|
mode="agent",
|
|
|
model="fake-model",
|
|
|
parent_trace_id=root_id,
|
|
|
- context={
|
|
|
+ context=with_root_anchor({
|
|
|
"agent_mode": "recursive",
|
|
|
"agent_mode_revision": 2,
|
|
|
"agent_depth": 1,
|
|
|
"root_trace_id": root_id,
|
|
|
"created_by_tool": "agent",
|
|
|
"task_protocol": new_task_protocol(CHILD_BRIEF),
|
|
|
- },
|
|
|
+ }),
|
|
|
))
|
|
|
await ResourceBudgetController(self.store).initialize(
|
|
|
root_id,
|