记录系统设计中的关键决策、权衡和理由。
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=[])适用场景:
Step 之间的关系应该是树(单父节点)还是 DAG(多父节点)?
| 方案 | 优点 | 缺点 |
|---|---|---|
| DAG(多父节点) | 能精确表达并行汇合 | 复杂,难以折叠/展开 |
| 树(单父节点) | 简单,天然支持折叠 | 并行汇合需要其他方式表达 |
选择:树结构(单父节点)
理由:
实现:Step.parent_id: Optional[str](单个值,不是列表)
Agent 的计划(TODO)应该如何管理?
| 方案 | 优点 | 缺点 |
|---|---|---|
| 独立 TODO 列表(OpenCode 方式) | 简单,与执行分离 | 计划与执行无结构化关联 |
| 统一到 Step 树 | 计划和执行在同一结构中,可追踪关联 | 稍复杂 |
选择:统一到 Step 树
设计:
Step.status = "planned" 表示计划中的步骤Step.step_type = "goal" 表示计划项/目标step 工具管理计划理由:
参考:OpenCode 的 todowrite/todoread 工具(src/tool/todo.ts)
哪些 Step 需要生成 summary?
选择:仅 evaluation 类型节点需要 summary
理由:
实现:
Step.summary 字段可选step_type == "evaluation" 时填充tool_call/tool_result 不需要 summary,直接从 data 提取关键信息当消息历史过长时,如何压缩?
选择:基于树结构的分层压缩
设计:
goal 类型节点goal + result + evaluation 节点触发时机:
理由:
Step 的元数据(step_type、description、parent_id 等)如何设置?
| 方案 | 优点 | 缺点 |
|---|---|---|
| LLM 显式输出 | 准确 | 需要 LLM 配合特定格式,增加复杂度 |
| 系统自动推断 | 简单,不需要 LLM 额外输出 | 可能不够准确 |
| 混合 | 平衡准确性和简洁性 | 需要明确划分 |
选择:系统自动推断为主,显式工具调用为辅
设计:
step_id、parent_id、tokens、cost、duration_ms、created_atstep_type(基于输出内容)、description(从输出提取)goal、evaluation(summary)step_type 推断规则:
actionevaluationgoalresponsethought理由:
thought、action、result、response 可从输出内容判断Step 工具等核心功能如何让 Agent 知道?
| 方案 | 优点 | 缺点 |
|---|---|---|
| 写在 System Prompt | 始终可见 | 每次消耗 token,内容膨胀 |
| 作为普通 Skill | 按需加载 | 模型不知道存在就不会加载 |
| 分层:Core + 普通 | 核心功能始终可见,其他按需 | 需要区分两类 |
选择:Skill 分层
设计:
agent/skills/core.md,自动注入到 System Promptagent/skills/{name}/,通过 skill 工具加载到对话消息理由:
实现:
build_system_prompt() 时自动读取并拼接skill 工具时返回内容到对话消息这些设计决策的核心原则: