ソースを参照

补充 recursive 核心运行说明

SamLee 19 時間 前
コミット
db56db0f48

+ 34 - 8
cyber_agent/core/agent_mode.py

@@ -1,9 +1,7 @@
-"""Agent delegation mode policy.
+"""Agent 委托模式及其统一运行策略。
 
-The environment selects a mode only when a root Trace is created.  The chosen
-mode is then persisted on the Trace and inherited by every local descendant so
-that a running tree cannot change behaviour after a process configuration
-change.
+Runner 仅在创建根 Trace 时读取环境模式,随后将策略持久化并传给所有本地子孙。
+Runner、Sub-Agent 工具和 Goal 工具共同使用该策略,保证运行中的任务树不被环境变更改变。
 """
 
 from __future__ import annotations
@@ -28,12 +26,22 @@ MAX_PARALLEL_RECURSIVE_CHILDREN = 2
 
 
 class AgentMode(str, Enum):
+    """Agent 对外暴露的两种运行模式。
+
+    `legacy` 保留旧的一层委托,`recursive` 启用递归任务协议与资源边界。
+    """
+
     LEGACY = "legacy"
     RECURSIVE = "recursive"
 
 
 @dataclass(frozen=True)
 class AgentPolicy:
+    """某棵 Trace 树不可变的模式策略快照。
+
+    Runner 将其写入根 Trace,Sub-Agent 创建子 Trace 时继承,各运行门禁据此判断能力。
+    """
+
     mode: AgentMode
     revision: int
     max_depth: int
@@ -43,6 +51,7 @@ class AgentPolicy:
 
     @property
     def requires_task_protocol(self) -> bool:
+        """Recursive revision 2 是否必须走结构化任务报告和审核流程。"""
         return self.mode is AgentMode.RECURSIVE and self.revision >= 2
 
     def to_context(self) -> dict[str, Any]:
@@ -73,6 +82,10 @@ def _policy(mode: AgentMode, revision: int = 1) -> AgentPolicy:
 
 
 def assert_removed_config_absent() -> None:
+    """拒绝已删除的递归开关,避免运行时继续使用含糊配置。
+
+    Runner 准备 Trace 和 ``agent`` 工具入口都会调用,新根 Trace 解析模式时也会间接调用。
+    """
     if REMOVED_RECURSION_ENV in os.environ:
         raise ValueError(
             f"{REMOVED_RECURSION_ENV} has been removed; "
@@ -81,7 +94,10 @@ def assert_removed_config_absent() -> None:
 
 
 def policy_from_environment(*, recursive_revision: int = 1) -> AgentPolicy:
-    """Resolve the policy for a newly-created root Trace."""
+    """为新建根 Trace 解析一次环境模式。
+
+    `AgentRunner._prepare_new_trace` 在任何 Trace 落库前调用,返回的策略随后被持久化。
+    """
     assert_removed_config_absent()
     raw_mode = os.getenv(AGENT_MODE_ENV, AgentMode.LEGACY.value).strip().lower()
     try:
@@ -96,7 +112,10 @@ def policy_from_environment(*, recursive_revision: int = 1) -> AgentPolicy:
 
 
 def policy_from_context(context: Mapping[str, Any] | None) -> AgentPolicy:
-    """Resolve a persisted Trace policy; missing metadata is legacy."""
+    """从 Trace context 恢复已持久化的模式策略。
+
+    Runner、Sub-Agent 和 Goal 工具在恢复或执行时调用;无模式字段的历史 Trace 按 Legacy 处理。
+    """
     context = context or {}
     raw_mode = context.get(AGENT_MODE_CONTEXT_KEY, AgentMode.LEGACY.value)
     try:
@@ -114,6 +133,10 @@ def apply_policy_to_context(
     context: MutableMapping[str, Any] | None,
     policy: AgentPolicy,
 ) -> dict[str, Any]:
+    """将模式和 revision 覆盖写入一份 Trace context 副本。
+
+    Runner 创建根 Trace、Sub-Agent 创建或续跑子 Trace 时调用,保证子孙继承同一策略。
+    """
     merged = dict(context or {})
     merged.update(policy.to_context())
     return merged
@@ -123,7 +146,10 @@ def validate_recursive_child_execution(
     mode: Any,
     max_parallel_children: Any,
 ) -> tuple[str, int]:
-    """校验单批 Recursive 子 Agent 的执行策略。"""
+    """校验单批 Recursive 子 Agent 的串并行策略和硬上限。
+
+    Runner 创建/恢复根 Trace 以及 `agent()` 调度子任务前调用,防止模型或 context 提高并发。
+    """
     if mode not in {"sequential", "parallel"}:
         raise ValueError(
             "child_execution_mode must be 'sequential' or 'parallel'"

+ 17 - 4
cyber_agent/core/context_policy.py

@@ -1,4 +1,8 @@
-"""Minimal, explicit context inheritance for Recursive task delegation."""
+"""Recursive 任务委托的最小上下文继承策略。
+
+`agent()` 创建子任务和父 Agent 批准后续任务时共用本模块,只规范化 TaskBrief
+并继承父级硬约束,不复制父级完整消息、GoalTree 或内部运行对象。
+"""
 
 from __future__ import annotations
 
@@ -14,7 +18,10 @@ MAX_TASK_BRIEF_CHARS = 16_000
 
 
 class ContextPolicyError(ValueError):
-    """A TaskBrief cannot cross the Recursive delegation boundary safely."""
+    """TaskBrief 无法安全穿过 Recursive 委托边界。
+
+    `agent()` 或 `review_task_result` 捕获该异常后拒绝创建/批准不受控的子任务。
+    """
 
 
 def _stable_unique(items: list[str]) -> list[str]:
@@ -27,7 +34,10 @@ def normalize_task_brief(
     parent_task_brief: TaskBrief | dict[str, Any] | None = None,
     max_chars: int = MAX_TASK_BRIEF_CHARS,
 ) -> TaskBrief:
-    """Validate a TaskBrief and inherit only the parent's hard constraints."""
+    """校验并规范化 TaskBrief,仅向下继承父级硬约束。
+
+    `agent()` 解析任务、`review_task_result` 批准下一步和续跑任务时调用,同时执行 JSON 与 16K 边界检查。
+    """
     if max_chars <= 0:
         raise ContextPolicyError("max_chars must be greater than zero")
     try:
@@ -79,7 +89,10 @@ def task_briefs_match(
     *,
     parent_task_brief: TaskBrief | dict[str, Any] | None = None,
 ) -> bool:
-    """Compare actual and approved briefs after applying the same policy."""
+    """用同一继承策略规范化后,精确比较实际与已批准 TaskBrief。
+
+    Sub-Agent 在消费 `next_actions` 或执行 `REVISE_CHILD` 前调用,防止模型篡改父级审批内容。
+    """
     actual_brief = normalize_task_brief(
         actual,
         parent_task_brief=parent_task_brief,

+ 52 - 19
cyber_agent/core/resource_budget.py

@@ -1,9 +1,8 @@
-"""Single-process resource budgets for a Recursive Agent tree.
+"""Recursive Agent 任务树的单进程资源预算。
 
-The environment is read once when a root Trace is created.  Callers persist
-the resulting :class:`ResourceBudget` snapshot on that root Trace and pass the
-same snapshot to every descendant operation.  Dynamic usage is stored through
-``TraceStore`` and guarded by one in-process lock per root Trace.
+Runner 创建根 Trace 时只读取一次环境配置,将 ResourceBudget 快照固化在根 Trace;
+Runner 的模型调用、Sub-Agent 创建和 Validator 沿用同一快照。动态用量由 TraceStore
+单独持久化,ResourceBudgetController 以每个根 Trace 一把进程内异步锁保护。
 """
 
 from __future__ import annotations
@@ -94,7 +93,10 @@ def _positive_float(
 
 @dataclass(frozen=True)
 class ResourceBudget:
-    """Immutable limits captured for one Recursive root Trace."""
+    """一棵 Recursive 任务树不可变的资源上限快照。
+
+    `AgentRunner._prepare_new_trace` 从环境创建它并写入根 Trace,子孙执行始终从根 Trace 恢复。
+    """
 
     enabled: bool = True
     max_total_agents: int = DEFAULT_MAX_TOTAL_AGENTS
@@ -132,6 +134,10 @@ class ResourceBudget:
         cls,
         environ: Mapping[str, str] | None = None,
     ) -> "ResourceBudget":
+        """在新建 Recursive 根 Trace 时解析并校验预算环境变量。
+
+        只由 Runner 的根 Trace 初始化阶段调用,Legacy 和既有 Trace 不会重新读取。
+        """
         values = os.environ if environ is None else environ
         return cls(
             enabled=_strict_bool(values, RESOURCE_BUDGET_ENABLED_ENV, True),
@@ -157,6 +163,10 @@ class ResourceBudget:
 
     @classmethod
     def from_dict(cls, value: Mapping[str, Any]) -> "ResourceBudget":
+        """从根 Trace context 恢复已固化的预算快照。
+
+        Runner 在每次 Recursive 模型调用或子 Agent 预留前调用,字段缺失或多余都会失败。
+        """
         if not isinstance(value, Mapping):
             raise ValueError("resource budget snapshot must be an object")
         expected = {field.name for field in cls.__dataclass_fields__.values()}
@@ -176,7 +186,10 @@ class ResourceBudget:
 
 @dataclass
 class ResourceUsage:
-    """Persisted, cumulative usage for one Recursive Agent tree."""
+    """一棵 Recursive 任务树已持久化的累计用量。
+
+    ResourceBudgetController 每次预留或记账时从 TraceStore 读取、校验并原子替换该快照。
+    """
 
     total_agents: int
     llm_calls: int
@@ -277,11 +290,11 @@ class ResourceUsage:
 
 
 class ResourceBudgetStateError(RuntimeError):
-    """The persisted tree usage is missing or invalid, so execution must stop."""
+    """任务树持久化用量缺失或损坏,执行必须失败关闭。"""
 
 
 class ResourceBudgetExceeded(RuntimeError):
-    """A tree resource limit was denied or exceeded."""
+    """某项任务树资源预留被拒绝,或响应记账后已超限。"""
 
     def __init__(
         self,
@@ -303,7 +316,10 @@ _ROOT_LOCKS: WeakValueDictionary[
 
 
 class ResourceBudgetController:
-    """Atomic, single-process checks over a root Trace's persisted usage."""
+    """根据持久化用量执行单进程原子预留和记账。
+
+    AgentRunner 持有该控制器:模型/Validator 调用由 Runner 记账,子 Agent 数由 `agent()` 创建阶段预留。
+    """
 
     def __init__(
         self,
@@ -329,6 +345,10 @@ class ResourceBudgetController:
         *,
         initial_agents: int = 1,
     ) -> ResourceUsage:
+        """为新 Recursive 根 Trace 初始化用量,并将根 Agent 计入总数。
+
+        `AgentRunner._prepare_new_trace` 在根 Trace 和预算快照落库后调用;已有用量时幂等返回。
+        """
         if (
             isinstance(initial_agents, bool)
             or not isinstance(initial_agents, int)
@@ -354,6 +374,10 @@ class ResourceBudgetController:
             return usage
 
     async def get_usage(self, root_trace_id: str) -> ResourceUsage:
+        """读取并严格校验根 Trace 的当前累计用量。
+
+        所有预留、记账与运行状态查询共用该入口,文件缺失或损坏时 fail closed。
+        """
         try:
             raw = await self.trace_store.get_resource_usage(root_trace_id)
         except Exception as exc:
@@ -372,6 +396,10 @@ class ResourceBudgetController:
         budget: ResourceBudget,
         count: int,
     ) -> ResourceUsage:
+        """在创建一批本地业务子 Trace 前原子预留 Agent 数。
+
+        Sub-Agent `_run_agents` 在六孩子临界区内调用,超限时整批拒绝且不创建 Trace。
+        """
         if isinstance(count, bool) or not isinstance(count, int) or count <= 0:
             raise ValueError("count must be a positive integer")
         async with self._lock(root_trace_id):
@@ -397,10 +425,9 @@ class ResourceBudgetController:
         budget: ResourceBudget,
         count: int,
     ) -> ResourceUsage:
-        """Roll back a reservation for children whose Trace was never created.
+        """回滚批次预留中尚未创建 Trace 的 Agent 数。
 
-        This is not a refund mechanism for stopped, failed, or rewound Agents.
-        Callers may only release the uncreated remainder of a batch reservation.
+        仅由 Sub-Agent 初始化失败时调用;已创建、停止、失败或回溯的 Agent 都不退还。
         """
         del budget  # The persisted count is rolled back even after another limit expires.
         if isinstance(count, bool) or not isinstance(count, int) or count <= 0:
@@ -423,6 +450,10 @@ class ResourceBudgetController:
         *,
         purpose: LLMCallPurpose = "ordinary",
     ) -> ResourceUsage:
+        """在模型请求发出前预留一次调用,必要时保留根验收槽位。
+
+        `AgentRunner.call_recursive_llm` 为普通 Agent、子 Validator 和根 Validator 统一调用该入口。
+        """
         if purpose not in {"ordinary", "root_validator"}:
             raise ValueError("purpose must be 'ordinary' or 'root_validator'")
         async with self._lock(root_trace_id):
@@ -454,6 +485,10 @@ class ResourceBudgetController:
         completion_tokens: int,
         cost_usd: float,
     ) -> ResourceUsage:
+        """在框架模型响应返回后登记 Token 和成本。
+
+        Runner 在已预留调用次数后使用;响应造成超限时会先持久化实际用量再报错。
+        """
         self._validate_reported_usage(prompt_tokens, completion_tokens, cost_usd)
         async with self._lock(root_trace_id):
             usage = await self.get_usage(root_trace_id)
@@ -476,10 +511,9 @@ class ResourceBudgetController:
         completion_tokens: int,
         cost_usd: float,
     ) -> ResourceUsage:
-        """Record a tool-internal LLM call that could not be pre-reserved.
+        """事后登记工具内部不能预留的模型调用。
 
-        Actual external usage is always persisted.  If it pushes any dimension
-        over its limit, the method raises only after the atomic replacement.
+        Runner 仅在工具返回 `tool_usage` 时调用;总调用数、Token 和成本都会先落库再检查超限。
         """
         self._validate_reported_usage(prompt_tokens, completion_tokens, cost_usd)
         async with self._lock(root_trace_id):
@@ -618,10 +652,9 @@ class ResourceBudgetController:
 
     @staticmethod
     def _raise_if_terminal(usage: ResourceUsage, budget: ResourceBudget) -> None:
-        """Reject new work after a response makes the tree irrecoverably over budget.
+        """响应已造成 Token、成本或时长超限后,拒绝新工作。
 
-        Agent-count denials only prevent more children.  An ordinary-call denial
-        also leaves the explicitly reserved root-validator slot usable.
+        Agent 数超限只禁止新建孩子;普通调用耗尽仍保留明确预留的根 Validator 槽位。
         """
         prefix = "budget_exhausted:"
         reason = usage.exhausted_reason or ""

+ 56 - 17
cyber_agent/core/runner.py

@@ -278,7 +278,10 @@ class AgentRunner:
         self,
         trace_id: str,
     ) -> tuple[str, ResourceBudget] | None:
-        """Resolve the immutable Recursive root budget for one local Trace."""
+        """解析本地 Trace 所属 Recursive 根树的不可变预算快照。
+
+        由模型调用、工具用量记账和子 Agent 创建入口调用;Legacy Trace 直接返回无预算。
+        """
         if not self.trace_store:
             return None
         trace = await self.trace_store.get_trace(trace_id)
@@ -306,7 +309,10 @@ class AgentRunner:
         fail_on_post_response_exhaustion: bool = False,
         **kwargs: Any,
     ) -> Dict[str, Any]:
-        """Call an LLM and atomically account for Recursive tree usage."""
+        """Recursive 树中统一的 LLM 调用和资源记账入口。
+
+        Agent 主循环、上下文压缩、图片描述和 Validator 共用;请求前预留次数,响应后记账。
+        """
         llm = call or self.llm_call
         if not llm:
             raise ValueError("llm_call function not provided")
@@ -342,7 +348,10 @@ class AgentRunner:
         trace_id: str,
         tool_usage: Dict[str, Any],
     ) -> None:
-        """Account for a tool-owned model call when the tool reports usage."""
+        """登记工具内部自行发起的模型用量。
+
+        Agent 主循环在 Tool Result 携带 ``tool_usage`` 时调用,并计入同一棵 Recursive 树。
+        """
         resolved = await self._resource_budget_for_trace(trace_id)
         if resolved is None:
             return
@@ -370,7 +379,10 @@ class AgentRunner:
         deterministic_failure: Optional[Dict[str, Any]] = None,
         root_validator: bool = False,
     ) -> ValidationRun:
-        """Run one framework-owned, tool-free validation Trace."""
+        """运行一次由框架控制、不带工具的独立验收 Trace。
+
+        子 Agent 结果汇合或根 Agent 候选答案完成时调用,返回权威 ``ValidationResult``。
+        """
         if not self.trace_store or not self.llm_call:
             raise RuntimeError("Validator requires trace_store and llm_call")
         evaluated = await self.trace_store.get_trace(evaluated_trace_id)
@@ -665,8 +677,8 @@ class AgentRunner:
         """
         停止运行中的 Trace
 
-        设置取消信号,agent loop 在下一个 LLM 调用前检查并退出。
-        Trace 状态置为 "stopped"
+        Trace API 定位实际 Runner 后调用本方法;Legacy 只停当前 Trace,
+        Recursive 由 ``request_stop`` 把信号传给当前进程内已登记的子树
 
         Returns:
             True 如果成功发送停止信号,False 如果该 trace 不在运行中
@@ -674,7 +686,10 @@ class AgentRunner:
         return self.request_stop(trace_id)
 
     def request_stop(self, trace_id: str) -> bool:
-        """同步设置停止信号,供 API 与 stdin 回调共用。"""
+        """同步设置停止信号,供 API 与 stdin 回调共用。
+
+        Recursive Trace 会沿进程内父子登记表向下遍历,不影响父级或兄弟分支。
+        """
         if trace_id not in self._cancel_events:
             return False
 
@@ -700,7 +715,10 @@ class AgentRunner:
         parent_trace_id: str,
         child_trace_id: str,
     ) -> asyncio.Event:
-        """登记已创建但可能仍在排队的 Recursive 直属孩子。"""
+        """登记已创建但可能仍在排队的 Recursive 直属孩子。
+
+        ``agent`` 工具预创建孩子后、Runner 启动 Validator 前调用,使父级停止能传递到后代。
+        """
         event = self._cancel_events.setdefault(child_trace_id, asyncio.Event())
         self._recursive_active_traces[child_trace_id] = event
         self._active_children.setdefault(parent_trace_id, set()).add(child_trace_id)
@@ -824,7 +842,9 @@ class AgentRunner:
         config: RunConfig,
     ) -> Tuple[Trace, Optional[GoalTree], int]:
         """
-        准备 Trace:创建新的或加载已有的
+        准备 Trace:为 ``run`` 选择新建、续跑或回溯路径。
+
+        在 Agent 主循环前调用,并在任何 Trace 操作前校验已废弃的配置项。
 
         Returns:
             (trace, goal_tree, next_sequence)
@@ -840,7 +860,10 @@ class AgentRunner:
         messages: List[Dict],
         config: RunConfig,
     ) -> Tuple[Trace, Optional[GoalTree], int]:
-        """创建新 Trace"""
+        """创建并持久化一个新根 Trace。
+
+        ``run`` 首次执行时调用;Recursive 会在此固化模式、根验收标准和树级预算。
+        """
         # 在任何标题生成/LLM 调用前完成模式校验。
         policy = policy_from_environment(recursive_revision=2)
         if policy.mode is AgentMode.RECURSIVE:
@@ -916,7 +939,10 @@ class AgentRunner:
         self,
         config: RunConfig,
     ) -> Tuple[Trace, Optional[GoalTree], int]:
-        """加载已有 Trace(续跑或回溯)"""
+        """加载已有 Trace,决定续跑或回溯。
+
+        ``run`` 携带 ``trace_id`` 时调用;Recursive 只信任持久化模式、预算和协议状态。
+        """
         if not self.trace_store:
             raise ValueError("trace_store required for continue/rewind")
 
@@ -1482,7 +1508,10 @@ class AgentRunner:
         inject_skills: Optional[List[str]] = None,
         skill_recency_threshold: int = 10,
     ) -> AsyncIterator[Union[Trace, Message]]:
-        """ReAct 循环"""
+        """执行 Agent 的 ReAct 主循环。
+
+        ``run`` 在 Trace 准备后调用;Recursive 的预算、停止、工具门禁和根验收都在此串联。
+        """
         trace_id = trace.trace_id
         runtime_tool_names = self._get_runtime_tool_names(config, trace)
         tool_schemas = self._get_runtime_tool_schemas(
@@ -2807,9 +2836,9 @@ class AgentRunner:
         goal_tree: Optional[GoalTree],
     ) -> int:
         """
-        执行回溯:快照 GoalTree,重建干净树,设置 head_sequence
+        回溯 Trace:快照 GoalTree,重建干净树并设置 ``head_sequence``。
 
-        新消息的 parent_sequence 将指向 rewind 点,旧消息通过树结构自然脱离主路径
+        ``_prepare_existing_trace`` 判定为回溯时调用;Recursive 同时重建待审核和待重规划状态
 
         Returns:
             下一个可用的 sequence 号
@@ -3848,7 +3877,10 @@ class AgentRunner:
         *,
         in_side_branch: bool = False,
     ) -> set[str]:
-        """在基础能力上应用 Recursive 协议状态门禁。"""
+        """在 RunConfig 基础能力上应用 Recursive 协议状态门禁。
+
+        主循环每轮调用,使待审核、待执行与报告阶段只暴露当前允许的工具。
+        """
         tool_names = self._get_configured_tool_names(
             config.tools,
             config.tool_groups,
@@ -3883,7 +3915,11 @@ class AgentRunner:
         in_side_branch: bool = False,
         runtime_tool_names: Optional[set[str]] = None,
     ) -> List[Dict]:
-        """按 Trace 持久化模式和当前协议状态过滤工具 Schema。"""
+        """按 Trace 持久化模式和当前协议状态生成工具 Schema。
+
+        主循环把结果交给 LLM;Recursive revision 2 本地委托使用 ``task_brief``,
+        仍保留 ``remote_*`` 的 ``task``,Legacy 和 Recursive revision 1 继续使用 ``task``。
+        """
         tool_names = (
             runtime_tool_names
             if runtime_tool_names is not None
@@ -3933,7 +3969,10 @@ class AgentRunner:
         side_branch_ctx: Optional[SideBranchContext],
         trigger_event: Optional[str],
     ) -> Dict[str, Any]:
-        """构建工具上下文;Recursive 内部字段最后覆盖,不能由调用方伪造。"""
+        """构建 ToolRegistry 执行时注入的隐藏上下文。
+
+        主循环在 dispatch 前调用;Recursive 权限快照和调度配置最后覆盖,不可伪造。
+        """
         framework_context = {
             "store": self.trace_store,
             "trace_id": trace_id,

+ 65 - 2
cyber_agent/core/task_protocol.py

@@ -1,4 +1,8 @@
-"""Recursive revision 2 parent/child task protocol."""
+"""Recursive revision 2 的父子任务协议与持久化状态。
+
+Sub-Agent 使用 TaskBrief 下发任务,子 Agent 通过 TaskReport 上报,父 Agent 使用
+TaskReview 审核。Runner 和两个协议工具共同把待审、后续动作和回退状态保存在 Trace context。
+"""
 
 from __future__ import annotations
 
@@ -25,10 +29,17 @@ ReviewDecision = Literal[
 
 
 class StrictProtocolModel(BaseModel):
+    """协议模型的统一严格基类,拒绝未定义字段并清理字符串空白。"""
+
     model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
 
 
 class TaskBrief(StrictProtocolModel):
+    """父 Agent 下发给直接孩子的结构化任务说明。
+
+    `agent()` 在创建/续跑子 Trace 前规范化,Runner 再将格式化内容作为子 Agent 的任务上下文。
+    """
+
     objective: str = Field(min_length=1)
     reason: str = Field(min_length=1)
     completion_criteria: list[str] = Field(min_length=1)
@@ -51,6 +62,11 @@ class TaskBrief(StrictProtocolModel):
 
 
 class Validation(StrictProtocolModel):
+    """子 Agent 在 TaskReport 中提交的执行者自检。
+
+    `submit_task_report` 校验并持久化它;它不等于框架独立 Validator 产生的权威 ValidationResult。
+    """
+
     hard_passed: bool
     open_issues: list[str] = Field(default_factory=list)
 
@@ -63,6 +79,11 @@ class Validation(StrictProtocolModel):
 
 
 class NextStepSuggestion(StrictProtocolModel):
+    """子 Agent 对直接父级的下一步建议,本身不会创建同级任务。
+
+    父 Agent 在 `review_task_result` 中结合独立验收决定是否采纳、修订或上行。
+    """
+
     direction: NextStepDirection
     reason: str = Field(min_length=1)
     suggested_next_task: TaskBrief | None = None
@@ -77,6 +98,11 @@ class NextStepSuggestion(StrictProtocolModel):
 
 
 class TaskReportSubmission(StrictProtocolModel):
+    """子 Agent 可提交的 TaskReport 负载,不允许模型填写 Trace ID。
+
+    `submit_task_report` 接收该结构,检查结果与自检是否一致后由框架注入子 Trace ID。
+    """
+
     summary: str = Field(min_length=1)
     outcome: TaskOutcome
     validation: Validation
@@ -108,10 +134,20 @@ class TaskReportSubmission(StrictProtocolModel):
 
 
 class TaskReport(TaskReportSubmission):
+    """框架补全 `child_trace_id` 后的权威子任务报告。
+
+    Sub-Agent 汇合孩子结果后将其与 ValidationResult 一起写入父 Trace 的待审队列。
+    """
+
     child_trace_id: str = Field(min_length=1)
 
 
 class TaskReview(StrictProtocolModel):
+    """父 Agent 对一份直接孩子报告的结构化审核结果。
+
+    `review_task_result` 根据 ValidationResult 的决策矩阵构建它,然后持久化修订、新任务或回退动作。
+    """
+
     parent_trace_id: str = Field(min_length=1)
     child_trace_id: str = Field(min_length=1)
     decision: ReviewDecision
@@ -137,6 +173,10 @@ class TaskReview(StrictProtocolModel):
 
 
 def new_task_protocol(task_brief: TaskBrief | dict | None = None) -> dict[str, Any]:
+    """创建一份完整的 Recursive 任务协议初始状态。
+
+    Runner 初始化根 Trace,Sub-Agent 创建子 Trace 时调用,状态随 Trace context 持久化。
+    """
     brief = TaskBrief.model_validate(task_brief).model_dump() if task_brief else None
     return {
         "task_brief": brief,
@@ -156,6 +196,10 @@ def new_task_protocol(task_brief: TaskBrief | dict | None = None) -> dict[str, A
 
 
 def ensure_task_protocol(context: dict[str, Any]) -> dict[str, Any]:
+    """获取 Trace context 中的协议状态,并幂等补齐缺失字段。
+
+    Runner、Sub-Agent 和报告/审核工具在读写任务状态前统一调用此入口。
+    """
     state = context.get("task_protocol")
     if not isinstance(state, dict):
         state = new_task_protocol()
@@ -167,7 +211,10 @@ def ensure_task_protocol(context: dict[str, Any]) -> dict[str, Any]:
 
 
 def rebuild_pending_replans(state: dict[str, Any]) -> list[dict[str, Any]]:
-    """Rebuild unresolved replans after review history is rewound."""
+    """在 Trace 回溯截断审核历史后,重建尚未解决的重规划请求。
+
+    `AgentRunner._rewind` 清理截断点之后的协议记录时调用,避免丢失同批次的 `REPLAN_CURRENT`。
+    """
     pending_batches = {
         entry.get("received_at_sequence")
         for entry in state.get("pending_reviews", {}).values()
@@ -195,6 +242,10 @@ def rebuild_pending_replans(state: dict[str, Any]) -> list[dict[str, Any]]:
 
 
 def protocol_error_report(child_trace_id: str, reason: str) -> TaskReport:
+    """在子 Agent 未能提交合法报告时生成框架级失败报告。
+
+    Runner 的完成门禁和 Sub-Agent 结果汇合阶段调用,使父级仍能审核并恢复。
+    """
     return TaskReport(
         child_trace_id=child_trace_id,
         summary="Task protocol failed before a valid report was submitted.",
@@ -209,6 +260,10 @@ def protocol_error_report(child_trace_id: str, reason: str) -> TaskReport:
 
 
 def stopped_task_report(child_trace_id: str, reason: str) -> TaskReport:
+    """为被协作式停止的子 Agent 生成 `failed` TaskReport。
+
+    Sub-Agent 的停止汇合路径调用,然后交由父 Agent 选择修订孩子或失败结束。
+    """
     return TaskReport(
         child_trace_id=child_trace_id,
         summary="Child Agent execution was stopped before completion.",
@@ -229,6 +284,10 @@ def pending_review_entry(
     validation_result: dict[str, Any],
     received_at_sequence: int,
 ) -> dict[str, Any]:
+    """组装父 Trace `pending_reviews` 中的一份待审记录。
+
+    Sub-Agent 完成子 Trace 验收后调用,将 TaskReport、ValidationResult、原 Goal 和序列绑定。
+    """
     return {
         "goal_id": goal_id,
         "task_report": report.model_dump(),
@@ -239,6 +298,10 @@ def pending_review_entry(
 
 
 def format_task_brief(task_brief: TaskBrief) -> str:
+    """将结构化 TaskBrief 格式化为子 Agent 可执行的任务文本。
+
+    ``agent`` 完成规范化后调用,生成 Runner 作为 user message 传给子 Agent 的任务说明。
+    """
     criteria = "\n".join(
         f"{index}. {item}"
         for index, item in enumerate(task_brief.completion_criteria, start=1)

+ 44 - 10
cyber_agent/core/validation.py

@@ -1,4 +1,8 @@
-"""Independent, tool-free validation for Recursive revision 2 traces."""
+"""Recursive revision 2 Trace 的独立、无工具 LLM 验收。
+
+Runner 在子 Agent 提交 TaskReport 后或根 Agent 候选完成时创建 Validator Trace,
+本模块裁剪持久化轨迹、单次调用模型并由框架注入 Trace ID,验收失败时默认关闭。
+"""
 
 from __future__ import annotations
 
@@ -61,7 +65,10 @@ class _StrictModel(BaseModel):
 
 
 class ValidationDecision(_StrictModel):
-    """The only fields the validator model is permitted to choose."""
+    """Validator 模型唯一允许决定的审核内容。
+
+    `parse_validation_result` 对单次 LLM 输出严格解析,Trace ID 和被验收对象不开放给模型。
+    """
 
     outcome: Literal["passed", "failed"]
     scope: ValidationScope
@@ -86,7 +93,10 @@ class ValidationDecision(_StrictModel):
 
 
 class ValidationResult(_StrictModel):
-    """Framework-owned validation result persisted with a child report."""
+    """框架持有并与子任务报告一起持久化的权威验收结果。
+
+    Sub-Agent 将它写入父 Trace 待审记录,`review_task_result` 据此限制父 Agent 可选的审核决策。
+    """
 
     validator_trace_id: str = Field(min_length=1)
     evaluated_trace_id: str = Field(min_length=1)
@@ -116,7 +126,11 @@ class ValidationResult(_StrictModel):
 
 @dataclass(frozen=True)
 class ValidationRun:
-    """Validation result plus the usage needed by the tree budget controller."""
+    """一次 Validator 运行的结果、Trace ID 与模型用量。
+
+    LLMValidator 将用量和耗时写入 Validator Trace;Runner 取出结果进入审核。
+    树级预算由外层 ``call_recursive_llm`` 在模型调用前后登记。
+    """
 
     result: ValidationResult
     trace_id: str
@@ -133,7 +147,10 @@ def validation_error(
     scope: ValidationScope,
     reason: str,
 ) -> ValidationResult:
-    """Create a deterministic, fail-closed result for validator failures."""
+    """为 Validator 异常或非法输出生成确定性、失败关闭的结果。
+
+    LLMValidator 在输入组装、模型调用或 JSON 解析失败时调用,不再追加格式修正调用。
+    """
 
     detail = reason.strip() or "Validator failed without an error description"
     return ValidationResult(
@@ -154,7 +171,10 @@ def parse_validation_result(
     evaluated_trace_id: str,
     expected_scope: ValidationScope,
 ) -> ValidationResult:
-    """Strictly parse one model decision and inject framework-owned IDs."""
+    """严格解析一次模型决策,并注入框架持有的 Trace ID。
+
+    `LLMValidator.validate` 在确认响应无工具调用后使用,验收范围不一致也会直接失败。
+    """
 
     raw = json.loads(content)
     if not isinstance(raw, dict):
@@ -184,7 +204,7 @@ def _jsonable(value: Any) -> Any:
 
 
 def _visible_message_content(role: str | None, content: Any) -> Any:
-    """Remove persisted hidden reasoning while retaining observable outputs."""
+    """移除持久化的隐藏推理,仅保留 Validator 可观察的输出。"""
     if role == "assistant" and isinstance(content, Mapping):
         content = {
             key: value
@@ -249,7 +269,10 @@ def build_validation_packet(
     candidate_output: str | None = None,
     max_chars: int = MAX_VALIDATION_INPUT_CHARS,
 ) -> str:
-    """Build a bounded packet, retaining fixed contracts before recent history."""
+    """构建有长度上限的验收包,优先保留固定任务契约和最新轨迹。
+
+    `LLMValidator.validate` 在发起单次验收前调用,包含 TaskBrief、TaskReport、标准和真实主路径消息。
+    """
 
     brief = _jsonable(task_brief)
     report = _jsonable(task_report)
@@ -290,7 +313,10 @@ def build_validation_packet(
 
 
 class LLMValidator:
-    """One-call independent validator with no tools and no Agent loop."""
+    """无工具、无 Agent 循环的单次 LLM 独立验收器。
+
+    `AgentRunner.validate_recursive_trace` 在子报告汇合和根完成门禁中创建它,并统一接入预算与取消。
+    """
 
     def __init__(
         self,
@@ -319,6 +345,11 @@ class LLMValidator:
         model: str | None = None,
         validator_trace_id: str | None = None,
     ) -> ValidationRun:
+        """创建 Validator Trace,组装实际轨迹并完成一次 LLM 验收。
+
+        Runner 为 ``satisfied/partial`` 子报告或根候选答案调用;结果进入审核,
+        模型用量写入 Validator Trace,树级预算已由 Runner 外层包装登记。
+        """
         if scope not in _VALIDATION_SCOPES:
             raise ValueError(f"unsupported validation scope: {scope}")
 
@@ -427,7 +458,10 @@ class LLMValidator:
         retry_from: RetryFrom | None = None,
         validator_trace_id: str | None = None,
     ) -> ValidationRun:
-        """Persist a non-passing result without spending an LLM call."""
+        """不消耗 LLM 调用,为已知失败/错误持久化 Validator Trace。
+
+        Runner 遇到子 Agent 失败、停止或协议错误时调用,使父级仍收到可审核的 ValidationResult。
+        """
 
         trace_id = validator_trace_id or generate_sub_trace_id(
             evaluated_trace.trace_id,

+ 33 - 20
cyber_agent/tools/builtin/subagent.py

@@ -1,10 +1,8 @@
-"""
-Sub-Agent 工具 - agent / evaluate
+"""Sub-Agent 的创建、调度、汇合与旧评估工具。
 
-agent: 创建子 Agent 执行任务。
-  - 本地:`agent_type` 无 `remote_` 前缀,进程内执行(单任务 delegate / 多任务并行 explore)
-  - 远端:`agent_type` 以 `remote_` 开头,HTTP 路由到 KnowHub 服务器的 /api/agent
-evaluate: 评估目标执行结果是否满足要求
+``agent`` 由父 Agent 调用:Legacy 只创建一层,Recursive 还会执行深度、孩子数、
+权限、预算和审核门禁;本地任务进程内运行,``remote_*`` 仍通过 KnowHub HTTP 路由。
+``evaluate`` 保留给 Legacy 和旧的 Recursive revision 1;revision 2 使用框架管理的独立 Validator。
 """
 
 import asyncio
@@ -232,6 +230,10 @@ async def _load_or_create_task_report(
     child_result_status: str,
     generated_at_sequence: int,
 ) -> TaskReport:
+    """读取孩子已提交的报告,或为异常、停止生成框架报告。
+
+    子任务执行结束后由报告汇合阶段调用,将异常和停止统一转成可审核的 ``TaskReport``。
+    """
     child = await store.get_trace(child_trace_id)
     if not child:
         return protocol_error_report(child_trace_id, fallback_reason)
@@ -292,6 +294,10 @@ async def _record_pending_task_reports(
     child_results: List[tuple[str, Dict[str, Any]]],
     received_at_sequence: int,
 ) -> List[TaskReport]:
+    """验收一批子 Agent 结果并写入父 Trace 的待审核队列。
+
+    ``_run_agents`` 汇合孩子后按输入顺序调用,必要时触发 Validator 并更新 Goal 状态。
+    """
     state = ensure_task_protocol(parent_trace.context)
     reports: List[TaskReport] = []
     for child_trace_id, result in child_results:
@@ -417,7 +423,10 @@ async def _execute_child_spec(
     store,
     semaphore: Optional[asyncio.Semaphore] = None,
 ) -> Dict[str, Any]:
-    """延迟启动一个孩子;排队期间收到停止信号时不进入 Runner。"""
+    """按调度规格延迟启动一个子 Agent。
+
+    ``_run_agents`` 串行执行或在并行信号量内调用;排队期间停止则不进入 Runner。
+    """
     trace_id = spec["trace_id"]
 
     async def execute() -> Dict[str, Any]:
@@ -441,6 +450,10 @@ async def _execute_child_spec(
 
 
 def _get_recursive_parent_capabilities(context: dict) -> Optional[set[str]]:
+    """从隐藏 Tool Context 读取并校验父 Agent 的冻结权限快照。
+
+    ``_run_agents`` 和子级权限计算共用;快照缺失或格式不合法时 Recursive 拒绝创建孩子。
+    """
     runner = context.get("runner")
     capabilities = context.get(RECURSIVE_CAPABILITY_TOOLS_CONTEXT_KEY)
     if (
@@ -459,7 +472,10 @@ def _get_allowed_tools(
     agent_depth: int,
     policy: AgentPolicy,
 ) -> Optional[List[str]]:
-    """按子 Agent 深度生成唯一的工具权限列表。"""
+    """按父级能力、子级固定规则和当前深度计算有效工具集合。
+
+    创建子 Runner 前调用;Recursive 权限只能逐层收紧,最深层会移除 ``agent``。
+    """
     runner = context.get("runner")
     if runner and hasattr(runner, "tools") and hasattr(runner.tools, "get_tool_names"):
         registered_tools = set(runner.tools.get_tool_names())
@@ -730,13 +746,10 @@ async def _run_agents(
     task_briefs: Optional[List[TaskBrief]] = None,
 ) -> Dict[str, Any]:
     """
-    统一 agent 执行逻辑。
-
-    single (len(tasks)==1): delegate 模式,全量工具
-    multi (len(tasks)>1): explore 模式,并行执行
+    本地 Sub-Agent 的统一创建、调度和结果汇合入口。
 
-    本地 Sub-Agent 的深度、直接孩子配额和工具权限由父 Trace
-    已持久化的 AgentPolicy 决定,不重新读取环境变量
+    ``agent`` 完成参数归一化后调用;此处执行 Spawn Guard、权限交集、串并行调度和
+    报告汇合,并始终使用父 Trace 已持久化的 ``AgentPolicy``。
     """
     single = len(tasks) == 1
     parent_trace = await store.get_trace(trace_id)
@@ -858,7 +871,7 @@ async def _run_agents(
     created_trace_ids: list[str] = []
 
     async def fail_created_children(error: Exception) -> None:
-        """Close Recursive children that never reached the execution phase."""
+        """将已创建但未进入执行阶段的 Recursive 孩子收敛为失败。"""
         for created_trace_id in created_trace_ids:
             created = await store.get_trace(created_trace_id)
             if created and created.status == "running":
@@ -870,7 +883,7 @@ async def _run_agents(
                 )
 
     async def run_initialization(operation):
-        """Apply one pre-execution write with consistent child cleanup."""
+        """执行一次运行前持久化,失败时统一清理已创建孩子。"""
         try:
             return await operation
         except Exception as exc:
@@ -1362,7 +1375,7 @@ async def _run_remote_agent(
     通过 HTTP 调用 KnowHub 服务器上的远端 Agent。
 
     远端 Agent 的 tools / model / prompt 由服务器端 preset 决定。
-    skills 由 caller 指定,服务器按 agent_type 的白名单过滤
+    skills 由 caller 指定并原样发给服务器,最终权限由远端实现决定
     """
     import httpx
 
@@ -1421,11 +1434,11 @@ async def agent(
     context: Optional[dict] = None,
 ) -> Dict[str, Any]:
     """
-    创建子 Agent 执行任务
+    父 Agent 用来创建或续跑直属子 Agent 的公开工具
 
     路由规则:
     - agent_type 以 "remote_" 开头:HTTP 调用 KnowHub 服务器的 /api/agent(仅单任务,无本地文件访问)
-    - 否则本地执行:单任务 str = delegate(全量工具);多任务 List[str] = explore(并行、只读)
+    - 否则本地执行:Legacy/Recursive revision 1 使用 ``task``;revision 2 使用 ``task_brief``
 
     Args:
         task: Legacy/Recursive revision 1 任务描述。
@@ -1435,7 +1448,7 @@ async def agent(
         agent_type: 子 Agent 类型。带 "remote_" 前缀走远端;否则本地 preset
         skills: 指定本次调用使用的 skill 列表
                 - 本地:附加到 system prompt
-                - 远端:由服务器按 agent_type 白名单过滤(如 remote_librarian 允许 ask_strategy / upload_strategy)
+                - 远端:原样发送,由远端实现决定最终权限
         context: 框架自动注入的上下文
     """
     try:

+ 13 - 1
cyber_agent/tools/builtin/task_protocol.py

@@ -1,4 +1,8 @@
-"""Tools for Recursive revision 2 task reporting and parent review."""
+"""Recursive 任务报告与父级审核工具。
+
+子 Agent 通过 ``submit_task_report`` 提交结构化结果;Validator 验收并汇合后,
+直接父 Agent 必须通过 ``review_task_result`` 批准下一步,两者均不向 Legacy 暴露。
+"""
 
 from __future__ import annotations
 
@@ -49,6 +53,10 @@ async def submit_task_report(
     task_report: TaskReportSubmission,
     context: dict | None = None,
 ) -> dict[str, Any]:
+    """提交当前 Recursive 非根 Agent 的结构化任务报告。
+
+    子 Agent 在结束前调用;Runner 会拒绝未提交报告或尚有待审核孩子的返回。
+    """
     if not context:
         return _error("context is required")
     store = context.get("store")
@@ -125,6 +133,10 @@ async def review_task_result(
     approved_next_task: TaskBrief | None = None,
     context: dict | None = None,
 ) -> dict[str, Any]:
+    """审核一份直属孩子的待处理 ``TaskReport``。
+
+    父 Agent 在 ``pending_review`` 阶段调用;决策受 Validator 结果约束并持久化下一动作。
+    """
     if not context:
         return _error("context is required")
     store = context.get("store")

+ 5 - 1
cyber_agent/tools/registry.py

@@ -232,7 +232,10 @@ class ToolRegistry:
 		allowed_tool_names: Optional[set[str]] = None,
 	) -> str:
 		"""
-		执行工具调用
+		执行一次工具调用,是 AgentRunner 的统一 dispatch 入口。
+
+		Recursive 运行会传入 ``allowed_tool_names`` 作为 Schema 之外的硬门禁,
+		在参数注入、统计和函数调用前拒绝伪造的未授权 Tool Call。
 
 		Args:
 			name: 工具名称
@@ -240,6 +243,7 @@ class ToolRegistry:
 			uid: 用户ID(自动注入)
 			context: 额外上下文
 			sensitive_data: 敏感数据字典(用于替换 <secret> 占位符)
+			allowed_tool_names: 当前运行允许的工具集;None 表示保留旧行为
 
 		Returns:
 			JSON 字符串格式的结果

+ 3 - 1
cyber_agent/tools/schema.py

@@ -82,7 +82,9 @@ class SchemaGenerator:
     @classmethod
     def generate(cls, func: callable, hidden_params: Optional[List[str]] = None) -> Dict[str, Any]:
         """
-        从函数生成 OpenAI Tool Schema
+        从工具函数签名和 docstring 生成 OpenAI Tool Schema。
+
+        ``@tool`` 注册时调用;Recursive 的 ``TaskBrief`` 等 Pydantic 参数会在此展开并内联引用。
 
         Args:
             func: 要生成 Schema 的函数