Browse Source

补充递归追踪与页面钻取说明

SamLee 20 giờ trước cách đây
mục cha
commit
cb3854d016

+ 14 - 8
cyber_agent/trace/goal_models.py

@@ -1,9 +1,8 @@
 """
-Goal 数据模型
+Goal 数据模型与目标树操作。
 
-Goal: 执行计划中的目标节点
-GoalTree: 目标树,管理整个执行计划
-GoalStats: 目标统计信息
+GoalTree 是 Runner 执行计划的可视化投影;Legacy 保留自动推进,
+Recursive 由协议审核流显式控制等待、待审核、失败和完成状态。
 """
 
 from dataclasses import dataclass, field
@@ -140,7 +139,8 @@ class GoalTree:
     """
     目标树 - 管理整个执行计划
 
-    使用扁平列表 + parent_id 构建层级结构
+    使用扁平列表 + parent_id 构建层级结构。goal_tool 负责日常读写,
+    Recursive 审核工具只把协议状态投影到这棵树,协议权威数据仍在 Trace context。
     """
     mission: str                             # 总任务描述
     goals: List[Goal] = field(default_factory=list)  # 扁平列表(通过 parent_id 构建层级)
@@ -340,7 +340,9 @@ class GoalTree:
         cascade_parent: bool = True,
     ) -> Goal:
         """
-        完成指定 Goal
+        完成指定 Goal,并按开关决定是否自动切换兄弟或级联完成父目标。
+
+        goal_tool 在 Legacy 中保持两项旧行为;Recursive 关闭它们,将焦点交回直接父 Goal 由当前 Agent 重新决策。
 
         Args:
             goal_id: 要完成的目标 ID
@@ -399,7 +401,9 @@ class GoalTree:
         return goal
 
     def fail(self, goal_id: str, reason: str) -> Goal:
-        """将 Goal 标记为失败,不级联父级也不自动切换兄弟。"""
+        """将 Goal 标记为失败,不级联父级也不自动切换兄弟。
+
+        该方法供直接 GoalTree 调用者使用;TaskReview FAIL 的持久化路径通过 TraceStore.update_goal() 执行同样语义。"""
         goal = self.find(goal_id)
         if not goal:
             raise ValueError(f"Goal not found: {goal_id}")
@@ -570,7 +574,9 @@ class GoalTree:
 
     def rebuild_for_rewind(self, cutoff_time: datetime) -> "GoalTree":
         """
-        为 Rewind 重建干净的 GoalTree
+        为 Rewind 重建干净的 GoalTree,移除截断点之后的目标并重置未完成状态。
+
+        AgentRunner 的 rewind 流程调用它;Recursive 的 waiting_children/pending_review 也会回到 pending。
 
         以截断点消息的 created_at 为界:
         - 保留 created_at <= cutoff_time 的所有 goals(无论状态)

+ 9 - 4
cyber_agent/trace/goal_tool.py

@@ -1,7 +1,8 @@
 """
-Goal 工具 - 计划管理
+Goal 工具与目标树持久化入口。
 
-提供 goal 工具供 LLM 管理执行计划。
+ToolRegistry 向 LLM 暴露 goal(),实际变更由 goal_tool() 执行;
+Recursive 还会在此阻止绕过待审核或已批准下一步的 Goal 修改。
 """
 
 import logging
@@ -122,7 +123,9 @@ async def goal(
     context: Optional[dict] = None
 ) -> str:
     """
-    管理执行计划,添加/完成/放弃目标,切换焦点。
+    LLM 可调用的计划管理工具,由 ToolRegistry 注入当前 GoalTree 和 Trace 上下文。
+
+    它只解析公开参数,再转交 goal_tool() 持久化状态并发送前端事件。
 
     Args:
         add: 添加目标(逗号分隔多个)
@@ -181,7 +184,9 @@ async def goal_tool(
     context: Optional[dict] = None,
 ) -> str:
     """
-    管理执行计划。
+    Goal 工具的共用实现,更新 GoalTree、TraceStore 和实时事件。
+
+    Recursive 会先读持久化协议状态,关闭自动父级完成与兄弟切换,把后续决策留给 TaskReview。
 
     Args:
         tree: GoalTree 实例

+ 15 - 6
cyber_agent/trace/protocols.py

@@ -1,7 +1,8 @@
 """
-Trace Storage Protocol - Trace 存储接口定义
+Trace 存储协议。
 
-使用 Protocol 定义接口,允许不同的存储实现(内存、PostgreSQL、Neo4j 等)
+使用 Protocol 约束 Runner、Sub-Agent、资源预算和 API 依赖的存储能力,
+使文件系统或未来的数据库实现可替换而不改上层调用。
 """
 
 from typing import Protocol, List, Optional, Dict, Any, runtime_checkable
@@ -12,7 +13,9 @@ from .goal_models import GoalTree, Goal
 
 @runtime_checkable
 class TraceStore(Protocol):
-    """Trace + Message + GoalTree 存储接口"""
+    """Trace、Message、GoalTree 与 Recursive 树级用量的统一存储接口。
+
+    Runner 负责主路径读写,Sub-Agent 用父子过滤查询直接孩子,预算控制器读写根树用量。"""
 
     # ===== Trace 操作 =====
 
@@ -52,7 +55,9 @@ class TraceStore(Protocol):
         parent_trace_id: Optional[str] = None,
         created_by_tool: Optional[str] = None,
     ) -> List[Trace]:
-        """列出 Traces"""
+        """按条件列出 Trace,过滤必须在 limit 之前完成。
+
+        Recursive Spawn Guard 用 parent_trace_id + created_by_tool 精确统计直接业务孩子。"""
         ...
 
     # ===== Recursive 树级资源用量 =====
@@ -61,7 +66,9 @@ class TraceStore(Protocol):
         self,
         root_trace_id: str,
     ) -> Optional[Dict[str, Any]]:
-        """读取 Recursive 根 Trace 的树级累计用量。"""
+        """读取 Recursive 根 Trace 的树级累计用量。
+
+        ResourceBudgetController 在预留 Agent/LLM 或登记 Token/成本前后调用。"""
         ...
 
     async def replace_resource_usage(
@@ -69,7 +76,9 @@ class TraceStore(Protocol):
         root_trace_id: str,
         usage: Dict[str, Any],
     ) -> None:
-        """原子替换 Recursive 根 Trace 的树级累计用量。"""
+        """原子替换 Recursive 根 Trace 的树级累计用量。
+
+        ResourceBudgetController 在单进程根树锁内调用,存储实现需避免留下半写入快照。"""
         ...
 
     # ===== GoalTree 操作 =====

+ 18 - 12
cyber_agent/trace/run_api.py

@@ -1,8 +1,8 @@
 """
-Trace 控制 API — 新建 / 运行 / 停止 / 反思
+Trace 控制 API:新建、续跑、回溯、停止与反思。
 
-提供 POST 端点触发 Agent 执行和控制。需要通过 set_runner() 注入 AgentRunner 实例
-执行在后台异步进行,客户端通过 WebSocket (/api/traces/{trace_id}/watch) 监听实时更新
+路由将请求转为 RunConfig 并在后台驱动 AgentRunner,客户端通过 WebSocket 读取事件
+Recursive 的根完成标准和子树停止也在这一 API 边界传递给实际 Runner
 
 端点:
   POST /api/traces              — 新建 Trace 并执行
@@ -164,6 +164,8 @@ class CommitResponse(BaseModel):
 # ===== 后台执行 =====
 
 _running_tasks: Dict[str, asyncio.Task] = {}
+# 记住每个运行中 Trace 真正使用的 Runner,避免 Example 专属 Runner 的续跑/停止误落到全局 Runner。
+# 映射只覆盖当前进程活跃任务,由两个后台执行入口按对象身份清理。
 _running_runners: Dict[str, Any] = {}
 
 
@@ -182,7 +184,9 @@ async def _cancel_and_wait_for_running_task(trace_id: str) -> None:
 
 
 async def _run_in_background(trace_id: str, messages: List[Dict], config, runner_instance=None):
-    """后台执行 agent,消费 run() 的所有 yield"""
+    """后台执行已知 Trace ID 的 Agent,消费 run() 的所有 yield。
+
+    run_trace() 续跑/回溯时调用,并登记实际 Runner 供 stop_trace() 命中 Recursive 子树。"""
     runner = runner_instance or _get_runner()
     current_task = asyncio.current_task()
     if current_task:
@@ -203,7 +207,9 @@ async def _run_in_background(trace_id: str, messages: List[Dict], config, runner
 async def _run_with_trace_signal(
     messages: List[Dict], config, trace_id_future: asyncio.Future, runner_instance=None
 ):
-    """后台执行 agent,通过 Future 将 trace_id 传回给等待的 endpoint"""
+    """后台新建 Agent,通过 Future 将首个 Trace 对象的 ID 传回 API。
+
+    create_and_run() 调用它,同时登记实际 Runner,使后续停止不会丢失 Example 专属运行环境。"""
     from cyber_agent.trace.models import Trace
 
     runner = runner_instance or _get_runner()
@@ -235,10 +241,9 @@ async def _run_with_trace_signal(
 @router.post("", response_model=RunResponse)
 async def create_and_run(req: CreateRequest):
     """
-    新建 Trace 并开始执行
+    新建 Trace 并开始执行,将 HTTP 参数和 Example 默认值合并为 RunConfig。
 
-    立即返回 trace_id,后台异步执行。
-    通过 WebSocket /api/traces/{trace_id}/watch 监听实时更新。
+    Recursive 的 root_completion_criteria 从此传入 Runner 完成门禁;获取 trace_id 后立即返回,执行继续留在后台。
     """
     import importlib
     from cyber_agent.core.runner import RunConfig
@@ -411,7 +416,9 @@ def _parse_sequence_from_message_id(message_id: str) -> int:
 @router.post("/{trace_id}/run", response_model=RunResponse)
 async def run_trace(trace_id: str, req: TraceRunRequest):
     """
-    运行已有 Trace(统一续跑 + 回溯)
+    运行已有 Trace,统一处理续跑与回溯。
+
+    它优先取活跃 Trace 实际 Runner,再由 AgentRunner 恢复持久化的 Legacy/Recursive 模式和协议状态。
 
     - after_message_id 为 null(或省略):从末尾续跑
     - after_message_id 为 message_id 字符串:从该消息后运行(Runner 自动判断续跑/回溯)
@@ -512,10 +519,9 @@ async def run_trace(trace_id: str, req: TraceRunRequest):
 @router.post("/{trace_id}/stop", response_model=StopResponse)
 async def stop_trace(trace_id: str):
     """
-    停止运行中的 Trace
+    向运行中 Trace 的实际 Runner 发送协作式停止信号。
 
-    设置取消信号,agent loop 在下一个 LLM 调用前检查并退出。
-    Trace 状态置为 "stopped"。
+    Legacy 只停当前 Trace;Recursive 由 AgentRunner.stop() 向当前进程的活跃子孙传播。
     """
     runner = _running_runners.get(trace_id) or _get_runner()
 

+ 21 - 7
cyber_agent/trace/store.py

@@ -1,7 +1,8 @@
 """
-FileSystem Trace Store - 文件系统存储实现
+文件系统 Trace 存储实现。
 
-用于跨进程数据共享,数据持久化到 .trace/ 目录
+Runner、Trace API 和 Sub-Agent 共享 .trace/ 目录中的血缘、消息与 Goal 数据;
+Recursive 资源预算还在根 Trace 目录内维护独立的用量快照。
 
 目录结构:
 .trace/{trace_id}/
@@ -36,7 +37,9 @@ logger = logging.getLogger(__name__)
 
 
 class FileSystemTraceStore:
-    """文件系统 Trace 存储"""
+    """按 Trace 分目录持久化数据的 TraceStore 默认实现。
+
+    它实现 protocols.TraceStore,由 Runner、API、Goal 工具和 ResourceBudgetController 共用。"""
 
     def __init__(self, base_path: str = ".trace"):
         self.base_path = Path(base_path)
@@ -136,7 +139,9 @@ class FileSystemTraceStore:
         parent_trace_id: Optional[str] = None,
         created_by_tool: Optional[str] = None,
     ) -> List[Trace]:
-        """列出 Traces"""
+        """扫描 meta.json 并在截断前完成所有过滤。
+
+        Sub-Agent Spawn Guard 依赖父 Trace 和 created_by_tool 条件,避免 Validator 混入六孩子配额。"""
         traces = []
 
         if not self.base_path.exists():
@@ -188,6 +193,9 @@ class FileSystemTraceStore:
         self,
         root_trace_id: str,
     ) -> Optional[Dict[str, Any]]:
+        """读取根 Trace 的树级用量快照。
+
+        ResourceBudgetController 在进程内锁中调用;文件缺失由上层按 fail closed 处理。"""
         usage_file = self._get_resource_usage_file(root_trace_id)
         if not usage_file.exists():
             return None
@@ -201,7 +209,9 @@ class FileSystemTraceStore:
         root_trace_id: str,
         usage: Dict[str, Any],
     ) -> None:
-        """通过同目录临时文件 + os.replace 原子替换用量快照。"""
+        """通过同目录临时文件 + os.replace 原子替换用量快照。
+
+        ResourceBudgetController 在 Agent/LLM 预留和 Token/成本登记后调用,避免单进程留下半写入文件。"""
         trace_dir = self._get_trace_dir(root_trace_id)
         trace_dir.mkdir(exist_ok=True)
         usage_file = self._get_resource_usage_file(root_trace_id)
@@ -279,7 +289,9 @@ class FileSystemTraceStore:
         cascade_completion: bool = True,
         **updates,
     ) -> None:
-        """更新 Goal 字段"""
+        """更新 Goal 并推送 goal_updated 事件。
+
+        goal_tool、Sub-Agent 和审核工具调用它;Recursive 传入 cascade_completion=False,防止存储层代替父级完成。"""
         tree = await self.get_goal_tree(trace_id)
         if not tree:
             return
@@ -334,7 +346,9 @@ class FileSystemTraceStore:
         completed_goal: Goal
     ) -> List[Dict[str, Any]]:
         """
-        检查级联完成:如果一个 Goal 的所有子 Goal 都完成,则自动完成父 Goal
+        Legacy 的 Goal 级联完成:所有子 Goal 都 completed 或 abandoned 时自动完成父 Goal。
+
+        update_goal() 只在 cascade_completion=True 时调用;Recursive 审核流明确禁用该路径。
 
         Args:
             trace_id: Trace ID

+ 9 - 4
cyber_agent/trace/trace_id.py

@@ -1,7 +1,8 @@
 """
-Trace ID 生成和解析工具
+Trace ID 生成与逐层解析工具。
 
-提供 Trace ID 的生成、解析等功能。
+Sub-Agent 创建 Trace 时生成嵌套 ID;查询和辅助显示只解析最后一层,
+权威父子关系始终以 Trace.parent_trace_id 为准。
 
 Trace ID 格式:
 - 主 Trace: {uuid} (标准 UUID)
@@ -67,7 +68,9 @@ def generate_sub_trace_id(parent_id: str, mode: str) -> str:
 
 def parse_parent_trace_id(trace_id: str) -> Optional[str]:
     """
-    从 trace_id 解析出 parent_trace_id
+    从嵌套 ID 最后一个 @ 之前解析直接父 Trace ID。
+
+    该函数仅供追溯与辅助显示,不取代持久化的 parent_trace_id 安全校验。
 
     Args:
         trace_id: Trace ID
@@ -109,7 +112,9 @@ def is_sub_trace(trace_id: str) -> bool:
 
 def extract_mode(trace_id: str) -> Optional[str]:
     """
-    从 Sub-Trace ID 中提取运行模式
+    从最后一层 Sub-Trace ID 中提取当前子 Trace 的运行模式。
+
+    递归 ID 可含多个 @,因此这里与 parse_parent_trace_id() 一样从末层解析。
 
     Args:
         trace_id: Trace ID

+ 4 - 0
frontend/react-template/src/components/FlowChart/FlowChart.tsx

@@ -108,6 +108,10 @@ interface LayoutEdge {
   isFoldedEdge?: boolean;
 }
 
+/**
+ * 将 Goal 与 Trace 消息排布为交互流程图。
+ * MainContent 传入 agentMode 和 onSubTraceClick;Recursive Goal 由此绘制协议状态及 A1~A6 子 Trace 入口。
+ */
 const FlowChartComponent: ForwardRefRenderFunction<FlowChartRef, FlowChartProps> = (
   {
     goals,

+ 8 - 0
frontend/react-template/src/components/FlowChart/goalStatus.ts

@@ -1,3 +1,7 @@
+/**
+ * Recursive Goal 协议状态的显示映射。
+ * FlowChart 在绘制 Goal 节点时调用,只负责文案和样式,不改变后端状态。
+ */
 import type { Goal } from "../../types/goal";
 
 export type GoalStatusPresentation = {
@@ -28,6 +32,10 @@ const PRESENTATIONS: Partial<Record<Goal["status"], GoalStatusPresentation>> = {
   },
 };
 
+/**
+ * 把后端 Goal 状态转为可选的节点标签和颜色。
+ * FlowChart 调用它突出 waiting_children、pending_review 和 failed,其他状态沿用原显示。
+ */
 export const goalStatusPresentation = (
   status: Goal["status"],
 ): GoalStatusPresentation | undefined => PRESENTATIONS[status];

+ 4 - 1
frontend/react-template/src/components/FlowChart/hooks/useFlowChartData.ts

@@ -42,7 +42,10 @@ const buildSubGoals = (flatGoals: Goal[]): Goal[] => {
   });
 };
 
-// FlowChart 专用数据 Hook:处理实时事件并聚合消息组
+/**
+ * FlowChart 的 Trace 数据入口,合并 REST 快照与 WebSocket 增量事件。
+ * MainContent 调用它获取 Goal、消息组和持久化 agent_mode,用于 Recursive 状态显示与逐层钻取。
+ */
 export const useFlowChartData = (traceId: string | null, refreshTrigger?: number) => {
   const [goals, setGoals] = useState<Goal[]>([]);
   const [messages, setMessages] = useState<Message[]>([]);

+ 12 - 0
frontend/react-template/src/components/FlowChart/subTraceEntries.ts

@@ -1,8 +1,16 @@
+/**
+ * Recursive 流程图的子 Trace 入口规范化。
+ * useFlowChartData 提供 Goal 与模式,FlowChart 用这里的结果绘制 A1~A6 钻取按钮。
+ */
 import type { Goal } from "../../types/goal";
 import type { AgentMode } from "../../types/trace";
 
 export type SubTraceEntry = { id: string; mission?: string };
 
+/**
+ * 兼容历史字符串与当前对象格式,统一为最多六个可点击入口。
+ * visibleSubTraceEntries() 在模式过滤后调用它,不修改 Goal 中的原始 sub_trace_ids。
+ */
 export const normalizeSubTraceEntries = (goal: Goal): SubTraceEntry[] =>
   (goal.sub_trace_ids ?? [])
     .map((item) => {
@@ -13,6 +21,10 @@ export const normalizeSubTraceEntries = (goal: Goal): SubTraceEntry[] =>
     .filter((entry): entry is SubTraceEntry => entry !== null && entry.id.length > 0)
     .slice(0, 6);
 
+/**
+ * 按 Trace 持久化的 agent_mode 决定是否显示子 Trace 快捷入口。
+ * FlowChart 每次绘制 Goal 节点时调用;Legacy 固定返回空列表。
+ */
 export const visibleSubTraceEntries = (
   goal: Goal,
   agentMode: AgentMode,

+ 2 - 0
frontend/react-template/src/components/MainContent/MainContent.tsx

@@ -211,6 +211,8 @@ export const MainContent: FC<MainContentProps> = ({
             agentMode={agentMode}
             onNodeClick={onNodeClick}
             onSubTraceClick={(_parentGoal, entry) => {
+              // FlowChart 的 A1~A6 入口触发该回调,onTraceChange 再切换当前 Trace。
+              // 子 Trace 里使用同一链路,因此可继续进入孙 Trace。
               onTraceChange?.(entry.id, entry.mission || entry.id);
             }}
           />