trace-api.md 18 KB

Trace 模块 - 执行记录存储

执行轨迹记录和存储的后端实现

运行边界

Trace API 当前只面向单用户本地开发,不提供账号、租户或 Token 鉴权。

  • API 默认仅监听 127.0.0.1:8000,前端仅监听 127.0.0.1:3000
  • HTTP 只接受 localhost127.0.0.1 Host;CORS 只允许这两个本地前端 Origin。
  • Trace 和日志 WebSocket 同样要求本地前端 Origin。
  • 默认禁用 API 热重载;确需时显式设置 TRACE_API_RELOAD=true

不应将当前 API 通过公网 IP、容器端口映射或反向代理对外暴露。

工具执行确认

当一批 tool calls 中任意一项需要人工确认时,Trace 进入 waiting_confirmation,整批调用都不会提前执行。确认状态持久化在该 Trace 目录的 tool_approvals.json

  • GET /api/traces/{trace_id}/tool-approvals/pending:读取待决策批次。
  • POST /api/traces/{trace_id}/tool-approvals/{batch_id}:一次提交全部待决策 调用的 approve / reject;只能修改工具声明的 editable_params
  • 待确认时普通 /run 返回 409;/stop 取消整批并停止 Trace。
  • 重启时,未决策批次恢复为等待确认;已决策但尚未开始执行的 批次从持久化 assistant tool-call 安全恢复。
  • 重复决策返回 409。如进程在已开始的副作用期间中断,批次进入 execution_unknown,Trace 失败关闭且不会自动重试。

新 Trace 在 context.run_config_snapshot 保存带哈希的完整运行配置。 续跑、回溯、反思和压缩只从该快照恢复行为配置,不接受本次覆盖。 无快照的历史 Recursive Trace 拒绝续跑;Legacy Trace 使用安全默认值推导 一次并标记 legacy_inferred=true


架构概览

职责定位cyber_agent/trace 模块负责所有 Trace/Message 相关功能

cyber_agent/trace/
├── models.py          # Trace/Message 数据模型
├── goal_models.py     # Goal/GoalTree 数据模型
├── protocols.py       # TraceStore 存储接口
├── store.py           # 文件系统存储实现
├── trace_id.py        # Trace ID 生成工具
├── api.py             # RESTful 查询 API
├── run_api.py         # 控制 API(run/stop/reflect)
├── websocket.py       # WebSocket 实时推送
├── goal_tool.py       # goal 工具(计划管理)
└── compaction.py      # Context 压缩

设计原则

  • 高内聚:所有 Trace 相关代码在一个模块
  • 松耦合:核心模型不依赖 FastAPI
  • 可扩展:易于添加 PostgreSQL 等存储实现
  • 统一模型:主 Agent 和 Sub-Agent 使用相同的 Trace 结构

核心模型

Trace - 执行轨迹

一次完整的 LLM 交互(单次调用或 Agent 任务)。每个 Sub-Agent 都是独立的 Trace。

# 主 Trace
main_trace = Trace.create(mode="agent", task="探索代码库")

# Sub-Trace(由统一 agent() 工具创建)
sub_trace = Trace(
    trace_id="2f8d3a1c...@explore-20260204220012-001",
    mode="agent",
    task="探索 JWT 认证方案",
    parent_trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d",
    parent_goal_id="3",
    agent_type="explore",
    status="running"
)

# 字段说明
trace.trace_id        # UUID(主 Trace)或 {parent}@{mode}-{timestamp}-{seq}(Sub-Trace)
trace.mode            # "call" | "agent"
trace.task            # 任务描述
trace.parent_trace_id # 父 Trace ID(Sub-Trace 专用)
trace.parent_goal_id  # 触发的父 Goal ID(Sub-Trace 专用)
trace.agent_type      # Agent 类型:explore, delegate 等
trace.status          # "running" | "completed" | "failed" | "stopped"
trace.total_messages  # Message 总数
trace.total_tokens    # Token 总数
trace.total_cost      # 总成本
trace.current_goal_id # 当前焦点 goal
trace.head_sequence   # 当前主路径头节点 sequence(用于 build_llm_messages)
trace.context         # 扩展上下文;Recursive 会固化模式、血缘、协议和预算快照

Recursive 的关键 context 字段:

字段 说明
agent_mode / agent_mode_revision 创建时固化的 recursive 模式及协议版本
root_trace_id / agent_depth 根 Trace ID 和业务 Agent 深度(根为 0)
created_by_tool agent 表示业务子 Agent,validator 表示独立验收 Trace
root_task_anchor / root_task_anchor_hash Recursive 根任务的不可变目标、完成标准、硬约束及内容哈希
task_protocol TaskBrief、报告、审核、待审核项和下一步动作等权威协议状态
context_access 当前 Trace 本地被授权的不可变协议引用及静态字符用量
resource_budget 根 Trace 固化的树级资源预算快照;动态用量单独保存

旧 Trace 缺少 agent_mode 时按 Legacy 处理。详细语义见 Agent 运行模式Recursive 任务协议

Trace ID 格式

  • 主 Trace:标准 UUID,例如 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d
  • Sub-Trace{parent_uuid}@{mode}-{timestamp}-{seq},例如 2f8d3a1c...@explore-20260204220012-001

实现cyber_agent/trace/models.py:Trace

Message - 执行消息

对应 LLM API 消息,加上元数据。通过 goal_id 关联 GoalTree 中的目标。通过 parent_sequence 形成消息树。

# assistant 消息(模型返回,可能含 text + tool_calls)
assistant_msg = Message.create(
    trace_id=trace.trace_id,
    role="assistant",
    goal_id="3",                    # Goal ID(Trace 内部自增)
    content={"text": "...", "tool_calls": [...]},
    parent_sequence=5,              # 父消息的 sequence
)

# tool 消息
tool_msg = Message.create(
    trace_id=trace.trace_id,
    role="tool",
    goal_id="5",
    tool_call_id="call_abc123",
    content="工具执行结果",
    parent_sequence=6,
)

parent_sequence:指向父消息的 sequence,构成消息树。主路径 = 从 trace.head_sequence 沿 parent chain 回溯到 root。

description 字段(系统自动生成):

  • assistant 消息:优先取 content 中的 text,若无 text 则生成 "tool call: XX, XX"
  • tool 消息:使用 tool name

实现cyber_agent/trace/models.py:Message


存储接口

TraceStore Protocol

class TraceStore(Protocol):
    # Trace 操作
    async def create_trace(self, trace: Trace) -> str: ...
    async def get_trace(self, trace_id: str) -> Optional[Trace]: ...
    async def update_trace(self, trace_id: str, **updates) -> None: ...
    async def list_traces(
        self, ..., parent_trace_id=None, created_by_tool=None
    ) -> List[Trace]: ...

    # Recursive 根 Trace 的树级动态用量
    async def get_resource_usage(self, root_trace_id: str) -> Optional[Dict]: ...
    async def replace_resource_usage(self, root_trace_id: str, usage: Dict) -> None: ...

    # GoalTree 操作(每个 Trace 有独立的 GoalTree)
    async def get_goal_tree(self, trace_id: str) -> Optional[GoalTree]: ...
    async def update_goal_tree(self, trace_id: str, tree: GoalTree) -> None: ...
    async def add_goal(self, trace_id: str, goal: Goal) -> None: ...
    async def update_goal(self, trace_id: str, goal_id: str, **updates) -> None: ...

    # Message 操作
    async def add_message(self, message: Message) -> str: ...
    async def get_message(self, message_id: str) -> Optional[Message]: ...
    async def get_trace_messages(self, trace_id: str) -> List[Message]: ...
    async def get_main_path_messages(self, trace_id: str, head_sequence: int) -> List[Message]: ...
    async def get_messages_by_goal(self, trace_id: str, goal_id: str) -> List[Message]: ...
    async def update_message(self, message_id: str, **updates) -> None: ...

    # 事件流(WebSocket 断线续传)
    async def get_events(self, trace_id: str, since_event_id: int) -> List[Dict]: ...
    async def append_event(self, trace_id: str, event_type: str, payload: Dict) -> int: ...

实现cyber_agent/trace/protocols.py

list_traces() 先应用 parent_trace_idcreated_by_tool 过滤,再应用 limit。因此可以准确查询某 Trace 由 agent 创建的直接业务孩子,Validator Trace 不会混入六孩子计数。

FileSystemTraceStore

from cyber_agent.trace import FileSystemTraceStore

store = FileSystemTraceStore(base_path=".trace")

目录结构

.trace/
├── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d/           # 主 Trace
│   ├── meta.json                                   # Trace 元数据
│   ├── goal.json                                   # GoalTree(扁平 JSON)
│   ├── resource_usage.json                         # Recursive 根 Trace 的树级资源用量
│   ├── messages/                                   # Messages
│   │   ├── {message_id}.json
│   │   └── ...
│   └── events.jsonl                                # 事件流
│
├── 2f8d3a1c...@explore-20260204220012-001/        # Sub-Trace A
│   ├── meta.json                                   # parent_trace_id 指向主 Trace
│   ├── goal.json                                   # 独立的 GoalTree
│   ├── messages/
│   └── events.jsonl
│
└── 2f8d3a1c...@explore-20260204220012-002/        # Sub-Trace B
    └── ...

关键变化(相比旧设计):

  • ❌ 不再有 branches/ 子目录
  • ✅ 每个 Sub-Trace 是顶层独立目录
  • ✅ Sub-Trace 有完整的 Trace 结构(meta + goal + messages + events)

实现cyber_agent/trace/store.py


REST API 端点

查询端点

1. 列出 Traces

GET /api/traces?mode=agent&status=running&limit=20

返回所有 Traces(包括主 Trace 和 Sub-Traces)。

2. 获取 Trace + GoalTree + Sub-Traces

GET /api/traces/{trace_id}

返回:

  • trace:Trace 元数据(包含 context
  • GoalTree(该 Trace 的完整 Goal 树)
  • sub_traces:所有 parent_trace_id == trace_id 的直接子 Trace 元数据,包括业务子 Agent 和 Validator Trace

每个有效验收 Scope 都会生成一个 Validator Trace,使用 agent_type="validator"context.created_by_tool="validator"context.validation_scope。它不增加业务 agent_depth,也不计入每父六个业务孩子。如果只需业务子 Agent,存储层查询应同时使用 parent_trace_idcreated_by_tool="agent"

3. 获取 Messages

GET /api/traces/{trace_id}/messages?mode=main_path&head=15&goal_id=3

返回指定 Trace 的 Messages。参数:

  • mode: main_path(默认)| all — 返回主路径消息或全部消息
  • head: 可选 sequence 值 — 指定主路径的 head(默认用 trace.head_sequence,仅 mode=main_path 有效)
  • goal_id: 可选,按 Goal 过滤

实现cyber_agent/trace/api.py

控制端点

需在 api_server.py 中配置 Runner。执行在后台异步进行,通过 WebSocket 监听进度。

4. 新建 Trace 并执行

POST /api/traces
Content-Type: application/json

{
  "messages": [
    {"role": "system", "content": "自定义 system prompt(可选,不传则从 skills 自动构建)"},
    {"role": "user", "content": "分析项目架构"}
  ],
  "model": "gpt-4o",
  "temperature": 0.3,
  "max_iterations": 200,
  "tools": null,
  "name": "任务名称",
  "uid": "user_id",
  "project_name": null,
  "root_task_anchor": {
    "objective": "分析项目架构",
    "completion_criteria": ["列出模块与核心调用链"],
    "constraints": ["结论必须来自实际代码"]
  }
}

root_task_anchor 在 Recursive 模式下必填,objective 和至少一项 completion_criteria 不能为空;Legacy 模式忽略该字段。旧字段 root_completion_criteria 会明确报错,不静默兼容。

5. 运行(统一续跑 + 回溯)

POST /api/traces/{trace_id}/run
Content-Type: application/json

{
  "messages": [{"role": "user", "content": "..."}],
  "after_message_id": null
}
  • after_message_id: null(或省略)→ 从末尾续跑
  • after_message_id: "<message_id>"(主路径上且 < head)→ 回溯到该消息后运行
  • messages: [] + after_message_id: "<message_id>" → 重新生成

Runner 根据解析出的 sequence 与 head_sequence 的关系自动判断续跑/回溯行为。

6. 停止运行中的 Trace

POST /api/traces/{trace_id}/stop

设置取消信号,agent loop 在下一个检查点退出,Trace 状态置为 stopped。 Legacy 只停止请求的 Trace;Recursive 会在当前进程内向活跃的直接孩子和后代传播停止信号。

Recursive 为协作式停止:不强制中断已发出的 LLM 请求,但请求返回后不再执行 Tool Call 或开始下一轮。尚未启动的孩子直接写为 stopped;已停止子任务以 outcome=failed 的 TaskReport 返回父级。当前不支持跨 Worker 取消。

7. 列出正在运行的 Trace

GET /api/traces/running

8. 反思(提取经验)

POST /api/traces/{trace_id}/reflect
Content-Type: application/json

{
  "focus": "可选,反思重点"
}

force_side_branch=["reflection"] 启动反思侧分支,使用当前 Agent 循环进行多轮思考,必要时可调用知识工具。 默认提取结果以 extraction_pending 写入 cognition_log,再通过提取审核 CLI/API 审核和提交;反思侧分支不改变主路径的 head_sequence

经验端点

9. 读取经验文件

GET /api/experiences

返回 ./.cache/experiences.md 的文件内容。

实现cyber_agent/trace/run_api.py


WebSocket 事件

连接

ws://43.106.118.91:8000/api/traces/{trace_id}/watch?since_event_id=0

事件类型

事件 触发时机 payload
connected WebSocket 连接成功 trace_id, current_event_id, trace_status, is_running, goal_tree, sub_traces
goal_added 新增 Goal goal 完整数据(含 stats, parent_id, type)
goal_updated Goal 状态变化 goal_id, patch(历史补发为 updates), affected_goals(Legacy 级联完成时可包含父节点)
message_added 新 Message message 数据(含 goal_id),affected_goals
sub_trace_started Sub-Trace 开始执行 trace_id, parent_goal_id, agent_type, task
sub_trace_completed Sub-Trace 完成 trace_id, status, summary, stats
rewind 回溯执行 after_sequence, head_sequence, goal_tree_snapshot
trace_completed 执行完成 trace_id, total_messages
trace_status_changed 停止或续跑导致 Trace 状态变化 trace_id, status

Stats 更新逻辑

每次添加 Message 时,后端执行:

  1. 更新对应 Goal 的 self_stats
  2. 沿 parent_id 链向上更新所有祖先的 cumulative_stats
  3. message_added 事件的 affected_goals 中推送所有受影响的 Goal 及其最新 stats

级联完成(Cascade Completion)

该自动行为只用于 Legacy:当所有子 Goals 都完成时,自动完成父 Goal:

  1. 检测子 Goals 全部 status == "completed"
  2. 自动设置父 Goal 的 status = "completed"
  3. goal_updated 事件的 affected_goals 中包含级联完成的父节点

Recursive 会显式关闭级联完成和自动切换下一个 sibling:子 Agent 返回后,父 Goal 从 waiting_children 进入 pending_review,由父 Agent 审核后再决定重新规划、修订、完成或失败。

实现cyber_agent/trace/goal_models.pycyber_agent/trace/goal_tool.pycyber_agent/trace/store.py


Sub-Trace 工具

agent() 工具

本地 Sub-Agent 统一由 agent() 工具创建。delegateexplore 只是内部运行模式,不是可单独导入或调用的工具:

# Legacy:单任务进入 delegate 模式
agent(task="实现用户登录功能")

# Legacy:多任务进入 explore 模式
agent(task=["JWT 方案", "Session 方案"])

# Recursive:使用结构化 TaskBrief,单个对象或对象列表
agent(task_brief={
    "objective": "实现用户登录功能",
    "reason": "将身份认证从父任务中独立验证",
    "completion_criteria": ["登录成功和失败路径均通过验证"],
    "expected_outputs": ["实现代码", "验证结果"]
})
  • 单任务使用内部 delegate 模式,多任务使用内部 explore 模式并汇总结果。
  • Legacy 的多任务整批并行;Recursive 默认串行,显式配置并行时最多同时执行 2 个孩子。
  • Trace/Goal 上下文由框架注入;Recursive 子 Agent 的工具权限不会超过父 Agent 的实际权限。

使用场景

Agent 执行时记录

from cyber_agent import AgentRunner
from cyber_agent.trace import FileSystemTraceStore

store = FileSystemTraceStore(base_path=".trace")
runner = AgentRunner(trace_store=store, llm_call=my_llm_fn)

async for event in runner.run([
    {"role": "user", "content": "探索代码库"},
]):
    print(event)  # Trace 或 Message

查询 Sub-Traces

# 获取主 Trace 的直接 Sub-Traces
sub_traces = await store.list_traces(
    parent_trace_id=main_trace_id,
    created_by_tool="agent",
    limit=1000,
)

Recursive 根 Trace 的 resource_usage.json 记录 Agent 数、LLM 调用、Token、成本、起始时间及超限原因。它由 TraceStore 供框架内部读写,不是 Trace 详情响应的独立顶层字段。停止、回溯和续跑不退还历史用量。


相关文档