记录系统设计中的关键决策、权衡和理由。
Skills 包含大量能力描述,如何提供给 Agent?
| 方案 | 优点 | 缺点 |
|---|---|---|
| 预先注入到 system prompt | 实现简单 | 浪费 token,Agent 无法选择需要的 skill |
| 作为工具动态加载 | 按需加载,Agent 自主选择 | 需要实现 skill 工具 |
选择:作为工具动态加载
理由:
参考:
Skills 如何存储?
| 方案 | 优点 | 缺点 |
|---|---|---|
| 文件系统(Markdown) | 易于编辑,支持版本控制,零依赖 | 搜索能力弱 |
| 数据库 | 搜索强大,支持元数据 | 编辑困难,需要额外服务 |
选择:文件系统(Markdown)
理由:
实现:
~/.reson/skills/ # 全局 skills
└── error-handling/SKILL.md
./project/.reson/skills/ # 项目级 skills
└── api-integration/SKILL.md
Experiences 如何存储?
| 方案 | 优点 | 缺点 |
|---|---|---|
| 文件系统 | 简单,零依赖 | 搜索慢,不支持向量检索 |
| 数据库(PostgreSQL + pgvector) | 向量检索,统计分析,高性能 | 需要数据库服务 |
选择:数据库(PostgreSQL + pgvector)
理由:
实现:
CREATE TABLE experiences (
exp_id TEXT PRIMARY KEY,
scope TEXT,
condition TEXT,
rule TEXT,
evidence JSONB,
confidence FLOAT,
usage_count INT,
success_rate FLOAT,
embedding vector(1536), -- 向量检索
created_at TIMESTAMP,
updated_at TIMESTAMP
);
CREATE INDEX ON experiences USING ivfflat (embedding vector_cosine_ops);
是否需要事件总线(EventBus)来通知任务状态变化?
选择:不需要事件系统
理由:
替代方案:
Trace 和 Step 如何存储?
| 方案 | 优点 | 缺点 |
|---|---|---|
| 文件系统(JSON) | 简单,易于调试,可直接查看 | 搜索和分析能力弱 |
| 数据库 | 搜索强大,支持复杂查询 | 初期复杂,调试困难 |
选择:文件系统(JSON)用于 MVP,后期可选数据库
理由(MVP阶段):
后期迁移到数据库的时机:
实现接口保持一致:
class TraceStore(Protocol):
async def save(self, trace: Trace) -> None: ...
async def get(self, trace_id: str) -> Trace: ...
# ...
通过 Protocol 定义,可以无缝切换实现。
工具返回的数据可能很大(如 Browser-Use 的 extract 返回 10K tokens),如何避免占用过多 context?
| 方案 | 优点 | 缺点 |
|---|---|---|
| 单一输出 | 简单 | 大数据会持续占用 context |
| 双层记忆(output + long_term_memory) | 节省 context,避免重复传输 | 稍微复杂 |
选择:双层记忆管理
设计:
@dataclass
class ToolResult:
title: str
output: str # 临时内容(可能很长)
long_term_memory: Optional[str] # 永久记忆(简短摘要)
include_output_only_once: bool # output 是否只给 LLM 看一次
效果:
[User] 提取 amazon.com 的商品价格
[Assistant] 调用 extract_page_data(url="amazon.com")
[Tool]
# Extracted page data
<完整的 10K tokens 数据...>
Summary: Extracted 10000 chars from amazon.com
[User] 现在保存到文件
[Assistant] 调用 write_file(content="...")
[Tool] (此时不再包含 10K tokens,只有摘要)
Summary: Extracted 10000 chars from amazon.com
理由:
参考:Browser-Use 的 ActionResult.extracted_content 和 long_term_memory
某些工具只在特定网站可用(如 Google 搜索技巧),是否需要域名过滤?
选择:支持域名过滤(可选)
设计:
@tool(url_patterns=["*.google.com", "www.google.*"])
async def google_advanced_search(...):
"""仅在 Google 页面可用的工具"""
...
理由:
url_patterns=None,所有页面可用实现:
tools/url_matcher.py)registry.get_schemas_for_url())浏览器自动化需要输入密码、Token,但不想在对话历史中显示明文,如何处理?
选择:占位符替换机制
设计:
# LLM 输出占位符
arguments = {
"password": "<secret>github_password</secret>",
"totp": "<secret>github_2fa_bu_2fa_code</secret>"
}
# 执行前自动替换
sensitive_data = {
"*.github.com": {
"github_password": "secret123",
"github_2fa_bu_2fa_code": "JBSWY3DPEHPK3PXP" # TOTP secret
}
}
理由:
实现:
tools/sensitive.py)参考:Browser-Use 的 _replace_sensitive_data
是否需要记录工具调用统计(调用次数、成功率、执行时间)?
选择:内建统计支持
设计:
class ToolStats:
call_count: int
success_count: int
failure_count: int
total_duration: float
last_called: Optional[float]
理由:
用途:
LLM 生成的工具参数是否允许用户编辑?
选择:支持可选的参数编辑
设计:
@tool(editable_params=["query", "filters"])
async def advanced_search(
query: str,
filters: Optional[Dict] = None,
uid: str = ""
) -> ToolResult:
"""高级搜索(用户可编辑 query 和 filters)"""
...
理由:
editable_params=[])适用场景:
日期: 2026-02-04
自主长程 Agent(非交互式工具)如何有效管理 Context?
选择:基于 OpenCode 方案,增强计划管理和回溯能力
核心设计:
工具:
goal:线性计划管理(add, done, abandon, focus)explore:并行探索-合并(系统管理分支 msg list 和结果汇总)回溯机制:
详细设计:见 docs/context-management.md
日期: 2026-02-04(更新)
选择:独立的 Goal Tree + 线性 Message List
理由:
日期: 2026-02-04(更新)
选择:Goal 完成或放弃时生成 summary
goal(done="summary") - 正常完成goal(abandon="原因") - 放弃(包含失败原因,避免重蹈覆辙)日期: 2026-02-04(更新)
选择:基于 Goal 状态的增量压缩
日期: 2026-02-04
选择:explore 工具,基于 Sub-Trace 机制
设计:
background:LLM 概括的背景(可选,为空则继承全部历史)branches:具体探索方向列表(每个方向创建独立的 Sub-Trace)执行:
Step 工具等核心功能如何让 Agent 知道?
| 方案 | 优点 | 缺点 |
|---|---|---|
| 写在 System Prompt | 始终可见 | 每次消耗 token,内容膨胀 |
| 作为普通 Skill | 按需加载 | 模型不知道存在就不会加载 |
| 分层:Core + 普通 | 核心功能始终可见,其他按需 | 需要区分两类 |
选择:Skill 分层
设计:
agent/skills/core.md,自动注入到 System Promptagent/skills/{name}/,通过 skill 工具加载到对话消息理由:
实现:
build_system_prompt() 时自动读取并拼接skill 工具时返回内容到对话消息日期: 2026-02-03
在 execution trace v2.0 开发中引入了 ErrorCode、StepError 和 feedback 机制,但代码审查发现这些功能完全未被使用。
选择:删除未使用的代码
理由:
影响:
日期: 2026-02-03
execution trace v2.0 设计中引入了多个跨节点关联字段(tool_call_id、paired_action_id、span_id),但实际分析后发现这些字段存在冗余。
字段使用情况:
tool_call_id - Step 对象中未使用,只在 messages 中使用paired_action_id - 完全未使用span_id - 完全未使用关联需求:
保留:
parent_id - 唯一的树结构关联字段删除:
tool_call_id - messages 中已包含,Step 不需要重复paired_action_id - 与 parent_id 冗余span_id - 分布式追踪功能,当前用不到日期: 2026-02-03
execution trace v2.0 引入了 Blob 存储系统用于处理大输出和图片,但实际分析后发现该系统过度设计且与 Agent 现有架构冗余。
Agent 的文件处理方式:
Blob 系统的问题:
删除内容:
agent/execution/blob_store.py 整个文件BlobStore 协议及所有实现extract_images_from_messages() 方法restore_images_in_messages() 方法store_large_output() 方法output_preview、blob_ref保留方案:
data 字段直接存储 messages(包含 base64)YAGNI 原则:
架构更简洁:
符合 Agent 场景:
数据完整性:
日期: 2026-02-08
原有架构存在以下问题:
execution/ 和 goal/ 目录职责边界模糊Trace.context 命名与 ToolContext 等概念混淆subagents/ 目录与 Agent 预设概念重复目录重组:
execution/ + goal/ → trace/(统一执行追踪和计划管理)subagents/ 目录,逻辑整合到 trace/task_tool.pycore/config.py,合并到 runner.py 和 presets.py命名调整:
Trace.context → Trace.meta(TraceMeta 数据类)统一 Agent 模型:
task 工具创建的子 Traceask_human 工具创建的阻塞式 TraceAgent 预设替代 Sub-Agent 配置:
core/presets.py 定义 Agent 类型(default, explore, analyst 等).agent/presets.json 可覆盖工具目录简化:
file/ 和 browser/ 做子目录分类builtin/goal 和 task 工具放 trace/(Agent 内部控制)统一工具,三种模式:
subagent 支持三种模式:explore(并行探索)、delegate(委托执行)、evaluate(结果评估)agent/tools/builtin/subagent.pyExplore 模式的并行执行:
asyncio.gather() 实现真并行read_file, grep_content, glob_files, goal)权限隔离:
subagent 外的所有工具subagent,防止无限递归Sub-Trace 信息存储:
meta.json 中events.jsonl 记录 sub_trace_started 和 sub_trace_completed 事件sub_trace_ids 字段记录关联关系asyncio.gather() 充分利用 I/O 等待时间,提升效率日期: 2026-02-10
Agent(含 sub-agent)有时不创建 goal 就直接执行工具调用,导致 message 的 goal_id 为 null。这造成:
_update_goal_stats 跳过 null goal_id)| 方案 | 优点 | 缺点 |
|---|---|---|
| 预创建 root goal | 保证非 null | 复杂任务多一层无意义嵌套,需要溶解逻辑 |
| 全面接受 null | 无改动 | 丢失统计、可视化、context 锚点 |
| 按需自动创建 | 仅在需要时兜底,不干扰正常规划 | 首轮含 goal() 调用时该轮消息仍为 null(可接受) |
选择:按需自动创建 + prompt 引导
触发条件(三个 AND):
goal_tree.goals 为空(尚无任何目标)tool_calls(正在执行操作)tool_calls 中不包含 goal() 调用(LLM 未自行创建目标)触发时机:LLM 返回后、记录消息前(runner.py agent loop 中)
行为:从 goal_tree.mission 截取前 200 字符作为 root goal description,创建并 focus。
Prompt 配合:core.md 引导 LLM "先明确目标再行动",但不强制。
实现:agent/core/runner.py:AgentRunner.run
is_auto_root 标记、溶解逻辑或 display ID 特殊处理goal() 调用时该轮消息 goal_id 为 null,仅影响一轮,属于规划阶段的过渡消息日期: 2026-02-11
原 AgentRunner.run() 存在以下问题:
task 字符串同时充当 user message、GoalTree mission、trace.taskif trace_id: 分支耦合在一起subagent.py 越权管理 Trace 生命周期(创建 Trace、GoalTree 等本该由 Runner 处理的事务)选择:参数分层 + 统一 run(messages, config) 入口
参数分三层:
List[Dict] 任务消息,支持多模态三种模式通过 RunConfig 区分:
trace_id=Nonetrace_id=已有ID, insert_after=Nonetrace_id=已有ID, insert_after=N回溯机制:Message 新增 status 字段(active/abandoned),插入点之后的消息标记为 abandoned,goals 按规则 abandon/保留。
任务命名:RunConfig.name 可选指定,未指定时由 utility_llm(小模型)自动生成标题。
活跃协作者注入:GoalTree 和 collaborators 信息每 10 轮注入一次(非每轮),减少 context 开销。
Subagent 简化:subagent 工具仍负责创建 Sub-Trace 和 GoalTree(需要自定义元数据和命名规则),然后将 trace_id 传给 RunConfig,由 Runner 接管执行。工具同时维护 trace.context["collaborators"] 列表。
实现:agent/core/runner.py, agent/trace/models.py, agent/tools/builtin/subagent.py
日期: 2026-02-11
模型需要知道当前任务中有哪些可以交互的实体(子 Agent、正在对接的人类),但不应该把所有持久联系人都注入到 context。
选择:按任务关系分类,活跃协作者随 GoalTree 注入
按"与当前任务的关系"(而非 human/agent)分两类:
feishu_get_contact_list)trace.context["collaborators"],周期性注入到 LLM 上下文各工具(subagent、feishu 等)负责维护 collaborators 列表,Runner 只负责读取和注入。
实现:agent/core/runner.py:AgentRunner._build_context_injection