Преглед изворни кода

完善 recursive 任务合同与版本模型

新增 RootTaskAnchor 和 ContextRef 协议模型,为 TaskBrief 增加受控上下文引用。

统一 TaskBrief 的初始化、替换、历史版本保存和稳定 JSON 展示,为后续授权、修订与回溯提供单一数据入口。
SamLee пре 20 часа
родитељ
комит
8150299359
1 измењених фајлова са 96 додато и 2 уклоњено
  1. 96 2
      cyber_agent/core/task_protocol.py

+ 96 - 2
cyber_agent/core/task_protocol.py

@@ -8,6 +8,7 @@ from __future__ import annotations
 
 
 from copy import deepcopy
 from copy import deepcopy
 from datetime import datetime
 from datetime import datetime
+import json
 from typing import Any, Literal
 from typing import Any, Literal
 
 
 from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
 from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
@@ -34,6 +35,35 @@ class StrictProtocolModel(BaseModel):
     model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
     model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
 
 
 
 
+class RootTaskAnchor(StrictProtocolModel):
+    """Recursive 根任务不可变的目标锚点。
+
+    调用方创建根 Trace 时提供;Runner 固化后由每层孩子和 Validator读取,
+    中间 Agent不能重新概括或覆盖它。
+    """
+
+    objective: str = Field(min_length=1)
+    completion_criteria: list[str] = Field(min_length=1)
+    constraints: list[str] = Field(default_factory=list)
+
+    @field_validator("completion_criteria", "constraints")
+    @classmethod
+    def validate_non_empty_items(cls, items: list[str]) -> list[str]:
+        if any(not item.strip() for item in items):
+            raise ValueError("items must be non-empty strings")
+        return items
+
+
+class ContextRef(StrictProtocolModel):
+    """模型可在 TaskBrief 中转交的最小上下文引用句柄。
+
+    来源、类型、摘要和内容由框架保管;模型只能选择已获授权的 ID与版本。
+    """
+
+    ref_id: str = Field(min_length=1)
+    version: str = Field(pattern=r"^[0-9a-f]{64}$")
+
+
 class TaskBrief(StrictProtocolModel):
 class TaskBrief(StrictProtocolModel):
     """父 Agent 下发给直接孩子的结构化任务说明。
     """父 Agent 下发给直接孩子的结构化任务说明。
 
 
@@ -47,6 +77,7 @@ class TaskBrief(StrictProtocolModel):
     parent_findings: list[str] = Field(default_factory=list)
     parent_findings: list[str] = Field(default_factory=list)
     context: dict[str, Any] = Field(default_factory=dict)
     context: dict[str, Any] = Field(default_factory=dict)
     constraints: list[str] = Field(default_factory=list)
     constraints: list[str] = Field(default_factory=list)
+    context_refs: list[ContextRef] = Field(default_factory=list)
 
 
     @field_validator(
     @field_validator(
         "completion_criteria",
         "completion_criteria",
@@ -180,6 +211,9 @@ def new_task_protocol(task_brief: TaskBrief | dict | None = None) -> dict[str, A
     brief = TaskBrief.model_validate(task_brief).model_dump() if task_brief else None
     brief = TaskBrief.model_validate(task_brief).model_dump() if task_brief else None
     return {
     return {
         "task_brief": brief,
         "task_brief": brief,
+        "task_brief_version": 1 if brief else 0,
+        "task_brief_effective_at_sequence": 0,
+        "task_brief_history": [],
         "task_report": None,
         "task_report": None,
         "task_report_submitted_at_sequence": None,
         "task_report_submitted_at_sequence": None,
         "task_report_validation": None,
         "task_report_validation": None,
@@ -195,6 +229,44 @@ def new_task_protocol(task_brief: TaskBrief | dict | None = None) -> dict[str, A
     }
     }
 
 
 
 
+def replace_task_brief(
+    state: dict[str, Any],
+    task_brief: TaskBrief | dict[str, Any],
+    *,
+    effective_at_sequence: int,
+) -> int:
+    """以唯一入口替换当前 TaskBrief并保留旧版本。
+
+    `review_task_result`批准修订后,`agent(continue_from=...)`真正恢复孩子时调用,
+    避免审核阶段提前改写孩子,也避免覆盖后丢失任务合同的审计版本。
+    """
+    brief = TaskBrief.model_validate(task_brief).model_dump(mode="json")
+    current = state.get("task_brief")
+    current_version = state.get("task_brief_version", 0)
+    if not isinstance(current_version, int) or current_version < 0:
+        current_version = 0
+    if current is not None:
+        history_entry = {
+            "version": current_version or 1,
+            "task_brief": deepcopy(current),
+            "effective_at_sequence": state.get(
+                "task_brief_effective_at_sequence",
+                0,
+            ),
+        }
+        history = state.setdefault("task_brief_history", [])
+        if history_entry not in history:
+            history.append(history_entry)
+    next_version = max(current_version, 0) + 1 if current is not None else 1
+    state["task_brief"] = brief
+    state["task_brief_version"] = next_version
+    state["task_brief_effective_at_sequence"] = max(
+        0,
+        int(effective_at_sequence),
+    )
+    return next_version
+
+
 def ensure_task_protocol(context: dict[str, Any]) -> dict[str, Any]:
 def ensure_task_protocol(context: dict[str, Any]) -> dict[str, Any]:
     """获取 Trace context 中的协议状态,并幂等补齐缺失字段。
     """获取 Trace context 中的协议状态,并幂等补齐缺失字段。
 
 
@@ -297,7 +369,11 @@ def pending_review_entry(
     }
     }
 
 
 
 
-def format_task_brief(task_brief: TaskBrief) -> str:
+def format_task_brief(
+    task_brief: TaskBrief,
+    *,
+    available_context_refs: list[dict[str, Any]] | None = None,
+) -> str:
     """将结构化 TaskBrief 格式化为子 Agent 可执行的任务文本。
     """将结构化 TaskBrief 格式化为子 Agent 可执行的任务文本。
 
 
     ``agent`` 完成规范化后调用,生成 Runner 作为 user message 传给子 Agent 的任务说明。
     ``agent`` 完成规范化后调用,生成 Runner 作为 user message 传给子 Agent 的任务说明。
@@ -323,9 +399,27 @@ def format_task_brief(task_brief: TaskBrief) -> str:
     if parent_findings:
     if parent_findings:
         parts.append(f"\n## Parent Findings\n{parent_findings}")
         parts.append(f"\n## Parent Findings\n{parent_findings}")
     if task_brief.context:
     if task_brief.context:
-        parts.append(f"\n## Context\n{task_brief.context}")
+        parts.append(
+            "\n## Context\n"
+            + json.dumps(
+                task_brief.context,
+                ensure_ascii=False,
+                sort_keys=True,
+                separators=(",", ":"),
+            )
+        )
     if constraints:
     if constraints:
         parts.append(f"\n## Constraints\n{constraints}")
         parts.append(f"\n## Constraints\n{constraints}")
+    if available_context_refs:
+        parts.append(
+            "\n## Available Context References\n"
+            + json.dumps(
+                available_context_refs,
+                ensure_ascii=False,
+                sort_keys=True,
+                separators=(",", ":"),
+            )
+        )
     parts.append(
     parts.append(
         "\nSubmit a validated TaskReport with submit_task_report before finishing."
         "\nSubmit a validated TaskReport with submit_task_report before finishing."
     )
     )