|
|
@@ -76,9 +76,15 @@ from cyber_agent.core.agent_mode import (
|
|
|
from cyber_agent.core.task_protocol import (
|
|
|
RootTaskAnchor,
|
|
|
ensure_task_protocol,
|
|
|
+ initialize_task_progress,
|
|
|
+ task_progress_artifact_refs,
|
|
|
+ task_progress_at_revision,
|
|
|
+ task_progress_readiness_error,
|
|
|
+ rewind_task_progress,
|
|
|
protocol_error_report,
|
|
|
rebuild_pending_replans,
|
|
|
)
|
|
|
+from cyber_agent.core.task_protocol_service import TaskProtocolService
|
|
|
from cyber_agent.core.context_policy import (
|
|
|
canonical_json,
|
|
|
ContextPolicyError,
|
|
|
@@ -86,6 +92,7 @@ from cyber_agent.core.context_policy import (
|
|
|
persist_root_task_anchor,
|
|
|
prune_context_access,
|
|
|
render_recursive_context,
|
|
|
+ get_authorized_context_snapshot,
|
|
|
require_matching_root_task_anchor,
|
|
|
require_root_task_anchor,
|
|
|
replace_context_access,
|
|
|
@@ -101,6 +108,7 @@ from cyber_agent.core.artifacts import (
|
|
|
ArtifactRef,
|
|
|
ArtifactResolver,
|
|
|
MaterialIssue,
|
|
|
+ ValidationMaterial,
|
|
|
extract_artifact_refs,
|
|
|
material_chars,
|
|
|
resolve_artifact_refs,
|
|
|
@@ -362,6 +370,9 @@ class AgentRunner:
|
|
|
self.resource_budget = (
|
|
|
ResourceBudgetController(trace_store) if trace_store else None
|
|
|
)
|
|
|
+ self.task_protocol_service = (
|
|
|
+ TaskProtocolService(trace_store) if trace_store else None
|
|
|
+ )
|
|
|
|
|
|
# ===== 核心公开方法 =====
|
|
|
|
|
|
@@ -517,22 +528,59 @@ class AgentRunner:
|
|
|
)
|
|
|
policy, settings = require_validation_policy(root.context)
|
|
|
state = ensure_task_protocol(evaluated.context)
|
|
|
+ runtime_policy = policy_from_context(evaluated.context)
|
|
|
authoritative_brief = state.get("task_brief")
|
|
|
if authoritative_brief is not None:
|
|
|
task_brief = authoritative_brief
|
|
|
task_brief_version = int(state.get("task_brief_version", 0) or 0)
|
|
|
+ progress_revision = (
|
|
|
+ state.get("task_progress_head_revision")
|
|
|
+ if root_validator or task_report is None
|
|
|
+ else state.get("task_report_progress_revision")
|
|
|
+ )
|
|
|
+ task_progress = task_progress_at_revision(state, progress_revision)
|
|
|
+ if runtime_policy.requires_task_progress and task_progress is None:
|
|
|
+ raise ValueError("Recursive revision 3 validation requires TaskProgress")
|
|
|
trajectory = await self.trace_store.get_main_path_messages(
|
|
|
evaluated_trace_id,
|
|
|
evaluated.head_sequence or evaluated.last_sequence,
|
|
|
)
|
|
|
|
|
|
refs: list[ArtifactRef] = []
|
|
|
+ materials: list[ValidationMaterial] = []
|
|
|
source_urls: list[str] = []
|
|
|
material_issues: list[MaterialIssue] = []
|
|
|
try:
|
|
|
if isinstance(task_report, dict):
|
|
|
refs.extend(extract_artifact_refs(task_report))
|
|
|
source_urls = list(task_report.get("source_urls") or [])
|
|
|
+ refs.extend(task_progress_artifact_refs(task_progress))
|
|
|
+ if task_progress is not None:
|
|
|
+ for item in (
|
|
|
+ *task_progress.questions,
|
|
|
+ *task_progress.blockers,
|
|
|
+ *task_progress.findings,
|
|
|
+ *task_progress.hypotheses,
|
|
|
+ *task_progress.work_items,
|
|
|
+ ):
|
|
|
+ for ref in item.context_refs:
|
|
|
+ snapshot = get_authorized_context_snapshot(
|
|
|
+ evaluated.context,
|
|
|
+ ref_id=ref.ref_id,
|
|
|
+ version=ref.version,
|
|
|
+ root_trace_id=root_trace_id,
|
|
|
+ uid=evaluated.uid,
|
|
|
+ )
|
|
|
+ materials.append(ValidationMaterial(
|
|
|
+ artifact_id=ref.ref_id,
|
|
|
+ version=snapshot.version,
|
|
|
+ content_hash=snapshot.version,
|
|
|
+ kind=f"context.{snapshot.kind}",
|
|
|
+ mime_type="application/json",
|
|
|
+ root_trace_id=root_trace_id,
|
|
|
+ uid=evaluated.uid,
|
|
|
+ content=snapshot.content,
|
|
|
+ ))
|
|
|
if root_validator:
|
|
|
for message in trajectory:
|
|
|
if message.role == "tool" and isinstance(message.content, dict):
|
|
|
@@ -546,12 +594,13 @@ class AgentRunner:
|
|
|
unique_refs = {
|
|
|
(item.artifact_id, item.version, item.content_hash): item for item in refs
|
|
|
}
|
|
|
- materials, resolved_issues = await resolve_artifact_refs(
|
|
|
+ resolved_materials, resolved_issues = await resolve_artifact_refs(
|
|
|
list(unique_refs.values()),
|
|
|
resolver=self.artifact_resolver,
|
|
|
root_trace_id=root_trace_id,
|
|
|
uid=evaluated.uid,
|
|
|
)
|
|
|
+ materials.extend(resolved_materials)
|
|
|
material_issues.extend(resolved_issues)
|
|
|
total_material_chars = sum(material_chars(item) for item in materials)
|
|
|
if deterministic_failure:
|
|
|
@@ -586,6 +635,7 @@ class AgentRunner:
|
|
|
material_issues=material_issues,
|
|
|
model_by_scope=model_by_scope,
|
|
|
root=root_validator,
|
|
|
+ task_progress=task_progress,
|
|
|
)
|
|
|
|
|
|
cached = state.get("task_report_validation")
|
|
|
@@ -761,6 +811,7 @@ class AgentRunner:
|
|
|
root_task_anchor=root_anchor,
|
|
|
task_brief=task_brief,
|
|
|
task_report=task_report,
|
|
|
+ task_progress=task_progress,
|
|
|
candidate_output=candidate_output,
|
|
|
materials=materials,
|
|
|
material_issues=material_issues,
|
|
|
@@ -1271,6 +1322,13 @@ class AgentRunner:
|
|
|
)
|
|
|
trace_context[RESOURCE_BUDGET_CONTEXT_KEY] = budget.to_dict()
|
|
|
state = ensure_task_protocol(trace_context)
|
|
|
+ if policy.requires_task_progress:
|
|
|
+ initialize_task_progress(
|
|
|
+ state,
|
|
|
+ root_task_anchor_hash=trace_context.get(
|
|
|
+ "root_task_anchor_hash"
|
|
|
+ ),
|
|
|
+ )
|
|
|
replace_context_access(
|
|
|
trace_context,
|
|
|
[],
|
|
|
@@ -1445,6 +1503,31 @@ class AgentRunner:
|
|
|
"Root task already used its two independent "
|
|
|
"validation attempts; create a new trace"
|
|
|
)
|
|
|
+ if policy.requires_task_progress:
|
|
|
+ state = ensure_task_protocol(trace_obj.context)
|
|
|
+ had_report = state.get("task_report") is not None
|
|
|
+ report_progress_revision = state.get(
|
|
|
+ "task_report_progress_revision"
|
|
|
+ )
|
|
|
+ if had_report and report_progress_revision is None:
|
|
|
+ raise ValueError(
|
|
|
+ "TaskReport is missing its TaskProgress revision binding"
|
|
|
+ )
|
|
|
+ previous_head = state.get("task_progress_head_revision")
|
|
|
+ rewind_task_progress(state, trace_obj.last_sequence)
|
|
|
+ if (
|
|
|
+ had_report
|
|
|
+ and state.get("task_report_progress_revision") is None
|
|
|
+ ):
|
|
|
+ raise ValueError(
|
|
|
+ "TaskReport references TaskProgress outside the "
|
|
|
+ "current revision ancestry"
|
|
|
+ )
|
|
|
+ if state.get("task_progress_head_revision") != previous_head:
|
|
|
+ await self.trace_store.update_trace(
|
|
|
+ trace_obj.trace_id,
|
|
|
+ context=trace_obj.context,
|
|
|
+ )
|
|
|
assert self.resource_budget is not None
|
|
|
await self.resource_budget.get_usage(root.trace_id)
|
|
|
|
|
|
@@ -2551,7 +2634,12 @@ class AgentRunner:
|
|
|
tool_calls = None
|
|
|
finish_reason = "budget_exhausted"
|
|
|
|
|
|
- lifecycle_tools = {"agent", "submit_task_report", "review_task_result"}
|
|
|
+ lifecycle_tools = {
|
|
|
+ "agent",
|
|
|
+ "submit_task_report",
|
|
|
+ "review_task_result",
|
|
|
+ "update_task_progress",
|
|
|
+ }
|
|
|
lifecycle_call_count = sum(
|
|
|
1 for tc in (tool_calls or [])
|
|
|
if tc.get("function", {}).get("name") in lifecycle_tools
|
|
|
@@ -3446,7 +3534,18 @@ class AgentRunner:
|
|
|
)
|
|
|
pending_reviews = bool(state["pending_reviews"])
|
|
|
pending_actions = bool(state["next_actions"])
|
|
|
- if missing_report or pending_reviews or pending_actions:
|
|
|
+ progress_error = (
|
|
|
+ task_progress_readiness_error(state)
|
|
|
+ if policy.requires_task_progress
|
|
|
+ and not trace.parent_trace_id
|
|
|
+ else None
|
|
|
+ )
|
|
|
+ if (
|
|
|
+ missing_report
|
|
|
+ or pending_reviews
|
|
|
+ or pending_actions
|
|
|
+ or progress_error
|
|
|
+ ):
|
|
|
attempts = state["protocol_correction_attempts"]
|
|
|
if attempts < 2:
|
|
|
state["protocol_correction_attempts"] = attempts + 1
|
|
|
@@ -3463,10 +3562,15 @@ class AgentRunner:
|
|
|
required_action = (
|
|
|
"execute the approved next action with agent"
|
|
|
)
|
|
|
- else:
|
|
|
+ elif missing_report:
|
|
|
required_action = (
|
|
|
"submit a valid TaskReport with submit_task_report"
|
|
|
)
|
|
|
+ else:
|
|
|
+ required_action = (
|
|
|
+ "update TaskProgress to a ready_to_submit snapshot "
|
|
|
+ f"({progress_error})"
|
|
|
+ )
|
|
|
history.append({
|
|
|
"role": "user",
|
|
|
"content": (
|
|
|
@@ -3483,6 +3587,9 @@ class AgentRunner:
|
|
|
)
|
|
|
state["task_report"] = report.model_dump()
|
|
|
state["task_report_submitted_at_sequence"] = sequence
|
|
|
+ state["task_report_progress_revision"] = state.get(
|
|
|
+ "task_progress_head_revision"
|
|
|
+ )
|
|
|
completion_status = "failed"
|
|
|
await self.trace_store.update_trace(
|
|
|
trace_id,
|
|
|
@@ -3629,6 +3736,9 @@ class AgentRunner:
|
|
|
)
|
|
|
state["task_report"] = report.model_dump()
|
|
|
state["task_report_submitted_at_sequence"] = sequence
|
|
|
+ state["task_report_progress_revision"] = state.get(
|
|
|
+ "task_progress_head_revision"
|
|
|
+ )
|
|
|
completion_status = "failed"
|
|
|
await self.trace_store.update_trace(
|
|
|
trace_id,
|
|
|
@@ -3775,6 +3885,7 @@ class AgentRunner:
|
|
|
state["report_history"].append(state["task_report"])
|
|
|
state["task_report"] = None
|
|
|
state["task_report_submitted_at_sequence"] = None
|
|
|
+ state["task_report_progress_revision"] = None
|
|
|
state["task_report_validation"] = None
|
|
|
validation_cache = state.get("task_report_validation")
|
|
|
if (
|
|
|
@@ -3821,6 +3932,8 @@ class AgentRunner:
|
|
|
"effective_at_sequence",
|
|
|
0,
|
|
|
)
|
|
|
+ if policy_from_context(trace.context).requires_task_progress:
|
|
|
+ rewind_task_progress(state, cutoff)
|
|
|
state["pending_replans"] = rebuild_pending_replans(state)
|
|
|
root_history = [
|
|
|
item
|
|
|
@@ -4816,6 +4929,7 @@ class AgentRunner:
|
|
|
protocol_tools = {
|
|
|
"submit_task_report",
|
|
|
"review_task_result",
|
|
|
+ "update_task_progress",
|
|
|
"read_context_ref",
|
|
|
}
|
|
|
|
|
|
@@ -4833,6 +4947,8 @@ class AgentRunner:
|
|
|
return tool_names & {"agent", "read_context_ref"}
|
|
|
|
|
|
tool_names.discard("review_task_result")
|
|
|
+ if not policy.requires_task_progress or state.get("task_report") is not None:
|
|
|
+ tool_names.discard("update_task_progress")
|
|
|
if not trace.parent_trace_id or state.get("task_report") is not None:
|
|
|
tool_names.discard("submit_task_report")
|
|
|
return tool_names
|
|
|
@@ -4905,6 +5021,7 @@ class AgentRunner:
|
|
|
"""
|
|
|
framework_context = {
|
|
|
"store": self.trace_store,
|
|
|
+ "task_protocol_service": self.task_protocol_service,
|
|
|
"trace_id": trace_id,
|
|
|
"goal_id": goal_id,
|
|
|
"runner": self,
|