guantao 14 часов назад
Родитель
Сommit
d36eff2209
100 измененных файлов с 7287 добавлено и 5031 удалено
  1. 0 0
      .browser_use_files/browseruse_agent_data/todo.md
  2. 6 1
      .gitignore
  3. 233 62
      README.md
  4. 168 0
      agent/README.md
  5. 2 2
      agent/__init__.py
  6. 11 0
      agent/cli/__init__.py
  7. 290 0
      agent/cli/interactive.py
  8. 2 3
      agent/core/__init__.py
  9. 0 546
      agent/core/prompts.py
  10. 58 0
      agent/core/prompts/__init__.py
  11. 50 0
      agent/core/prompts/compression.py
  12. 98 0
      agent/core/prompts/knowledge.py
  13. 39 0
      agent/core/prompts/runner.py
  14. 285 485
      agent/core/runner.py
  15. 1400 0
      agent/docs/architecture.md
  16. 0 0
      agent/docs/decisions.md
  17. 203 0
      agent/docs/knowledge.md
  18. 0 0
      agent/docs/multimodal.md
  19. 331 0
      agent/docs/scope-design.md
  20. 0 0
      agent/docs/skills.md
  21. 136 35
      agent/docs/tools.md
  22. 0 0
      agent/docs/trace-api.md
  23. 13 13
      agent/llm/openrouter.py
  24. 3 3
      agent/memory/skills/core.md
  25. 25 21
      agent/memory/skills/research.md
  26. 7 5
      agent/tools/builtin/__init__.py
  27. 1 1
      agent/tools/builtin/bash.py
  28. 1 5
      agent/tools/builtin/browser/baseClass.py
  29. 0 487
      agent/tools/builtin/experience.py
  30. 4 0
      agent/tools/builtin/feishu/chat.py
  31. 1 1
      agent/tools/builtin/file/edit.py
  32. 1 1
      agent/tools/builtin/file/glob.py
  33. 1 1
      agent/tools/builtin/file/grep.py
  34. 1 1
      agent/tools/builtin/file/read.py
  35. 1 1
      agent/tools/builtin/file/write.py
  36. 1 1
      agent/tools/builtin/glob_tool.py
  37. 334 1055
      agent/tools/builtin/knowledge.py
  38. 3 0
      agent/tools/builtin/sandbox.py
  39. 58 15
      agent/tools/builtin/subagent.py
  40. 69 11
      agent/tools/registry.py
  41. 15 5
      agent/tools/schema.py
  42. 2 7
      agent/trace/compaction.py
  43. 86 10
      agent/trace/goal_tool.py
  44. 13 4
      agent/trace/models.py
  45. 15 12
      agent/trace/websocket.py
  46. 7 0
      agent/utils/__init__.py
  47. 40 0
      agent/utils/logging.py
  48. 0 1
      api_server.py
  49. 75 969
      docs/README.md
  50. 651 0
      docs/a2a-im.md
  51. 0 78
      docs/ref/Claude Code/agent-prompt-agent-creation-architect.md
  52. 0 12
      docs/ref/Claude Code/agent-prompt-bash-command-description-writer.md
  53. 0 9
      docs/ref/Claude Code/system-prompt-doing-tasks.md
  54. 0 6
      docs/ref/Claude Code/system-prompt-tool-usage-policy.md
  55. 0 16
      docs/ref/Claude Code/system-prompt-tool-use-summary-generation.md
  56. 0 0
      docs/ref/Claude Code/tool-description-bash.md
  57. 0 659
      docs/ref/context-comparison.md
  58. 0 98
      docs/ref/create.md
  59. 0 357
      docs/ref/deconstruct_old.md
  60. 0 31
      docs/ref/skills.md
  61. 71 0
      docs/research/README.md
  62. 733 0
      docs/research/a2a-continuous-dialogue.md
  63. 640 0
      docs/research/a2a-cross-device.md
  64. 504 0
      docs/research/a2a-mamp-protocol.md
  65. 114 0
      docs/research/a2a-protocols.md
  66. 484 0
      docs/research/a2a-trace-storage.md
  67. 0 0
      examples/archive/analyze_story/README.md
  68. 0 0
      examples/archive/analyze_story/analysis_results.json
  69. 0 0
      examples/archive/analyze_story/analyze_samples.py
  70. 0 0
      examples/archive/analyze_story/generate_report.py
  71. 0 0
      examples/archive/analyze_story/input/中国合伙人.pdf
  72. 0 0
      examples/archive/analyze_story/input/大奉打更人.txt
  73. 0 0
      examples/archive/analyze_story/input/搜神记.txt
  74. 0 0
      examples/archive/analyze_story/input/无双.docx
  75. 0 0
      examples/archive/analyze_story/input/雪中悍刀行.txt
  76. 0 0
      examples/archive/analyze_story/input/魔道祖师.txt
  77. 0 0
      examples/archive/analyze_story/knowledge/01_Scene_Sequel_Structure.md
  78. 0 0
      examples/archive/analyze_story/knowledge/01_save_the_cat_beat_sheet.md
  79. 0 0
      examples/archive/analyze_story/knowledge/01_scene_sequel_theory.md
  80. 0 0
      examples/archive/analyze_story/knowledge/01_叙事理论综述.md
  81. 0 0
      examples/archive/analyze_story/knowledge/02_MICE_Quotient.md
  82. 0 0
      examples/archive/analyze_story/knowledge/02_MICE_quotient_theory.md
  83. 0 0
      examples/archive/analyze_story/knowledge/03_Save_the_Cat_Beats.md
  84. 0 0
      examples/archive/analyze_story/knowledge/03_web_novel_techniques.md
  85. 0 0
      examples/archive/analyze_story/knowledge/04_Web_Novel_Theory.md
  86. 0 0
      examples/archive/analyze_story/knowledge/04_save_the_cat_beats.md
  87. 0 0
      examples/archive/analyze_story/knowledge/05_AI_narrative_generation.md
  88. 0 0
      examples/archive/analyze_story/knowledge/05_Integrated_Methodology.md
  89. 0 0
      examples/archive/analyze_story/knowledge/07_Methodology_Analysis.md
  90. 0 0
      examples/archive/analyze_story/knowledge/1_scene_sequel_theory.md
  91. 0 0
      examples/archive/analyze_story/knowledge/samples_overview.md
  92. 0 0
      examples/archive/analyze_story/methodology/README.md
  93. 0 0
      examples/archive/analyze_story/methodology/v2_improved_methodology.md
  94. 0 0
      examples/archive/analyze_story/read_all_files.py
  95. 0 0
      examples/archive/analyze_story/read_samples.py
  96. 0 0
      examples/archive/analyze_story/read_txt_files.py
  97. 1 1
      examples/archive/analyze_story/run.py
  98. 0 0
      examples/archive/analyze_story/runs/搜神记/analysis/w0.error.txt
  99. 0 0
      examples/archive/analyze_story/runs/搜神记/analysis/w0.json
  100. 0 0
      examples/archive/analyze_story/runs/搜神记/sft_raw/w0_demo/stats.json

+ 0 - 0
.browser_use_files/browseruse_agent_data/todo.md


+ 6 - 1
.gitignore

@@ -64,4 +64,9 @@ output
 examples/**/output*/
 examples/**/output*/
 
 
 frontend/htmlTemplate/mock_data
 frontend/htmlTemplate/mock_data
-frontend/react-template/yarn.lock
+frontend/react-template/yarn.lock
+
+# data
+knowhub/knowhub.db
+knowhub/knowhub.db-shm
+knowhub/knowhub.db-wal

+ 233 - 62
README.md

@@ -125,78 +125,89 @@ runner = AgentRunner(
 
 
 内置 skills(`agent/memory/skills/`)始终自动加载,`skills_dir` 的内容额外追加。
 内置 skills(`agent/memory/skills/`)始终自动加载,`skills_dir` 的内容额外追加。
 
 
-## 经验系统(Experience System
+## 知识管理系统(Knowledge Management
 
 
-经验系统通过**提取、注入、反馈、更新**四个环节,让 Agent 从历史执行中学习并持续改进
+知识管理系统通过**提取、存储、注入**三个环节,让 Agent 积累和复用结构化知识
 
 
 ### 核心流程
 ### 核心流程
 
 
 **1. 提取(Extract)**
 **1. 提取(Extract)**
-- **触发时机**:Level 2 压缩时自动触发
-- **提取方式**:在压缩历史消息前,先调用 LLM 对当前执行过程进行反思(reflect)
-- **输出格式**:ACE 规范经验条目
-  ```
-  当 [条件/Context] 时,应该 [动作/Action](原因:[逻辑/Reason])
-  ```
-- **存储位置**:追加到 `experiences.md` 文件(默认 `./.cache/experiences.md`)
-
-**2. 注入(Inject)**
-- **触发时机**:切换 Goal 时自动触发
-- **检索策略**:两阶段检索
-  - Stage 1: 语义路由(LLM 挑选 2*k 个相关经验)
-  - Stage 2: 质量精排(根据 metrics 筛选最终 k 个)
-- **注入方式**:将检索到的经验注入到主 Agent 的上下文中
-
-**3. 反馈(Feedback)**
-- **触发时机**:压缩时分析历史消息中经验的使用效果
-- **评价维度**:
-  - `helpful`: 经验有效,帮助完成任务
-  - `harmful`: 经验误导,导致错误
-  - `mixed`: 部分有效,需要改进
-- **反馈来源**:LLM 分析执行过程中经验的实际效果
-
-**4. 更新(Update)**
-- **Metrics 更新**:根据反馈调整 `helpful` 和 `harmful` 计数
-- **内容进化**:
-  - `helpful` + 有改进建议 → 触发经验重写(evolve)
-  - `harmful` 累积 → 降低检索权重或标记为有害
-- **质量过滤**:检索时自动过滤 `quality_score < -2` 的有害经验
-
-### 经验文件格式
+- **触发时机**:
+  - 压缩时提取:消息量超阈值触发压缩时,在 Level 1 过滤前用完整 history 反思
+  - 完成时提取:Agent 运行完成后(不代表任务完成,可能中途退出等待人工评估)
+- **提取方式**:调用 LLM 对执行过程进行反思,提取可复用的知识
+- **自定义 Prompt**:可通过配置自定义反思 prompt,空则使用默认(见 `agent/core/prompts/knowledge.py`)
+
+**2. 存储(Store)**
+- **存储位置**:KnowHub 服务(默认 `http://localhost:8765`)
+- **知识结构**:
+  - `title`: 知识标题
+  - `content`: 知识内容
+  - `type`: 知识类型(strategy/tool/pattern/pitfall 等)
+  - `tags`: 标签(键值对,用于分类和检索)
+  - `scopes`: 作用域(如 `org:cybertogether`)
+  - `owner`: 所有者(默认从 git config user.email 获取)
+  - `resource_ids`: 关联资源 ID 列表(代码片段、凭证、cookies 等)
+- **资源管理**:
+  - 知识可关联多个资源(通过 `resource_ids` 字段)
+  - 资源包含 `body`(公开内容)和 `secure_body`(加密内容)
+  - 支持代码片段、API 凭证、cookies 等多种资源类型
+
+**3. 注入(Inject)**
+- **触发时机**:Agent 切换当前工作的 Goal 时自动触发
+- **检索策略**:基于 Goal 描述和上下文,从知识库检索相关知识
+- **注入方式**:将检索到的知识注入到 Agent 的上下文中
 
 
-```markdown
----
-id: ex_02271430_a3f2
-trace_id: 6822d4e0-8aeb-449f-962e-c431c409a5a0
-tags: {intent: [解构, 图片分析], state: [多图]}
-metrics: {helpful: 3, harmful: 0}
-created_at: 2026-02-27 14:30:15
-updated_at: 2026-02-27 15:20:42
----
-当需要分析多张图片时,应该先并行读取所有图片再统一分析(原因:避免重复调用 LLM,节省 token 和时间)。
-```
+### 配置
 
 
-### 经验库瘦身
+知识管理配置通过 `RunConfig.knowledge` 传递:
 
 
-`experience.py` 中提供 `slim_experiences()` 函数,可调用顶级 LLM 合并语义相似的经验,减少冗余。
+```python
+from agent.core.runner import KnowledgeConfig, RunConfig
 
 
-**功能**:
-- 识别并合并语义高度相似的经验
-- 保留 helpful 最高的 ID
-- 合并 metrics(helpful/harmful 取各条之和)
-- 保持 ACE 规范格式
+run_config = RunConfig(
+    model="claude-sonnet-4.5",
+    temperature=0.3,
+    max_iterations=1000,
+
+    knowledge=KnowledgeConfig(
+        # 压缩时提取(消息量超阈值触发压缩时,用完整 history 反思)
+        enable_extraction=True,
+        reflect_prompt="",  # 空则使用默认,见 agent/core/prompts/knowledge.py:REFLECT_PROMPT
+
+        # agent运行完成后提取
+        enable_completion_extraction=True,
+        completion_reflect_prompt="",  # 空则使用默认
+
+        # 知识注入(agent切换当前工作的goal时,自动注入相关知识)
+        enable_injection=True,
+
+        # 默认字段(保存/搜索时自动注入)
+        owner="",  # 空则从 git config user.email 获取(隐藏参数,LLM 不可见)
+        default_tags={"project": "my_project"},  # 与 LLM 传递的 tags 合并
+        default_scopes=["org:cybertogether"],  # 与 LLM 传递的 scopes 合并
+        default_search_types=["strategy", "tool"],
+        default_search_owner=""  # 空则不过滤
+    )
+)
+```
 
 
-**状态**:已实现但暂未自动调用,可在 `analyze_story/run.py` 的交互菜单中手动触发(选项 7)。
+**参数注入规则**(通过框架 `inject_params` 机制实现,详见 `agent/docs/tools.md`):
+- `owner`:隐藏参数,LLM 不可见,框架自动注入(`mode: default`)
+- `tags`:LLM 可追加新 key,框架默认 key 不可被覆盖(`mode: merge`)
+- `scopes`:LLM 可追加,与框架默认值合并去重(`mode: merge`)
 
 
-### 配置
+### 知识工具
 
 
-```python
-runner = AgentRunner(
-    llm_call=...,
-    trace_store=...,
-    experiences_path="./.cache/experiences.md",  # 自定义经验文件路径
-)
-```
+框架提供以下内置工具用于知识管理:
+
+- `knowledge_save`: 保存知识到知识库
+- `knowledge_search`: 搜索知识库
+- `knowledge_get`: 获取指定知识详情
+- `resource_save`: 保存资源(代码、凭证等)
+- `resource_get`: 获取资源内容
+
+这些工具会自动注入配置的默认字段(owner, tags, scopes 等)。
 
 
 ## AgentRunner 参数
 ## AgentRunner 参数
 
 
@@ -206,9 +217,8 @@ AgentRunner(
     trace_store=None,        # Trace 持久化(推荐 FileSystemTraceStore)
     trace_store=None,        # Trace 持久化(推荐 FileSystemTraceStore)
     tool_registry=None,      # 工具注册表(默认:全局 registry)
     tool_registry=None,      # 工具注册表(默认:全局 registry)
     skills_dir=None,         # 自定义 skills 目录
     skills_dir=None,         # 自定义 skills 目录
-    experiences_path="./.cache/experiences.md",  # 经验文件路径
-    memory_store=None,       # 记忆存储
     utility_llm_call=None,   # 轻量 LLM(生成任务标题等)
     utility_llm_call=None,   # 轻量 LLM(生成任务标题等)
+    debug=False,             # 调试模式
 )
 )
 ```
 ```
 
 
@@ -224,6 +234,13 @@ RunConfig(
     agent_type="default",     # 预设类型:default / explore / analyst
     agent_type="default",     # 预设类型:default / explore / analyst
     trace_id=None,            # 续跑/回溯时传入已有 trace ID
     trace_id=None,            # 续跑/回溯时传入已有 trace ID
     after_sequence=None,      # 从哪条消息后续跑(message sequence)
     after_sequence=None,      # 从哪条消息后续跑(message sequence)
+    knowledge=KnowledgeConfig(),  # 知识管理配置
+)
+```
+    system_prompt=None,       # None=从 skills 自动构建
+    agent_type="default",     # 预设类型:default / explore / analyst
+    trace_id=None,            # 续跑/回溯时传入已有 trace ID
+    after_sequence=None,      # 从哪条消息后续跑(message sequence)
 )
 )
 ```
 ```
 
 
@@ -286,3 +303,157 @@ agent/
 ```
 ```
 
 
 详细架构文档:[docs/README.md](./docs/README.md)
 详细架构文档:[docs/README.md](./docs/README.md)
+
+
+
+## 交互式 CLI(Interactive CLI)
+
+框架提供交互式控制器,支持实时监控、手动干预和经验总结。
+
+### 使用方式
+
+```python
+from agent.cli import InteractiveController
+
+# 创建交互控制器
+interactive = InteractiveController(
+    runner=runner,
+    store=store,
+    enable_stdin_check=True  # 启用标准输入检查
+)
+
+# 在执行循环中检查用户输入
+async for item in runner.run(messages=messages, config=config):
+    cmd = interactive.check_stdin()
+    if cmd == 'pause':
+        await runner.stop(trace_id)
+        menu_result = await interactive.show_menu(trace_id, current_sequence)
+        # 处理菜单结果...
+    elif cmd == 'quit':
+        await runner.stop(trace_id)
+        break
+```
+
+### 交互控制
+
+在执行过程中,可以通过命令行实时控制:
+
+| 按键 | 动作 | 说明 |
+| --- | --- | --- |
+| `p` / `pause` | **暂停执行** | 立即挂起 Agent 循环,进入交互菜单 |
+| `q` / `quit` | **停止执行** | 安全停止并保存当前的执行状态 |
+
+### 交互菜单功能
+
+进入暂停模式后,系统提供以下操作:
+
+1. **插入干预消息**:直接向 Agent 下达新指令
+2. **触发经验总结 (Reflect)**:强制 Agent 对当前过程进行反思
+3. **查看 GoalTree**:可视化当前任务的拆解结构和完成进度
+4. **上下文压缩 (Compact)**:手动精简对话历史
+
+### 项目配置示例
+
+完整的项目配置示例见 `examples/research/config.py`:
+
+```python
+from agent.core.runner import KnowledgeConfig, RunConfig
+from agent.utils import setup_logging
+
+# Agent 运行配置
+RUN_CONFIG = RunConfig(
+    model="claude-sonnet-4.5",
+    temperature=0.3,
+    max_iterations=1000,
+    name="Research Agent",
+
+    knowledge=KnowledgeConfig(
+        enable_extraction=True,
+        enable_completion_extraction=True,
+        enable_injection=True,
+        owner="",  # 空则从 git config 获取
+        default_tags={"project": "research"},
+        default_scopes=["org:cybertogether"],
+        default_search_types=["strategy", "tool"],
+    )
+)
+
+# 基础设施配置
+SKILLS_DIR = "./skills"
+TRACE_STORE_PATH = ".trace"
+DEBUG = True
+LOG_LEVEL = "INFO"
+LOG_FILE = None  # 可设置为文件路径
+
+# 在 run.py 中使用
+setup_logging(level=LOG_LEVEL, file=LOG_FILE)
+
+runner = AgentRunner(
+    trace_store=FileSystemTraceStore(base_path=TRACE_STORE_PATH),
+    llm_call=create_openrouter_llm_call(model=f"anthropic/{RUN_CONFIG.model}"),
+    skills_dir=SKILLS_DIR,
+    debug=DEBUG
+)
+
+async for item in runner.run(messages=messages, config=RUN_CONFIG):
+    # 处理执行结果
+    pass
+```
+
+**配置说明**:
+- 直接使用框架的 `RunConfig` 和 `KnowledgeConfig`,不需要自定义配置类
+- 基础设施配置(skills_dir, trace_store_path 等)用简单变量定义
+- 使用 `agent.utils.setup_logging()` 配置日志
+
+## 任务可视化与调试
+
+框架在运行期间会生成唯一的 `trace_id`。
+
+* **本地日志**:所有的执行细节、工具调用和 Goal 状态均持久化在 `.trace/` 目录下。
+* **Web 可视化**:
+1. 启动服务器:`python api_server.py`
+2. 启动前端:
+```
+  cd frontend/react-template
+  yarn
+  yarn dev
+```
+2. 访问控制台:`http://localhost:3000`
+3. 在前端界面中切换任务,即直观追踪 Agent 的思考链路。
+
+### 提示:目前前端可视化只供观看本地运行过的trace结果,新任务运行等功能正在开发中,运行可在命令行中执行
+### 绿色节点为整体的goal(目标),蓝色节点为子goal(目标),灰色节点为基础信息节点。点击蓝色边/绿色边会折叠节点,点击节点会在右侧显示详情。
+
+---
+
+## 示例项目结构
+
+可以参考其他文件夹中的结构:
+
+```text
+examples/[your_example]/
+├── input/             # (可选)输入数据
+├── output_1/          # (可选)输出目录
+├── skills/            # (可选)领域专属 Skill (.md)
+├── tool/              # (可选)自定义工具
+├── presets.json       # (可选)预定义的子 Agent 配置
+├── config.py          # (推荐)项目配置
+├── [task].prompt      # (必须)任务 System Prompt 和 User Prompt
+└── run.py             # (必须)交互式运行入口
+```
+
+---
+
+## 环境兼容性
+
+针对 Clash Verge / TUN 模式等网络环境,本项目已内置代理自动避让逻辑:
+
+* **代理优化**:通过 `no_proxy` 配置防止 `httpx` 错误引导流量。
+* **Browser 模式**:支持 `cloud` (远程) 和 `local` (本地) 模式切换。
+
+## 运行结果存储
+
+运行过程中,会自动存储以下内容:
+
+* **运行轨迹**:根目录下 `.trace/` 文件夹下的实际运行路径结果
+* **知识库**:KnowHub 服务中保存的知识条目(通过 API 访问)

+ 168 - 0
agent/README.md

@@ -0,0 +1,168 @@
+# Agent Core
+
+**Agent 核心框架**:提供单个 Agent 的执行能力
+
+## 文档维护规范
+
+0. **先改文档,再动代码** - 新功能或重大修改需先完成文档更新、并完成审阅后,再进行代码实现;除非改动较小、不被文档涵盖
+1. **文档分层,链接代码** - 重要或复杂设计可以另有详细文档;关键实现需标注代码文件路径;格式:`module/file.py:function_name`
+2. **简洁快照,日志分离** - 只记录最重要的、与代码准确对应的或者明确的已完成的设计的信息,避免推测、建议,或大量代码;决策依据或修改日志若有必要,可在`docs/decisions.md`另行记录
+
+---
+
+## 概述
+
+Agent Core 是一个完整的 Agent 执行框架,提供:
+- Trace、Message、Goal 管理
+- 工具系统(文件、命令、网络、浏览器)
+- LLM 集成(Gemini、OpenRouter、Yescode)
+- Skills 和 Memory(跨会话知识)
+- 子 Agent 机制
+
+**独立性**:Agent Core 不依赖任何其他模块,可以独立运行。
+
+---
+
+## 模块结构
+
+```
+agent/
+├── core/                  # 核心引擎
+│   ├── runner.py          # AgentRunner + 运行时配置
+│   └── presets.py         # Agent 预设(explore、analyst 等)
+│
+├── trace/                 # 执行追踪(含计划管理)
+│   ├── models.py          # Trace, Message
+│   ├── goal_models.py     # Goal, GoalTree, GoalStats
+│   ├── protocols.py       # TraceStore 接口
+│   ├── store.py           # FileSystemTraceStore 实现
+│   ├── goal_tool.py       # goal 工具(计划管理)
+│   ├── compaction.py      # Context 压缩
+│   ├── api.py             # REST API
+│   └── websocket.py       # WebSocket API
+│
+├── tools/                 # 外部交互工具
+│   ├── registry.py        # 工具注册表
+│   ├── schema.py          # Schema 生成器
+│   ├── models.py          # ToolResult, ToolContext
+│   └── builtin/
+│       ├── file/          # 文件操作
+│       ├── browser/       # 浏览器自动化
+│       ├── bash.py        # 命令执行
+│       ├── subagent.py    # 子 Agent 创建
+│       └── a2a_im.py      # A2A IM 工具(桥接到 Gateway)
+│
+├── memory/                # 跨会话记忆
+│   ├── models.py          # Experience, Skill
+│   ├── stores.py          # 存储实现
+│   ├── skill_loader.py    # Skill 加载器
+│   └── skills/            # 内置 Skills
+│
+└── llm/                   # LLM 集成
+    ├── gemini.py          # Gemini Provider
+    ├── openrouter.py      # OpenRouter Provider
+    └── yescode.py         # Yescode Provider
+```
+
+---
+
+## 核心概念
+
+### Trace(任务执行)
+
+一次完整的 Agent 执行。所有 Agent(主、子、人类协助)都是 Trace。
+
+**实现位置**:`agent/trace/models.py:Trace`
+
+### Goal(目标节点)
+
+计划中的一个目标,支持层级结构。
+
+**实现位置**:`agent/trace/goal_models.py:Goal`
+
+### Message(执行消息)
+
+对应 LLM API 的消息,每条 Message 关联一个 Goal。
+
+**实现位置**:`agent/trace/models.py:Message`
+
+---
+
+## 快速开始
+
+### 基础使用
+
+```python
+from agent.core.runner import AgentRunner, RunConfig
+
+# 创建 Runner
+runner = AgentRunner(
+    llm_call=create_llm_call(),
+    trace_store=FileSystemTraceStore()
+)
+
+# 运行 Agent
+async for item in runner.run(
+    messages=[{"role": "user", "content": "分析项目架构"}],
+    config=RunConfig(model="gpt-4o")
+):
+    if isinstance(item, Trace):
+        print(f"Trace: {item.trace_id}")
+    elif isinstance(item, Message):
+        print(f"Message: {item.content}")
+```
+
+### 使用工具
+
+```python
+from agent.tools import tool, ToolContext, ToolResult
+
+@tool(description="自定义工具")
+async def my_tool(arg: str, ctx: ToolContext) -> ToolResult:
+    return ToolResult(
+        title="成功",
+        output=f"处理结果: {arg}"
+    )
+```
+
+---
+
+## 文档
+
+### 模块文档(agent/docs/)
+
+- [架构设计](./docs/architecture.md):Agent Core 完整架构设计
+- [工具系统](./docs/tools.md):工具定义、注册、双层记忆
+- [Skills 指南](./docs/skills.md):Skill 分类、编写、加载
+- [Trace API](./docs/trace-api.md):Trace 模块 REST API 和 WebSocket 接口
+- [多模态支持](./docs/multimodal.md):图片、PDF 处理
+- [设计决策](./docs/decisions.md):架构决策记录
+
+### 项目级文档(../docs/)
+
+- [项目总览](../docs/README.md):完整的架构设计文档
+- [A2A IM 系统](../docs/a2a-im.md):Agent 间通讯
+- [Enterprise 层](../gateway/docs/enterprise/overview.md):组织级功能
+
+---
+
+## API
+
+### REST API
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| GET  | `/api/traces` | 列出 Traces |
+| GET  | `/api/traces/{id}` | 获取 Trace 详情 |
+| GET  | `/api/traces/{id}/messages` | 获取 Messages |
+| POST | `/api/traces` | 新建 Trace 并执行 |
+| POST | `/api/traces/{id}/run` | 续跑或回溯 |
+| POST | `/api/traces/{id}/stop` | 停止运行 |
+
+**实现位置**:`agent/trace/api.py`, `agent/trace/run_api.py`
+
+---
+
+## 相关项目
+
+- [Gateway](../gateway/README.md):A2A IM Gateway,提供 Agent 间通讯能力

+ 2 - 2
agent/__init__.py

@@ -3,7 +3,7 @@ Reson Agent - 模块化、可扩展的 Agent 框架
 
 
 核心导出:
 核心导出:
 - AgentRunner: Agent 执行引擎
 - AgentRunner: Agent 执行引擎
-- AgentConfig: Agent 配置
+- RunConfig: 运行配置
 - Trace, Message, Goal: 执行追踪
 - Trace, Message, Goal: 执行追踪
 - Experience, Skill: 记忆模型
 - Experience, Skill: 记忆模型
 - tool: 工具装饰器
 - tool: 工具装饰器
@@ -11,7 +11,7 @@ Reson Agent - 模块化、可扩展的 Agent 框架
 """
 """
 
 
 # 核心引擎
 # 核心引擎
-from agent.core.runner import AgentRunner, AgentConfig, CallResult, RunConfig
+from agent.core.runner import AgentRunner, CallResult, RunConfig
 from agent.core.presets import AgentPreset, AGENT_PRESETS, get_preset
 from agent.core.presets import AgentPreset, AGENT_PRESETS, get_preset
 
 
 # 执行追踪
 # 执行追踪

+ 11 - 0
agent/cli/__init__.py

@@ -0,0 +1,11 @@
+"""
+CLI 工具模块
+
+提供交互式控制等 CLI 相关功能。
+"""
+
+from .interactive import InteractiveController
+
+__all__ = [
+    "InteractiveController",
+]

+ 290 - 0
agent/cli/interactive.py

@@ -0,0 +1,290 @@
+"""
+交互式控制器
+
+提供暂停/继续、交互式菜单、经验总结等功能。
+"""
+
+import sys
+import asyncio
+from typing import Optional, Dict, Any
+from pathlib import Path
+
+from agent.core.runner import AgentRunner
+from agent.trace import TraceStore
+
+
+# ===== 非阻塞 stdin 检测 =====
+
+if sys.platform == 'win32':
+    import msvcrt
+
+
+def check_stdin() -> Optional[str]:
+    """
+    跨平台非阻塞检查 stdin 输入。
+
+    Windows: 使用 msvcrt.kbhit()
+    macOS/Linux: 使用 select.select()
+
+    Returns:
+        'pause' | 'quit' | None
+    """
+    if sys.platform == 'win32':
+        # Windows: 检查是否有按键按下
+        if msvcrt.kbhit():
+            ch = msvcrt.getwch().lower()
+            if ch == 'p':
+                return 'pause'
+            if ch == 'q':
+                return 'quit'
+        return None
+    else:
+        # Unix/Mac: 使用 select
+        import select
+        ready, _, _ = select.select([sys.stdin], [], [], 0)
+        if ready:
+            line = sys.stdin.readline().strip().lower()
+            if line in ('p', 'pause'):
+                return 'pause'
+            if line in ('q', 'quit'):
+                return 'quit'
+        return None
+
+
+def read_multiline() -> str:
+    """
+    读取多行输入,以连续两次回车(空行)结束。
+
+    Returns:
+        用户输入的多行文本
+    """
+    print("\n请输入干预消息(连续输入两次回车结束):")
+    lines = []
+    blank_count = 0
+
+    while True:
+        line = input()
+        if line == "":
+            blank_count += 1
+            if blank_count >= 2:
+                break
+            lines.append("")  # 保留单个空行
+        else:
+            blank_count = 0
+            lines.append(line)
+
+    # 去掉尾部多余空行
+    while lines and lines[-1] == "":
+        lines.pop()
+
+    return "\n".join(lines)
+
+
+# ===== 交互式控制器 =====
+
+class InteractiveController:
+    """
+    交互式控制器
+
+    管理暂停/继续、交互式菜单、经验总结等交互功能。
+    """
+
+    def __init__(
+        self,
+        runner: AgentRunner,
+        store: TraceStore,
+        enable_stdin_check: bool = True
+    ):
+        """
+        初始化交互式控制器
+
+        Args:
+            runner: Agent Runner 实例
+            store: Trace Store 实例
+            enable_stdin_check: 是否启用 stdin 检查
+        """
+        self.runner = runner
+        self.store = store
+        self.enable_stdin_check = enable_stdin_check
+
+    def check_stdin(self) -> Optional[str]:
+        """
+        检查 stdin 输入
+
+        Returns:
+            'pause' | 'quit' | None
+        """
+        if not self.enable_stdin_check:
+            return None
+        return check_stdin()
+
+    async def show_menu(
+        self,
+        trace_id: str,
+        current_sequence: int
+    ) -> Dict[str, Any]:
+        """
+        显示交互式菜单
+
+        Args:
+            trace_id: Trace ID
+            current_sequence: 当前消息序号
+
+        Returns:
+            用户选择的操作
+        """
+        print("\n" + "=" * 60)
+        print("  执行已暂停")
+        print("=" * 60)
+        print("请选择操作:")
+        print("  1. 插入干预消息并继续")
+        print("  2. 触发经验总结(reflect)")
+        print("  3. 查看当前 GoalTree")
+        print("  4. 手动压缩上下文(compact)")
+        print("  5. 继续执行")
+        print("  6. 停止执行")
+        print("=" * 60)
+
+        while True:
+            choice = input("请输入选项 (1-6): ").strip()
+
+            if choice == "1":
+                # 插入干预消息
+                text = read_multiline()
+                if not text:
+                    print("未输入任何内容,取消操作")
+                    continue
+
+                print(f"\n将插入干预消息并继续执行...")
+                # 从 store 读取实际的 last_sequence
+                live_trace = await self.store.get_trace(trace_id)
+                actual_sequence = live_trace.last_sequence if live_trace and live_trace.last_sequence else current_sequence
+
+                return {
+                    "action": "continue",
+                    "messages": [{"role": "user", "content": text}],
+                    "after_sequence": actual_sequence,
+                }
+
+            elif choice == "2":
+                # 触发经验总结
+                print("\n触发经验总结...")
+                focus = input("请输入反思重点(可选,直接回车跳过): ").strip()
+                await self.perform_reflection(trace_id, focus=focus)
+                continue
+
+            elif choice == "3":
+                # 查看 GoalTree
+                goal_tree = await self.store.get_goal_tree(trace_id)
+                if goal_tree and goal_tree.goals:
+                    print("\n当前 GoalTree:")
+                    print(goal_tree.to_prompt())
+                else:
+                    print("\n当前没有 Goal")
+                continue
+
+            elif choice == "4":
+                # 手动压缩上下文
+                await self.manual_compact(trace_id)
+                continue
+
+            elif choice == "5":
+                # 继续执行
+                print("\n继续执行...")
+                return {"action": "continue"}
+
+            elif choice == "6":
+                # 停止执行
+                print("\n停止执行...")
+                return {"action": "stop"}
+
+            else:
+                print("无效选项,请重新输入")
+
+    async def perform_reflection(
+        self,
+        trace_id: str,
+        focus: str = ""
+    ):
+        """
+        执行经验总结
+
+        Args:
+            trace_id: Trace ID
+            focus: 反思重点(可选)
+        """
+        from agent.core.prompts.knowledge import build_reflect_prompt
+        from agent.core.runner import RunConfig
+
+        trace = await self.store.get_trace(trace_id)
+        if not trace:
+            print("未找到 Trace")
+            return
+
+        saved_head = trace.head_sequence
+
+        # 构建反思 prompt
+        prompt = build_reflect_prompt()
+        if focus:
+            prompt += f"\n\n请特别关注:{focus}"
+
+        print("正在生成反思...")
+        reflect_cfg = RunConfig(trace_id=trace_id, max_iterations=1, tools=[])
+
+        try:
+            result = await self.runner.run_result(
+                messages=[{"role": "user", "content": prompt}],
+                config=reflect_cfg,
+            )
+            reflection_text = result.get("summary", "")
+
+            if reflection_text:
+                print("\n--- 反思内容 ---")
+                print(reflection_text)
+                print("--- 结束 ---\n")
+            else:
+                print("未生成反思内容")
+
+        finally:
+            # 恢复 head_sequence(反思消息成为侧枝,不污染主对话)
+            await self.store.update_trace(trace_id, head_sequence=saved_head)
+
+    async def manual_compact(self, trace_id: str):
+        """
+        手动压缩上下文
+
+        Args:
+            trace_id: Trace ID
+        """
+        from agent.core.runner import RunConfig
+
+        print("\n正在执行上下文压缩(compact)...")
+
+        try:
+            goal_tree = await self.store.get_goal_tree(trace_id)
+            trace = await self.store.get_trace(trace_id)
+
+            if not trace:
+                print("未找到 Trace,无法压缩")
+                return
+
+            # 重建当前 history
+            main_path = await self.store.get_main_path_messages(trace_id, trace.head_sequence)
+            history = [msg.to_llm_dict() for msg in main_path]
+            head_seq = main_path[-1].sequence if main_path else 0
+            next_seq = head_seq + 1
+
+            compact_config = RunConfig(trace_id=trace_id)
+            new_history, new_head, new_seq = await self.runner._compress_history(
+                trace_id=trace_id,
+                history=history,
+                goal_tree=goal_tree,
+                config=compact_config,
+                sequence=next_seq,
+                head_seq=head_seq,
+            )
+
+            print(f"\n✅ 压缩完成: {len(history)} 条消息 → {len(new_history)} 条")
+
+        except Exception as e:
+            print(f"\n❌ 压缩失败: {e}")

+ 2 - 3
agent/core/__init__.py

@@ -3,17 +3,16 @@ Agent Core - 核心引擎模块
 
 
 职责:
 职责:
 1. Agent 主循环逻辑(call() 和 run())
 1. Agent 主循环逻辑(call() 和 run())
-2. 配置数据类(AgentConfig, CallResult)
+2. 配置数据类(CallResult, RunConfig
 3. Agent 预设(AgentPreset)
 3. Agent 预设(AgentPreset)
 """
 """
 
 
-from agent.core.runner import AgentRunner, BUILTIN_TOOLS, AgentConfig, CallResult, RunConfig
+from agent.core.runner import AgentRunner, BUILTIN_TOOLS, CallResult, RunConfig
 from agent.core.presets import AgentPreset, AGENT_PRESETS, get_preset, register_preset
 from agent.core.presets import AgentPreset, AGENT_PRESETS, get_preset, register_preset
 
 
 __all__ = [
 __all__ = [
     "AgentRunner",
     "AgentRunner",
     "BUILTIN_TOOLS",
     "BUILTIN_TOOLS",
-    "AgentConfig",
     "CallResult",
     "CallResult",
     "RunConfig",
     "RunConfig",
     "AgentPreset",
     "AgentPreset",

+ 0 - 546
agent/core/prompts.py

@@ -1,546 +0,0 @@
-"""
-Agent 系统 Prompt 集中管理
-
-本文件集中管理 Agent 系统中使用的所有 prompt 模板,
-包括 runner.py、compaction.py、subagent.py、knowledge.py 等文件中的 prompt。
-
-## 📑 目录索引
-
-### 1. 系统级 Prompt (行 50+)
-- DEFAULT_SYSTEM_PREFIX                      # Agent 基础系统提示
-
-### 2. 研究流程 Prompt (行 60+)
-- RESEARCH_STAGE_PROMPT_TEMPLATE             # 调研阶段引导
-- PLANNING_STAGE_PROMPT                      # 计划阶段引导
-- RESEARCH_DECISION_GUIDE_TEMPLATE           # 调研决策阶段引导
-
-### 3. 压缩相关 Prompt (行 110+)
-- COMPRESSION_EVAL_PROMPT_TEMPLATE           # Level 2 压缩与经验评估
-- REFLECT_PROMPT                             # 经验反思提取
-- SUMMARY_HEADER_TEMPLATE                    # 压缩后的摘要头部
-
-### 4. 工具执行 Prompt (行 180+)
-- TRUNCATION_HINT                            # 响应截断提示
-- TOOL_INTERRUPTED_MESSAGE                   # 工具执行中断提示
-- AGENT_INTERRUPTED_SUMMARY                  # Agent 中断摘要
-- AGENT_CONTINUE_HINT_TEMPLATE               # Agent 继续执行提示
-
-### 5. 任务生成 Prompt (行 200+)
-- TASK_NAME_GENERATION_SYSTEM_PROMPT         # 任务名称生成
-- TASK_NAME_FALLBACK                         # 默认任务名称
-
-### 6. 经验管理 Prompt (行 210+)
-- EXPERIENCE_ENTRY_TEMPLATE                  # 经验条目格式
-- EXPERIENCE_SUMMARY_WITH_RESULTS            # 有结果的经验摘要
-- EXPERIENCE_SUMMARY_NO_RESULTS              # 无结果的经验摘要
-- EXPERIENCE_PARSE_WARNING                   # 经验解析警告
-
-### 7. 辅助函数 - 基础 Prompt 构建 (行 240+)
-- build_research_stage_prompt()              # 构建调研阶段 prompt
-- build_research_decision_guide()            # 构建调研决策引导
-- build_compression_eval_prompt()            # 构建压缩评估 prompt
-- build_summary_header()                     # 构建摘要头部
-- build_tool_interrupted_message()           # 构建工具中断消息
-- build_agent_continue_hint()                # 构建 Agent 继续提示
-- build_experience_entry()                   # 构建经验条目
-
-### 8. 子 Agent 相关 Prompt (行 320+)
-- EVALUATE_PROMPT_TEMPLATE                   # 评估任务 prompt
-- DELEGATE_RESULT_HEADER                     # 委托任务结果头部
-- DELEGATE_SAVED_KNOWLEDGE_HEADER            # 保存知识头部
-- DELEGATE_STATS_HEADER                      # 执行统计头部
-- EXPLORE_RESULT_HEADER                      # 探索结果头部
-- EXPLORE_BRANCH_TEMPLATE                    # 探索分支模板
-- EXPLORE_STATUS_SUCCESS                     # 成功状态
-- EXPLORE_STATUS_FAILED                      # 失败状态
-- EXPLORE_STATUS_ERROR                       # 错误状态
-- EXPLORE_SUMMARY_HEADER                     # 探索总结头部
-
-### 9. 辅助函数 - 子 Agent Prompt 构建 (行 380+)
-- build_evaluate_prompt()                    # 构建评估 prompt
-
-### 10. 知识管理相关 Prompt (行 400+)
-- KNOWLEDGE_SEMANTIC_ROUTE_PROMPT_TEMPLATE   # 知识语义路由
-- KNOWLEDGE_EVOLVE_PROMPT_TEMPLATE           # 知识进化重写
-- KNOWLEDGE_SLIM_PROMPT_TEMPLATE             # 知识库瘦身
-
-### 11. 辅助函数 - 知识管理 Prompt 构建 (行 450+)
-- build_knowledge_semantic_route_prompt()    # 构建知识路由 prompt
-- build_knowledge_evolve_prompt()            # 构建知识进化 prompt
-- build_knowledge_slim_prompt()              # 构建知识瘦身 prompt
-
-## 🔍 快速查找
-
-**按使用场景查找:**
-- 研究流程:第 2 节
-- 对话压缩:第 3 节
-- 工具调用:第 4 节
-- 经验管理:第 6 节
-- 子 Agent:第 8 节
-- 知识管理:第 10 节
-
-**按文件来源查找:**
-- runner.py → 第 1, 2, 4, 5 节
-- compaction.py → 第 3 节
-- subagent.py → 第 8 节
-- knowledge.py → 第 10 节
-
-## ⚠️ 重要提示
-
-1. **变量占位符**:所有 `{变量名}` 格式的占位符必须保留
-2. **特殊标记**:`[[EVALUATION]]`、`[[SUMMARY]]` 等标记不可删除
-3. **输出格式关键字**:代码解析依赖的关键字需要保持一致
-4. **使用辅助函数**:优先使用 `build_*()` 函数而非直接 `.format()`
-"""
-
-# ============================================================
-# 系统级 Prompt
-# ============================================================
-
-DEFAULT_SYSTEM_PREFIX = "你是最顶尖的AI助手,可以拆分并调用工具逐步解决复杂问题。"
-
-
-# ============================================================
-# 研究流程 Prompt - 调研阶段
-# ============================================================
-
-RESEARCH_STAGE_PROMPT_TEMPLATE = """## 📚 研究流程 - 执行调研
-
-现有信息不足,需要进行调研。
-
-{research_skill_content}
-
-**重要提示**:
-- 调研完成后,请使用 `save_knowledge` 工具保存调研结果
-- 系统会自动检测到 save_knowledge 调用,并进入下一阶段(计划)
-"""
-
-
-# ============================================================
-# 研究流程 Prompt - 计划阶段
-# ============================================================
-
-PLANNING_STAGE_PROMPT = """## 📋 研究流程 - 制定计划
-
-调研已完成(或无需调研),现在请制定执行计划。
-
-**请立即执行以下操作**:
-1. 使用 `goal` 工具创建目标树
-2. 将任务分解为可执行的子目标
-3. 为每个子目标设置合理的优先级
-
-注意:这是强制步骤,必须创建 goal tree 才能进入执行阶段。
-"""
-
-
-# ============================================================
-# 研究流程 Prompt - 调研决策阶段
-# ============================================================
-
-RESEARCH_DECISION_GUIDE_TEMPLATE = """---
-
-## 🤔 调研决策
-
-{experience_summary}
-
-### 决策指南
-
-**当前状态**:系统已自动检索知识库和经验库,相关内容已注入到上方的 GoalTree 中(查看 Current Goal 下的「📚 相关知识」部分)。
-
-**请根据已注入的知识和经验,选择下一步行动**:
-
-**选项 1: 知识充足,直接制定计划**
-- 如果上方显示的知识和经验已经足够完成任务
-- 直接使用 `goal` 工具制定执行计划
-
-**选项 2: 知识不足,需要调研** ⭐
-- 如果上方没有显示相关知识,或现有知识不足以完成任务
-- **立即调用 `agent` 工具启动调研子任务**:
-
-```python
-agent(
-    task=\"\"\"针对任务「{task_desc}」进行深入调研:
-
-1. 使用 web_search 工具搜索相关技术文档、教程、最佳实践
-2. 搜索关键词建议:
-   - 核心技术名称 + "教程"
-   - 核心技术名称 + "最佳实践"
-   - 核心技术名称 + "示例代码"
-3. 使用 read_file 工具查看项目中的相关文件
-4. 对每条有价值的信息,使用 save_knowledge 工具保存,标签类型选择:
-   - tool: 工具使用方法
-   - definition: 概念定义
-   - usercase: 使用案例
-   - strategy: 策略经验
-
-调研完成后,系统会自动进入计划阶段。
-\"\"\",
-    skills=["research"]  # 注入调研指南
-)
-```
-
-**重要提示**:
-- 如果 GoalTree 中没有显示「📚 相关知识」,说明知识库为空,必须先调研
-- 调研应该简洁高效,最多设立两个 goal
-"""
-
-
-# ============================================================
-# 压缩相关 Prompt - Level 2 压缩与经验评估
-# ============================================================
-
-COMPRESSION_EVAL_PROMPT_TEMPLATE = """请对以上对话历史进行压缩总结,并评价所引用的历史知识/经验。
-### 任务 1:评价已用知识
-本次任务参考了以下知识内容:{ex_reference_list}
-
-请对比"知识建议"与"实际执行轨迹",给出三色打分:
-[[EVALUATION]]
-ID: knowledge-xxx 或 research-xxx | Result: helpful/harmful/mixed | Reason: [优点]... [局限/修正]...
-
-### 任务 2:对话历史摘要
-要求:
-1. 保留关键决策、结论和产出(如创建的文件、修改的代码、得出的分析结论)
-2. 保留重要的上下文(如用户的要求、约束条件、之前的讨论结果)
-3. 省略中间探索过程、重复的工具调用细节
-4. 使用结构化格式(标题 + 要点 + 相关资源引用,若有)
-5. 控制在 2000 字以内
-格式要求:
-[[SUMMARY]]
-(此处填写结构化的摘要内容)
-
-当前 GoalTree 状态:
-{goal_tree_prompt}
-"""
-
-
-# ============================================================
-# 压缩相关 Prompt - 经验反思
-# ============================================================
-
-REFLECT_PROMPT = """请回顾以上整个执行过程,提取有价值的经验教训。
-你必须将经验与当前的任务意图(Intent)和环境状态(State)挂钩,以便未来精准检索。
-关注以下方面:
-1. 人工干预:用户中途的指令是否说明了原来的执行过程哪里有问题
-2. 弯路:哪些尝试是不必要的,有没有更直接的方法
-3. 好的决策:哪些判断和选择是正确的,值得记住
-4. 工具使用:哪些工具用法是高效的,哪些可以改进
-
-输出格式(严格遵守):
-- 在每条经验前加一个[]中添加自定义的标签,标签要求总结实际的内容为若干词语,包括:
-    - intent: 当前的goal
-    - state: 环境状态(如果与工具相关,可以在标签中加入工具的名称)
-- 经验标签可用自然语言描述
-- 每条经验单独成段,格式固定为:- 当 [条件] 时,应该 [动作](原因:[一句话说明])。具体案例:[案例]
-- 条目之间用一个空行分隔
-- 不输出任何标题、分类、编号、分隔线或其他结构
-- 不使用 markdown 加粗、表格、代码块等格式
-- 每条经验自包含,读者无需上下文即可理解
-- 只提取最有价值的 5-10 条,宁少勿滥
-
-示例(仅供参考格式,不要复制内容):
-- [intent:示例生成 state:用户提醒,指定样本] 当用户说"给我示例"时,应该用真实数据而不是编造(原因:编造的示例无法验证质量)。具体案例:training_samples.json 中的示例全是 LLM 自己编造的,用户明确要求"基于我指定的样本"。
-"""
-
-
-# ============================================================
-# 压缩相关 Prompt - 压缩后的摘要头部
-# ============================================================
-
-SUMMARY_HEADER_TEMPLATE = """## 对话历史摘要(自动压缩)
-
-{summary_text}
-
----
-请基于以上摘要和当前 GoalTree 继续执行任务。"""
-
-
-# ============================================================
-# 工具执行 Prompt - 响应截断提示
-# ============================================================
-
-TRUNCATION_HINT = """你的响应因为 max_tokens 限制被截断,tool call 参数不完整,未执行。请将大内容拆分为多次小的工具调用(例如用 write_file 的 append 模式分批写入)。"""
-
-
-# ============================================================
-# 工具执行 Prompt - 工具执行中断提示
-# ============================================================
-
-TOOL_INTERRUPTED_MESSAGE = """⚠️ 工具 {tool_name} 执行被中断(进程异常退出),未获得执行结果。请根据需要重新调用。"""
-
-AGENT_INTERRUPTED_SUMMARY = "⚠️ 子Agent执行被中断(进程异常退出)"
-
-AGENT_CONTINUE_HINT_TEMPLATE = '使用 continue_from="{sub_trace_id}" 可继续执行,保留已有进度'
-
-
-# ============================================================
-# 任务生成 Prompt
-# ============================================================
-
-TASK_NAME_GENERATION_SYSTEM_PROMPT = "用中文为以下任务生成一个简短标题(10-30字),只输出标题本身:"
-
-TASK_NAME_FALLBACK = "未命名任务"
-
-
-# ============================================================
-# 经验保存 Prompt - 经验条目格式
-# ============================================================
-
-EXPERIENCE_ENTRY_TEMPLATE = """---
-id: {ex_id}
-trace_id: {trace_id}
-tags: {{intent: {intents}, state: {states}}}
-metrics: {{helpful: 1, harmful: 0}}
-created_at: {created_at}
----
-{content}
-"""
-
-
-# ============================================================
-# 经验检索 Prompt - 经验摘要格式
-# ============================================================
-
-EXPERIENCE_SUMMARY_WITH_RESULTS = "✅ 已自动检索到 {count} 条相关经验(见上方 GoalTree 中的「📚 相关知识」)\n"
-
-EXPERIENCE_SUMMARY_NO_RESULTS = "❌ 未找到相关经验\n"
-
-
-# ============================================================
-# 经验评估 Prompt - 格式解析警告
-# ============================================================
-
-EXPERIENCE_PARSE_WARNING = "未能解析出符合格式的经验条目,请检查 REFLECT_PROMPT。"
-
-
-# ============================================================
-# 辅助函数:构建特定场景的 Prompt
-# ============================================================
-
-def build_research_stage_prompt(research_skill_content: str) -> str:
-    """构建调研阶段的引导 prompt"""
-    return RESEARCH_STAGE_PROMPT_TEMPLATE.format(
-        research_skill_content=research_skill_content
-    )
-
-
-def build_research_decision_guide(
-    experience_results: list,
-    task_desc: str
-) -> str:
-    """构建调研决策阶段的引导消息"""
-    if experience_results:
-        experience_summary = EXPERIENCE_SUMMARY_WITH_RESULTS.format(
-            count=len(experience_results)
-        )
-    else:
-        experience_summary = EXPERIENCE_SUMMARY_NO_RESULTS
-
-    # 截取任务描述前100字符
-    task_desc_short = task_desc[:100] if len(task_desc) > 100 else task_desc
-
-    return RESEARCH_DECISION_GUIDE_TEMPLATE.format(
-        experience_summary=experience_summary,
-        task_desc=task_desc_short
-    )
-
-
-def build_compression_eval_prompt(
-    goal_tree_prompt: str,
-    ex_reference_list: str
-) -> str:
-    """构建 Level 2 压缩 prompt(含经验评估)"""
-    return COMPRESSION_EVAL_PROMPT_TEMPLATE.format(
-        goal_tree_prompt=goal_tree_prompt,
-        ex_reference_list=ex_reference_list
-    )
-
-
-def build_summary_header(summary_text: str) -> str:
-    """构建压缩后的摘要头部"""
-    return SUMMARY_HEADER_TEMPLATE.format(summary_text=summary_text)
-
-
-def build_tool_interrupted_message(tool_name: str) -> str:
-    """构建工具中断消息"""
-    return TOOL_INTERRUPTED_MESSAGE.format(tool_name=tool_name)
-
-
-def build_agent_continue_hint(sub_trace_id: str) -> str:
-    """构建 Agent 继续执行提示"""
-    return AGENT_CONTINUE_HINT_TEMPLATE.format(sub_trace_id=sub_trace_id)
-
-
-def build_experience_entry(
-    ex_id: str,
-    trace_id: str,
-    intents: list,
-    states: list,
-    created_at: str,
-    content: str
-) -> str:
-    """构建经验条目"""
-    return EXPERIENCE_ENTRY_TEMPLATE.format(
-        ex_id=ex_id,
-        trace_id=trace_id,
-        intents=intents,
-        states=states,
-        created_at=created_at,
-        content=content
-    )
-
-# ============================================================
-# 子 Agent 相关 Prompt - 评估任务
-# ============================================================
-
-EVALUATE_PROMPT_TEMPLATE = """# 评估任务
-
-请评估以下任务的执行结果是否满足要求。
-
-## 目标描述
-
-{goal_description}
-
-## 执行结果
-
-{result_text}
-
-## 输出格式
-
-## 评估结论
-[通过/不通过]
-
-## 评估理由
-[详细说明通过或不通过原因]
-
-## 修改建议(如果不通过)
-1. [建议1]
-2. [建议2]
-"""
-
-
-# ============================================================
-# 子 Agent 相关 Prompt - 结果格式化
-# ============================================================
-
-DELEGATE_RESULT_HEADER = "## 委托任务完成\n"
-
-DELEGATE_SAVED_KNOWLEDGE_HEADER = "**保存的知识** ({count} 条):"
-
-DELEGATE_STATS_HEADER = "**执行统计**:"
-
-EXPLORE_RESULT_HEADER = "## 探索结果\n"
-
-EXPLORE_BRANCH_TEMPLATE = "### 方案 {branch_name}: {task}"
-
-EXPLORE_STATUS_SUCCESS = "**状态**: ✓ 完成"
-
-EXPLORE_STATUS_FAILED = "**状态**: ✗ 失败"
-
-EXPLORE_STATUS_ERROR = "**状态**: ✗ 异常"
-
-EXPLORE_SUMMARY_HEADER = "## 总结"
-
-
-# ============================================================
-# 辅助函数:构建子 Agent 相关 Prompt
-# ============================================================
-
-def build_evaluate_prompt(goal_description: str, result_text: str) -> str:
-    """构建评估 prompt"""
-    return EVALUATE_PROMPT_TEMPLATE.format(
-        goal_description=goal_description,
-        result_text=result_text or "(无执行结果)"
-    )
-
-
-# ============================================================
-# 知识管理相关 Prompt - 语义路由
-# ============================================================
-
-KNOWLEDGE_SEMANTIC_ROUTE_PROMPT_TEMPLATE = """你是一个知识检索专家。根据用户的当前任务需求,从下列原子知识元数据中挑选出最相关的最多 {routing_k} 个知识 ID。
-任务需求:"{query_text}"
-
-可选知识列表:
-{routing_data}
-
-请直接输出 ID 列表,用逗号分隔(例如: knowledge-20260302-001, research-20260302-002)。若无相关项请输出 "None"。
-"""
-
-
-# ============================================================
-# 知识管理相关 Prompt - 知识进化重写
-# ============================================================
-
-KNOWLEDGE_EVOLVE_PROMPT_TEMPLATE = """你是一个 AI Agent 知识库管理员。请根据反馈建议,对现有的知识内容进行重写进化。
-
-【原知识内容】:
-{old_content}
-
-【实战反馈建议】:
-{feedback}
-
-【重写要求】:
-1. 融合知识:将反馈中的避坑指南、新参数或修正后的选择逻辑融入原知识,使其更具通用性和准确性。
-2. 保持结构:如果原内容有特定格式(如 Markdown、代码示例等),请保持该格式。
-3. 语言:简洁直接,使用中文。
-4. 禁止:严禁输出任何开场白、解释语或额外的 Markdown 标题,直接返回重写后的正文。
-"""
-
-
-# ============================================================
-# 知识管理相关 Prompt - 知识库瘦身
-# ============================================================
-
-KNOWLEDGE_SLIM_PROMPT_TEMPLATE = """你是一个 AI Agent 知识库管理员。以下是当前知识库的全部条目,请执行瘦身操作:
-
-【任务】:
-1. 识别语义高度相似或重复的知识,将它们合并为一条更精炼、更通用的知识。
-2. 合并时保留 helpful 最高的那条的 ID 和 metrics(metrics 中 helpful/harmful 取各条之和)。
-3. 对于独立的、无重复的知识,保持原样不动。
-4. 保持原有的知识结构和格式。
-
-【当前知识库】:
-{entries_text}
-
-【输出格式要求】:
-严格按以下格式输出每条知识,条目之间用 === 分隔:
-ID: <保留的id>
-TAGS: <yaml格式的tags>
-METRICS: <yaml格式的metrics>
-SCORE: <评分>
-SCENARIO: <场景描述>
-CONTENT: <知识内容>
-===
-"""
-
-
-# ============================================================
-# 辅助函数:构建知识管理相关 Prompt
-# ============================================================
-
-def build_knowledge_semantic_route_prompt(
-    query_text: str,
-    routing_data: str,
-    routing_k: int
-) -> str:
-    """构建知识语义路由 prompt"""
-    return KNOWLEDGE_SEMANTIC_ROUTE_PROMPT_TEMPLATE.format(
-        query_text=query_text,
-        routing_data=routing_data,
-        routing_k=routing_k
-    )
-
-
-def build_knowledge_evolve_prompt(old_content: str, feedback: str) -> str:
-    """构建知识进化重写 prompt"""
-    return KNOWLEDGE_EVOLVE_PROMPT_TEMPLATE.format(
-        old_content=old_content,
-        feedback=feedback
-    )
-
-
-def build_knowledge_slim_prompt(entries_text: str) -> str:
-    """构建知识库瘦身 prompt"""
-    return KNOWLEDGE_SLIM_PROMPT_TEMPLATE.format(
-        entries_text=entries_text
-    )

+ 58 - 0
agent/core/prompts/__init__.py

@@ -0,0 +1,58 @@
+"""
+agent.core.prompts - Agent 系统 Prompt 集中管理
+
+子模块:
+- runner.py     系统提示、工具中断、任务命名、经验格式
+- knowledge.py  知识反思提取(压缩时 + 任务完成后)
+- compression.py  消息压缩总结
+- subagent.py   子 Agent 评估、结果格式化、知识管理
+"""
+
+from agent.core.prompts.runner import (
+    DEFAULT_SYSTEM_PREFIX,
+    TRUNCATION_HINT,
+    TOOL_INTERRUPTED_MESSAGE,
+    AGENT_INTERRUPTED_SUMMARY,
+    AGENT_CONTINUE_HINT_TEMPLATE,
+    TASK_NAME_GENERATION_SYSTEM_PROMPT,
+    TASK_NAME_FALLBACK,
+    build_tool_interrupted_message,
+    build_agent_continue_hint,
+)
+
+from agent.core.prompts.knowledge import (
+    REFLECT_PROMPT,
+    COMPLETION_REFLECT_PROMPT,
+    build_reflect_prompt,
+)
+
+from agent.core.prompts.compression import (
+    COMPRESSION_PROMPT_TEMPLATE,
+    COMPRESSION_EVAL_PROMPT_TEMPLATE,
+    SUMMARY_HEADER_TEMPLATE,
+    build_compression_eval_prompt,
+    build_summary_header,
+)
+
+__all__ = [
+    # runner
+    "DEFAULT_SYSTEM_PREFIX",
+    "TRUNCATION_HINT",
+    "TOOL_INTERRUPTED_MESSAGE",
+    "AGENT_INTERRUPTED_SUMMARY",
+    "AGENT_CONTINUE_HINT_TEMPLATE",
+    "TASK_NAME_GENERATION_SYSTEM_PROMPT",
+    "TASK_NAME_FALLBACK",
+    "build_tool_interrupted_message",
+    "build_agent_continue_hint",
+    # knowledge
+    "REFLECT_PROMPT",
+    "COMPLETION_REFLECT_PROMPT",
+    "build_reflect_prompt",
+    # compression
+    "COMPRESSION_PROMPT_TEMPLATE",
+    "COMPRESSION_EVAL_PROMPT_TEMPLATE",
+    "SUMMARY_HEADER_TEMPLATE",
+    "build_compression_eval_prompt",
+    "build_summary_header",
+]

+ 50 - 0
agent/core/prompts/compression.py

@@ -0,0 +1,50 @@
+"""
+压缩相关 Prompt
+
+包含 Level 2 消息压缩(LLM 总结)使用的 prompt。
+"""
+
+# ===== 压缩总结 =====
+
+COMPRESSION_PROMPT_TEMPLATE = """请对以上对话历史进行压缩总结。
+
+### 摘要要求
+1. 保留关键决策、结论和产出(如创建的文件、修改的代码、得出的分析结论)
+2. 保留重要的上下文(如用户的要求、约束条件、之前的讨论结果)
+3. 省略中间探索过程、重复的工具调用细节
+4. 使用结构化格式(标题 + 要点 + 相关资源引用,若有)
+5. 控制在 2000 字以内
+
+当前 GoalTree 状态:
+{goal_tree_prompt}
+
+格式要求:
+[[SUMMARY]]
+(此处填写结构化的摘要内容)
+"""
+
+# 保留旧名以兼容 compaction.py 的调用
+COMPRESSION_EVAL_PROMPT_TEMPLATE = COMPRESSION_PROMPT_TEMPLATE
+
+SUMMARY_HEADER_TEMPLATE = """## 对话历史摘要(自动压缩)
+
+{summary_text}
+
+---
+*以上为压缩摘要,原始对话历史已归档。*
+"""
+
+# ===== 辅助函数 =====
+
+def build_compression_eval_prompt(
+    goal_tree_prompt: str,
+    ex_reference_list: str = "",
+) -> str:
+    return COMPRESSION_EVAL_PROMPT_TEMPLATE.format(
+        goal_tree_prompt=goal_tree_prompt,
+        ex_reference_list=ex_reference_list,
+    )
+
+
+def build_summary_header(summary_text: str) -> str:
+    return SUMMARY_HEADER_TEMPLATE.format(summary_text=summary_text)

+ 98 - 0
agent/core/prompts/knowledge.py

@@ -0,0 +1,98 @@
+"""
+知识提取相关 Prompt
+
+两个场景,各自独立配置:
+- REFLECT_PROMPT:            压缩时阶段性反思(消息量超阈值,对当前批历史提炼)
+- COMPLETION_REFLECT_PROMPT: 任务完成后全局复盘(对整个任务的全局视角)
+
+两个 prompt 都要求 LLM 直接调用 `knowledge_save` 工具保存经验,
+而不是输出结构化文本再由 runner 解析。
+"""
+
+# ===== 压缩时阶段性反思 =====
+
+REFLECT_PROMPT = """请回顾以上执行过程,将值得沉淀的经验直接用 `knowledge_save` 工具保存到知识库。
+
+**关注以下方面**:
+1. 人工干预:用户中途的指令说明了哪里出了问题
+2. 弯路:哪些尝试是不必要的,有没有更直接的方法
+3. 好的决策:哪些判断和选择是正确的,值得记住
+4. 工具使用:哪些工具用法是高效的,哪些可以改进
+5. **资源发现**:是否发现了有价值的资源需要保存(见下方说明)
+
+**每条经验调用一次 `knowledge_save`,参数说明**:
+- `task`: 这条经验适用的场景,格式:「在[什么情境]下,[要完成什么]」
+- `content`: 具体经验内容,格式:「当[条件]时,应该[动作](原因:[一句话])。案例:[具体案例]」
+- `types`: 选 `["strategy"]`;如果涉及工具用法也可加 `"tool"`
+- `tags`: 用 `intent`(任务意图)和 `state`(环境状态/相关工具名)标注,便于检索
+- `score`: 1-5,根据这条经验的价值评估
+- `resource_ids`: 如果关联了资源,填写资源 ID 列表(可选)
+
+**资源提取指南**:
+如果执行过程中涉及以下内容,应先用 `resource_save` 保存资源,再用 `knowledge_save` 提交相关的经验/知识:
+
+1. **复杂代码工具**(逻辑复杂、超过 100 行):
+   - 调用 `resource_save(resource_id="code/{category}/{name}", title="...", body="代码内容", content_type="code", metadata={"language": "python"})`
+   - 然后在 `knowledge_save` 中通过 `resource_ids=["code/{category}/{name}"]` 关联
+
+2. **账号密码凭证**:
+   - 调用 `resource_save(resource_id="credentials/{website}", title="...", body="使用说明", secure_body="账号:xxx\\n密码:xxx", content_type="credential", metadata={"acquired_at": "2026-03-06T10:00:00Z"})`
+   - 然后在 `knowledge_save` 中通过 `resource_ids=["credentials/{website}"]` 关联
+
+3. **Cookie 和登录态**:
+   - 调用 `resource_save(resource_id="cookies/{website}", title="...", body="获取方法", secure_body="cookie内容", content_type="cookie", metadata={"acquired_at": "...", "expires_at": "..."})`
+   - 然后在 `knowledge_save` 中通过 `resource_ids=["cookies/{website}"]` 关联
+
+4. **多资源引用**:
+   - 一个知识可以关联多个资源,如:`resource_ids=["code/selenium/login", "credentials/website_a"]`
+
+**注意**:
+- 只保存最有价值的经验,宁少勿滥;一次就成功或比较简单的经验就不要记录了,记录反复尝试或被用户指导后才成功的经验、或者是调研之后的收获。
+- 不需要输出任何文字,直接调用工具即可
+- 如果没有值得保存的经验,不调用任何工具
+"""
+
+
+# ===== 任务完成后全局复盘 =====
+
+COMPLETION_REFLECT_PROMPT = """请对整个任务进行复盘,将值得沉淀的经验直接用 `knowledge_save` 工具保存到知识库。
+
+与压缩时的阶段性反思不同,这是任务结束后的全局视角,关注:
+1. 任务整体路径:实际走的路径与最初计划的偏差
+2. 关键决策点:哪些决策显著影响了最终结果
+3. 可复用的模式:哪些做法在类似任务中可以直接复用
+4. 踩过的坑:哪些问题本可提前规避
+5. **资源沉淀**:任务中产生或发现的有价值资源(见下方说明)
+
+**每条经验调用一次 `knowledge_save`,参数说明**:
+- `task`: 这条经验适用的场景,格式:「在[什么情境]下,[要完成什么]」
+- `content`: 具体经验内容,格式:「当[条件]时,应该[动作](原因:[一句话])。案例:[具体案例]」
+- `types`: 选 `["strategy"]`;如果涉及工具用法也可加 `"tool"`
+- `tags`: 用 `intent`(任务意图)和 `state`(环境状态/相关工具名)标注,便于检索
+- `score`: 1-5,根据这条经验的价值评估
+- `resource_ids`: 如果关联了资源,填写资源 ID 列表(可选)
+
+**资源提取指南**:
+如果任务中涉及以下内容,应先用 `resource_save` 保存资源,再用 `knowledge_save` 关联:
+
+1. **复杂代码工具**(逻辑复杂、超过 20 行、可复用):
+   - 调用 `resource_save(resource_id="code/{category}/{name}", title="...", body="代码内容", content_type="code", metadata={"language": "python"})`
+   - 然后在 `knowledge_save` 中通过 `resource_id` 关联
+
+2. **账号密码凭证**:
+   - 调用 `resource_save(resource_id="credentials/{website}", title="...", body="使用说明", secure_body="账号:xxx\\n密码:xxx", content_type="credential", metadata={"acquired_at": "2026-03-06T10:00:00Z"})`
+   - 然后在 `knowledge_save` 中通过 `secure_resource_id` 关联
+
+3. **Cookie 和登录态**:
+   - 调用 `resource_save(resource_id="cookies/{website}", title="...", body="获取方法", secure_body="cookie内容", content_type="cookie", metadata={"acquired_at": "...", "expires_at": "..."})`
+   - 然后在 `knowledge_save` 中通过 `secure_resource_id` 关联
+
+**注意**:
+- 只保存最有价值的经验,宁少勿滥;一次就成功或比较简单的经验就不要记录了,记录反复尝试或被用户指导后才成功的经验、或者是调研之后的收获。
+- 不需要输出任何文字,直接调用工具即可
+- 如果没有值得保存的经验,不调用任何工具
+"""
+
+
+def build_reflect_prompt() -> str:
+    return REFLECT_PROMPT

+ 39 - 0
agent/core/prompts/runner.py

@@ -0,0 +1,39 @@
+"""
+Runner 相关 Prompt
+
+包含 AgentRunner 主循环使用的 prompt:
+- 系统提示前缀
+- 工具执行中断提示
+- 任务名称生成
+- 经验条目格式
+"""
+
+# ===== 系统提示 =====
+
+DEFAULT_SYSTEM_PREFIX = "你是最顶尖的AI助手,可以拆分并调用工具逐步解决复杂问题。"
+
+# ===== 工具执行 =====
+
+TRUNCATION_HINT = """你的响应因为 max_tokens 限制被截断,tool call 参数不完整,未执行。请将大内容拆分为多次小的工具调用(例如用 write_file 的 append 模式分批写入)。"""
+
+TOOL_INTERRUPTED_MESSAGE = """⚠️ 工具 {tool_name} 执行被中断(进程异常退出),未获得执行结果。请根据需要重新调用。"""
+
+AGENT_INTERRUPTED_SUMMARY = "⚠️ 子Agent执行被中断(进程异常退出)"
+
+AGENT_CONTINUE_HINT_TEMPLATE = '使用 continue_from="{sub_trace_id}" 可继续执行,保留已有进度'
+
+# ===== 任务命名 =====
+
+TASK_NAME_GENERATION_SYSTEM_PROMPT = "用中文为以下任务生成一个简短标题(10-30字),只输出标题本身:"
+
+TASK_NAME_FALLBACK = "未命名任务"
+
+# ===== 辅助函数 =====
+
+def build_tool_interrupted_message(tool_name: str) -> str:
+    return TOOL_INTERRUPTED_MESSAGE.format(tool_name=tool_name)
+
+
+def build_agent_continue_hint(sub_trace_id: str) -> str:
+    return AGENT_CONTINUE_HINT_TEMPLATE.format(sub_trace_id=sub_trace_id)
+

Разница между файлами не показана из-за своего большого размера
+ 285 - 485
agent/core/runner.py


+ 1400 - 0
agent/docs/architecture.md

@@ -0,0 +1,1400 @@
+# Agent Core 架构设计
+
+本文档描述 Agent Core 模块的完整架构设计。
+
+## 文档维护规范
+
+0. **先改文档,再动代码** - 新功能或重大修改需先完成文档更新、并完成审阅后,再进行代码实现;除非改动较小、不被文档涵盖
+1. **文档分层,链接代码** - 重要或复杂设计可以另有详细文档;关键实现需标注代码文件路径;格式:`module/file.py:function_name`
+2. **简洁快照,日志分离** - 只记录最重要的、与代码准确对应的或者明确的已完成的设计的信息,避免推测、建议,或大量代码;决策依据或修改日志若有必要,可在 `decisions.md` 另行记录
+
+---
+
+## 系统概览
+
+**核心理念:所有 Agent 都是 Trace**
+
+| 类型 | 创建方式 | 父子关系 | 状态 |
+|------|---------|---------|------|
+| 主 Agent | 直接调用 `runner.run()` | 无 parent | 正常执行 |
+| 子 Agent | 通过 `agent` 工具 | `parent_trace_id` / `parent_goal_id` 指向父 | 正常执行 |
+| 人类协助 | 通过 `ask_human` 工具 | `parent_trace_id` 指向父 | 阻塞等待 |
+
+---
+
+## 核心架构
+
+### 模块结构
+
+```
+agent/
+├── core/                  # 核心引擎
+│   ├── runner.py          # AgentRunner + 运行时配置
+│   └── presets.py         # Agent 预设(explore、analyst 等)
+│
+├── trace/                 # 执行追踪(含计划管理)
+│   ├── models.py          # Trace, Message
+│   ├── goal_models.py     # Goal, GoalTree, GoalStats
+│   ├── protocols.py       # TraceStore 接口
+│   ├── store.py           # FileSystemTraceStore 实现
+│   ├── goal_tool.py       # goal 工具(计划管理)
+│   ├── compaction.py      # Context 压缩
+│   ├── api.py             # REST API
+│   ├── websocket.py       # WebSocket API
+│   └── trace_id.py        # Trace ID 生成工具
+│
+├── tools/                 # 外部交互工具
+│   ├── registry.py        # 工具注册表
+│   ├── schema.py          # Schema 生成器
+│   ├── models.py          # ToolResult, ToolContext
+│   └── builtin/
+│       ├── file/          # 文件操作(read, write, edit, glob, grep)
+│       ├── browser/       # 浏览器自动化
+│       ├── bash.py        # 命令执行
+│       ├── sandbox.py     # 沙箱环境
+│       ├── search.py      # 网络搜索
+│       ├── webfetch.py    # 网页抓取
+│       ├── skill.py       # 技能加载
+│       └── subagent.py    # agent / evaluate 工具(子 Agent 创建与评估)
+│
+├── memory/                # 跨会话记忆
+│   ├── models.py          # Experience, Skill
+│   ├── protocols.py       # MemoryStore 接口
+│   ├── stores.py          # 存储实现
+│   ├── skill_loader.py    # Skill 加载器
+│   └── skills/            # 内置 Skills(自动注入 system prompt)
+│       ├── planning.md    # 计划与 Goal 工具使用
+│       ├── research.md    # 搜索与内容研究
+│       └── browser.md     # 浏览器自动化
+│
+├── llm/                   # LLM 集成
+│   ├── gemini.py          # Gemini Provider
+│   ├── openrouter.py      # OpenRouter Provider(OpenAI 兼容格式)
+│   ├── yescode.py         # Yescode Provider(Anthropic 原生 Messages API)
+│   └── prompts/           # Prompt 工具
+```
+
+### 职责划分
+
+| 模块 | 职责 |
+|-----|------|
+| **core/** | Agent 执行引擎 + 预设配置 |
+| **trace/** | 执行追踪 + 计划管理 |
+| **tools/** | 与外部世界交互(文件、命令、网络、浏览器) |
+| **memory/** | 跨会话知识(Skills、Experiences) |
+| **llm/** | LLM Provider 适配 |
+
+### 三层记忆模型
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Layer 3: Skills(技能库)                                     │
+│ - Markdown 文件,存储领域知识和能力描述                        │
+│ - 通过 skill 工具按需加载到对话历史                            │
+└─────────────────────────────────────────────────────────────┘
+                              ▲
+                              │ 归纳
+┌─────────────────────────────────────────────────────────────┐
+│ Layer 2: Experience(经验库)                                 │
+│ - 数据库存储,条件 + 规则 + 证据                              │
+│ - 向量检索,注入到 system prompt                              │
+└─────────────────────────────────────────────────────────────┘
+                              ▲
+                              │ 提取
+┌─────────────────────────────────────────────────────────────┐
+│ Layer 1: Trace(任务状态)                                    │
+│ - 当前任务的工作记忆                                          │
+│ - Trace + Messages 记录执行过程                               │
+│ - Goals 管理执行计划                                          │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### LLM Provider 适配
+
+#### 内部格式
+
+框架内部统一使用 OpenAI 兼容格式(`List[Dict]`)存储和传递消息。各 Provider 负责双向转换:
+
+| 方向 | 说明 |
+|------|------|
+| 入(LLM 响应 → 框架) | 提取 content、tool_calls、usage,转换为统一 Dict |
+| 出(框架 → LLM 请求) | OpenAI 格式消息列表 → 各 API 原生格式 |
+
+#### 工具消息分组
+
+存储层每个 tool result 独立一条 Message(OpenAI 格式最大公约数)。各 Provider 在出方向按 API 要求自行分组:
+
+| Provider | 分组方式 |
+|----------|---------|
+| OpenRouter | 无需分组(OpenAI 原生支持独立 tool 消息) |
+| Yescode | `_convert_messages_to_anthropic` 合并连续 tool 消息为单个 user message |
+| Gemini | `_convert_messages_to_gemini` 通过 buffer 合并连续 tool 消息 |
+
+#### 跨 Provider 续跑:tool_call_id 规范化
+
+不同 Provider 生成的 tool_call_id 格式不同(OpenAI: `call_xxx`,Anthropic: `toolu_xxx`,Gemini: 合成 `call_0`)。存储层按原样保存,不做规范化。
+
+跨 Provider 续跑时,出方向转换前检测历史中的 tool_call_id 格式,不兼容时统一重写为目标格式(保持 tool_use / tool_result 配对一致)。同格式跳过,零开销。Gemini 按 function name 匹配,无需重写。
+
+**实现**:`agent/llm/openrouter.py:_normalize_tool_call_ids`, `agent/llm/yescode.py:_normalize_tool_call_ids`
+
+---
+
+## 核心流程:Agent Loop
+
+### 参数分层
+
+```
+Layer 1: Infrastructure(基础设施,AgentRunner 构造时设置)
+  trace_store, memory_store, tool_registry, llm_call, skills_dir, utility_llm_call
+
+Layer 2: RunConfig(运行参数,每次 run 时指定)
+  ├─ 模型层:model, temperature, max_iterations, tools
+  └─ 框架层:trace_id, agent_type, uid, system_prompt, parent_trace_id, ...
+
+Layer 3: Messages(任务消息,OpenAI SDK 格式 List[Dict])
+  [{"role": "user", "content": "分析这张图的构图"}]
+```
+
+### RunConfig
+
+```python
+@dataclass
+class RunConfig:
+    # 模型层参数
+    model: str = "gpt-4o"
+    temperature: float = 0.3
+    max_iterations: int = 200
+    tools: Optional[List[str]] = None          # None = 全部已注册工具
+
+    # 框架层参数
+    agent_type: str = "default"
+    uid: Optional[str] = None
+    system_prompt: Optional[str] = None        # None = 从 skills 自动构建
+    skills: Optional[List[str]] = None         # 注入 system prompt 的 skill 名称列表;None = 按 preset 决定
+    enable_memory: bool = True
+    auto_execute_tools: bool = True
+    name: Optional[str] = None                 # 显示名称(空则由 utility_llm 自动生成)
+
+    # Trace 控制
+    trace_id: Optional[str] = None             # None = 新建
+    parent_trace_id: Optional[str] = None      # 子 Agent 专用
+    parent_goal_id: Optional[str] = None
+
+    # 续跑控制
+    after_sequence: Optional[int] = None       # 从哪条消息后续跑(message sequence)
+```
+
+**实现**:`agent/core/runner.py:RunConfig`
+
+### 三种运行模式
+
+通过 RunConfig 参数自然区分,统一入口 `run(messages, config)`:
+
+| 模式 | trace_id | after_sequence | messages 含义 | API 端点 |
+|------|----------|---------------|--------------|----------|
+| 新建 | None | - | 初始任务消息 | `POST /api/traces` |
+| 续跑 | 已有 ID | None 或 == head | 追加到末尾的新消息 | `POST /api/traces/{id}/run` |
+| 回溯 | 已有 ID | 主路径上 < head | 在插入点之后追加的新消息 | `POST /api/traces/{id}/run` |
+
+Runner 根据 `after_sequence` 与当前 `head_sequence` 的关系自动判断行为,前端无需指定模式。
+
+### 执行流程
+
+```python
+async def run(messages: List[Dict], config: RunConfig = None) -> AsyncIterator[Union[Trace, Message]]:
+    # Phase 1: PREPARE TRACE
+    #   无 trace_id → 创建新 Trace(生成 name,初始化 GoalTree)
+    #   有 trace_id + after_sequence 为 None 或 == head → 加载已有 Trace,状态置为 running
+    #   有 trace_id + after_sequence < head → 加载 Trace,执行 rewind(快照 GoalTree,重建,设 parent_sequence)
+    trace = await _prepare_trace(config)
+    yield trace
+
+    # Phase 2: BUILD HISTORY
+    #   从 head_sequence 沿 parent chain 回溯构建主路径消息
+    #   构建 system prompt(新建时注入 skills/experiences;续跑时复用已有)
+    #   追加 input messages(设置 parent_sequence 指向当前 head)
+    history, sequence = await _build_history(trace, messages, config)
+
+    # Phase 3: AGENT LOOP
+    for iteration in range(config.max_iterations):
+        # 周期性注入 GoalTree + Active Collaborators(每 10 轮)
+        if iteration % 10 == 0:
+            inject_context(goal_tree, collaborators)
+
+        response = await llm_call(messages=history, model=config.model, tools=tool_schemas)
+
+        # 按需自动创建 root goal(兜底)
+        # 记录 assistant Message
+        # 执行工具,记录 tool Messages
+        # 无 tool_calls 则 break
+
+    # Phase 4: COMPLETE
+    #   更新 Trace 状态 (completed/failed)
+    trace.status = "completed"
+    yield trace
+```
+
+**实现**:`agent/core/runner.py:AgentRunner`
+
+### 回溯(Rewind)
+
+回溯通过 `RunConfig(trace_id=..., after_sequence=N)` 触发(N 在主路径上且 < head_sequence),在 Phase 1 中执行:
+
+1. **验证插入点**:确保不截断在 assistant(tool_calls) 和 tool response 之间
+2. **快照 GoalTree**:将当前完整 GoalTree 存入 `events.jsonl`(rewind 事件的 `goal_tree_snapshot` 字段)
+3. **按时间重建 GoalTree**:以截断点消息的 `created_at` 为界,保留 `created_at <= cutoff_time` 的所有 goals(无论状态),丢弃 cutoff 之后创建的 goals,清空 `current_id`。将被保留的 `in_progress` goal 重置为 `pending`
+4. **设置 parent_sequence**:新消息的 `parent_sequence` 指向 rewind 点,旧消息自动脱离主路径
+5. **更新 Trace**:`head_sequence` 更新为新消息的 sequence,status 改回 running
+
+新消息的 sequence 从 `last_sequence + 1` 开始(全局递增,不复用)。旧消息无需标记 abandoned,通过消息树结构自然隔离。
+
+### 调用接口
+
+三种模式共享同一入口 `run(messages, config)`:
+
+```python
+# 新建
+async for item in runner.run(
+    messages=[{"role": "user", "content": "分析项目架构"}],
+    config=RunConfig(model="gpt-4o"),
+):
+    ...
+
+# 续跑:在已有 trace 末尾追加消息继续执行
+async for item in runner.run(
+    messages=[{"role": "user", "content": "继续"}],
+    config=RunConfig(trace_id="existing-trace-id"),
+):
+    ...
+
+# 回溯:从指定 sequence 处切断,插入新消息重新执行
+# after_sequence=5 表示新消息的 parent_sequence=5,从此处开始
+async for item in runner.run(
+    messages=[{"role": "user", "content": "换一个方案试试"}],
+    config=RunConfig(trace_id="existing-trace-id", after_sequence=5),
+):
+    ...
+
+# 重新生成:回溯后不插入新消息,直接基于已有消息重跑
+async for item in runner.run(
+    messages=[],
+    config=RunConfig(trace_id="existing-trace-id", after_sequence=5),
+):
+    ...
+```
+
+`after_sequence` 的值是 message 的 `sequence` 号,可通过 `GET /api/traces/{trace_id}/messages` 查看。如果指定的 sequence 是一条带 `tool_calls` 的 assistant 消息,系统会自动将截断点扩展到其所有对应的 tool response 之后(安全截断)。
+
+**停止运行**:
+
+```python
+# 停止正在运行的 Trace
+await runner.stop(trace_id)
+```
+
+调用后 agent loop 在下一个检查点退出,Trace 状态置为 `stopped`,同时保存当前 `head_sequence`(确保续跑时能正确加载完整历史)。
+
+**消息完整性保护(orphaned tool_call 修复)**:续跑加载历史时,`_build_history` 自动检测并修复 orphaned tool_calls(`_heal_orphaned_tool_calls`)。当 agent 被 stop/crash 中断时,可能存在 assistant 的 tool_calls 没有对应的 tool results(包括部分完成的情况:3 个 tool_call 只有 1 个 tool_result)。直接发给 LLM 会导致 400 错误。
+
+修复策略:为每个缺失的 tool_result **插入合成的中断通知**(而非裁剪 assistant 消息):
+
+| 工具类型 | 合成 tool_result 内容 |
+|----------|---------------------|
+| 普通工具 | 简短中断提示,建议重新调用 |
+| agent/evaluate | 结构化中断信息,包含 `sub_trace_id`、执行统计、`continue_from` 用法指引 |
+
+agent 工具的合成结果对齐正常返回值格式(含 `sub_trace_id` 字段),主 Agent 可直接使用 `agent(task=..., continue_from=sub_trace_id)` 续跑被中断的子 Agent。合成消息持久化存储,确保幂等。
+
+**实现**:`agent/core/runner.py:AgentRunner._heal_orphaned_tool_calls`
+
+- `run(messages, config)`:**核心方法**,流式返回 `AsyncIterator[Union[Trace, Message]]`
+- `run_result(messages, config, on_event=None)`:便利方法,内部消费 `run()`,返回结构化结果。`on_event` 回调可实时接收每个 Trace/Message 事件(用于调试时输出子 Agent 执行过程)。主要用于 `agent`/`evaluate` 工具内部
+
+### REST API
+
+#### 查询端点
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| GET  | `/api/traces` | 列出 Traces |
+| GET  | `/api/traces/{id}` | 获取 Trace 详情(含 GoalTree、Sub-Traces) |
+| GET  | `/api/traces/{id}/messages` | 获取 Messages(支持 mode=main_path/all) |
+| GET  | `/api/traces/running` | 列出正在运行的 Trace |
+| WS   | `/api/traces/{id}/watch` | 实时事件推送 |
+
+**实现**:`agent/trace/api.py`, `agent/trace/websocket.py`
+
+#### 控制端点
+
+需在 `api_server.py` 中配置 Runner。执行在后台异步进行,通过 WebSocket 监听进度。
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| POST | `/api/traces` | 新建 Trace 并执行 |
+| POST | `/api/traces/{id}/run` | 运行(统一续跑 + 回溯) |
+| POST | `/api/traces/{id}/stop` | 停止运行中的 Trace |
+| POST | `/api/traces/{id}/reflect` | 触发反思,从执行历史中提取经验 |
+
+```bash
+# 新建
+curl -X POST http://localhost:8000/api/traces \
+  -H "Content-Type: application/json" \
+  -d '{"messages": [{"role": "user", "content": "分析项目架构"}], "model": "gpt-4o"}'
+
+# 续跑(after_sequence 为 null 或省略)
+curl -X POST http://localhost:8000/api/traces/{trace_id}/run \
+  -d '{"messages": [{"role": "user", "content": "继续深入分析"}]}'
+
+# 回溯:从 sequence 5 处截断,插入新消息重新执行
+curl -X POST http://localhost:8000/api/traces/{trace_id}/run \
+  -d '{"after_sequence": 5, "messages": [{"role": "user", "content": "换一个方案"}]}'
+
+# 重新生成:回溯到 sequence 5,不插入新消息,直接重跑
+curl -X POST http://localhost:8000/api/traces/{trace_id}/run \
+  -d '{"after_sequence": 5, "messages": []}'
+
+# 停止
+curl -X POST http://localhost:8000/api/traces/{trace_id}/stop
+
+# 反思:追加反思 prompt 运行,结果追加到 experiences 文件
+curl -X POST http://localhost:8000/api/traces/{trace_id}/reflect \
+  -d '{"focus": "为什么第三步选择了错误的方案"}'
+```
+
+响应立即返回 `{"trace_id": "...", "status": "started"}`,通过 `WS /api/traces/{trace_id}/watch` 监听实时事件。
+
+**实现**:`agent/trace/run_api.py`
+
+#### 经验端点
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| GET  | `/api/experiences` | 读取经验文件内容 |
+
+**实现**:`agent/trace/run_api.py`
+
+---
+
+## 数据模型
+
+### Trace(任务执行)
+
+一次完整的 Agent 执行。所有 Agent(主、子、人类协助)都是 Trace。
+
+```python
+@dataclass
+class Trace:
+    trace_id: str
+    mode: Literal["call", "agent"]           # 单次调用 or Agent 模式
+
+    # Prompt 标识
+    prompt_name: Optional[str] = None
+
+    # Agent 模式特有
+    task: Optional[str] = None
+    agent_type: Optional[str] = None
+
+    # 父子关系(Sub-Trace 特有)
+    parent_trace_id: Optional[str] = None    # 父 Trace ID
+    parent_goal_id: Optional[str] = None     # 哪个 Goal 启动的
+
+    # 状态
+    status: Literal["running", "completed", "failed", "stopped"] = "running"
+
+    # 统计
+    total_messages: int = 0
+    total_tokens: int = 0                    # 总 tokens(prompt + completion)
+    total_prompt_tokens: int = 0
+    total_completion_tokens: int = 0
+    total_cost: float = 0.0
+    total_duration_ms: int = 0
+
+    # 进度追踪
+    last_sequence: int = 0                   # 最新 message 的 sequence(全局递增,不复用)
+    head_sequence: int = 0                   # 当前主路径的头节点 sequence(用于 build_llm_messages)
+    last_event_id: int = 0                   # 最新事件 ID(用于 WS 续传)
+
+    # 配置
+    uid: Optional[str] = None
+    model: Optional[str] = None              # 默认模型
+    tools: Optional[List[Dict]] = None       # 工具定义(OpenAI 格式)
+    llm_params: Dict[str, Any] = {}          # LLM 参数(temperature 等)
+    context: Dict[str, Any] = {}             # 元数据(含 collaborators 列表)
+
+    # 当前焦点
+    current_goal_id: Optional[str] = None
+
+    # 结果
+    result_summary: Optional[str] = None
+    error_message: Optional[str] = None
+
+    # 时间
+    created_at: datetime
+    completed_at: Optional[datetime] = None
+```
+
+**实现**:`agent/trace/models.py`
+
+### Goal(目标节点)
+
+计划中的一个目标,支持层级结构。单独存储于 `goal.json`。
+
+```python
+@dataclass
+class Goal:
+    id: str                                  # 内部 ID("1", "2"...)
+    description: str
+    reason: str = ""                         # 创建理由
+    parent_id: Optional[str] = None          # 父 Goal ID
+    type: GoalType = "normal"                # normal | agent_call
+    status: GoalStatus = "pending"           # pending | in_progress | completed | abandoned
+    summary: Optional[str] = None            # 完成/放弃时的总结
+
+    # agent_call 特有(启动 Sub-Trace)
+    sub_trace_ids: Optional[List[str]] = None
+    agent_call_mode: Optional[str] = None    # explore | delegate | evaluate
+    sub_trace_metadata: Optional[Dict] = None
+
+    # 统计
+    self_stats: GoalStats                    # 自身 Messages 统计
+    cumulative_stats: GoalStats              # 包含子孙的累计统计
+
+    created_at: datetime
+```
+
+**Goal 类型**:
+- `normal` - 普通目标,由 Agent 直接执行
+- `agent_call` - 通过 `agent`/`evaluate` 工具创建的目标,会启动 Sub-Trace
+
+**agent_call 类型的 Goal**:
+- 调用 `agent`/`evaluate` 工具时自动设置
+- `agent_call_mode` 记录使用的模式(explore/delegate/evaluate)
+- `sub_trace_ids` 记录创建的所有 Sub-Trace ID
+- 状态转换:pending → in_progress(Sub-Trace 启动)→ completed(Sub-Trace 完成)
+- `summary` 包含格式化的汇总结果(explore 模式会汇总所有分支)
+
+**Goal 操作**(通过 goal 工具):
+- `add` - 添加顶层目标
+- `under` - 在指定目标下添加子目标
+- `after` - 在指定目标后添加兄弟目标
+- `focus` - 切换焦点到指定目标
+- `done` - 完成当前目标(附带 summary)
+- `abandon` - 放弃当前目标(附带原因)
+
+**实现**:`agent/trace/goal_models.py`, `agent/trace/goal_tool.py`
+
+### Message(执行消息)
+
+对应 LLM API 的消息,每条 Message 关联一个 Goal。消息通过 `parent_sequence` 形成树结构。
+
+```python
+@dataclass
+class Message:
+    message_id: str                          # 格式:{trace_id}-{sequence:04d}
+    trace_id: str
+    role: Literal["system", "user", "assistant", "tool"]
+    sequence: int                            # 全局顺序(递增,不复用)
+    parent_sequence: Optional[int] = None    # 父消息的 sequence(构成消息树)
+    goal_id: Optional[str] = None            # 关联的 Goal ID(初始消息为 None,系统会按需自动创建 root goal 兜底)
+    description: str = ""                    # 系统自动生成的摘要
+    tool_call_id: Optional[str] = None
+    content: Any = None
+
+    # 统计
+    prompt_tokens: Optional[int] = None
+    completion_tokens: Optional[int] = None
+    cost: Optional[float] = None
+    duration_ms: Optional[int] = None
+
+    # LLM 响应信息(仅 role="assistant")
+    finish_reason: Optional[str] = None
+
+    created_at: datetime
+
+    # [已弃用] 由 parent_sequence 树结构替代
+    status: Literal["active", "abandoned"] = "active"
+    abandoned_at: Optional[datetime] = None
+```
+
+**消息树(Message Tree)**:
+
+消息通过 `parent_sequence` 形成树。主路径 = 从 `trace.head_sequence` 沿 parent chain 回溯到 root。
+
+```
+正常对话:1 → 2 → 3 → 4 → 5       (每条的 parent 指向前一条)
+Rewind 到 3:3 → 6(parent=3) → 7   (新主路径,4-5 自动脱离)
+压缩 1-3:   8(summary, parent=None) → 6 → 7  (summary 跳过被压缩的消息)
+反思分支:   5 → 9(reflect, parent=5) → 10     (侧枝,不在主路径上)
+```
+
+`build_llm_messages` = 从 head 沿 parent_sequence 链回溯到 root,反转后返回。
+
+Message 提供格式转换方法:
+- `to_llm_dict()` → OpenAI 格式 Dict(用于 LLM 调用)
+- `from_llm_dict(d, trace_id, sequence, goal_id)` → 从 OpenAI 格式创建 Message
+
+**实现**:`agent/trace/models.py`
+
+---
+
+## Agent 预设
+
+不同类型 Agent 的配置模板,控制工具权限和参数。
+
+```python
+@dataclass
+class AgentPreset:
+    allowed_tools: Optional[List[str]] = None  # None 表示允许全部
+    denied_tools: Optional[List[str]] = None   # 黑名单
+    max_iterations: int = 30
+    temperature: Optional[float] = None
+    skills: Optional[List[str]] = None         # 注入 system prompt 的 skill 名称列表;None = 加载全部
+    description: Optional[str] = None
+
+
+_DEFAULT_SKILLS = ["planning", "research", "browser"]
+
+AGENT_PRESETS = {
+    "default": AgentPreset(
+        allowed_tools=None,
+        max_iterations=30,
+        skills=_DEFAULT_SKILLS,
+        description="默认 Agent,拥有全部工具权限",
+    ),
+    "explore": AgentPreset(
+        allowed_tools=["read", "glob", "grep", "list_files"],
+        denied_tools=["write", "edit", "bash", "task"],
+        max_iterations=15,
+        skills=["planning"],
+        description="探索型 Agent,只读权限,用于代码分析",
+    ),
+    "analyst": AgentPreset(
+        allowed_tools=["read", "glob", "grep", "web_search", "webfetch"],
+        denied_tools=["write", "edit", "bash", "task"],
+        temperature=0.3,
+        max_iterations=25,
+        skills=["planning", "research"],
+        description="分析型 Agent,用于深度分析和研究",
+    ),
+}
+```
+
+**实现**:`agent/core/presets.py`
+
+**用户自定义**:项目级配置文件(如 `examples/how/presets.json`)可通过 `register_preset()` 注册额外预设。项目专用的 Agent 类型建议放在项目目录下,而非内置预设。
+
+---
+
+## 子 Trace 机制
+
+通过 `agent` 工具创建子 Agent 执行任务。`task` 参数为字符串时为单任务(delegate),为列表时并行执行多任务(explore)。支持通过 `messages` 参数预置消息,通过 `continue_from` 参数续跑已有 Sub-Trace。
+
+`agent` 工具负责创建 Sub-Trace 和初始化 GoalTree(因为需要设置自定义 context 元数据和命名规则),创建完成后将 `trace_id` 传给 `RunConfig`,由 Runner 接管后续执行。工具同时维护父 Trace 的 `context["collaborators"]` 列表。
+
+### 跨设备 Agent 通信
+
+支持跨设备的 Agent 间持续对话,通过远程 Trace ID 实现:
+
+**Trace ID 格式**:
+- 本地 Trace:`abc-123`
+- 远程 Trace:`agent://terminal-agent-456/abc-123`(协议 + Agent 地址 + 本地 ID)
+
+**使用方式**:
+```python
+# 调用远程 Agent
+result = agent(task="分析本地项目", agent_url="https://terminal-agent.local")
+# 返回: {"sub_trace_id": "agent://terminal-agent.local/abc-123"}
+
+# 续跑远程 Trace(持续对话)
+result2 = agent(
+    task="重点分析core模块",
+    continue_from="agent://terminal-agent.local/abc-123",
+    agent_url="https://terminal-agent.local"
+)
+```
+
+**实现**:`HybridTraceStore` 自动路由到本地或远程存储,远程访问通过 HTTP API 实现。
+
+**实现位置**:`agent/trace/hybrid_store.py`(规划中)
+
+### agent 工具
+
+```python
+@tool(description="创建 Agent 执行任务")
+async def agent(
+    task: Union[str, List[str]],
+    messages: Optional[Union[Messages, List[Messages]]] = None,
+    continue_from: Optional[str] = None,
+    agent_type: Optional[str] = None,
+    skills: Optional[List[str]] = None,
+    agent_url: Optional[str] = None,  # 远程 Agent 地址(跨设备)
+    context: Optional[dict] = None,
+) -> Dict[str, Any]:
+```
+
+**参数**:
+- `agent_type`: 子 Agent 类型,决定工具权限和默认 skills(对应 `AgentPreset` 名称)
+- `skills`: 覆盖 preset 默认值,显式指定注入 system prompt 的 skill 列表
+- `agent_url`: 远程 Agent 地址,用于跨设备调用(返回远程 Trace ID)
+- `continue_from`: 支持本地或远程 Trace ID
+
+**单任务(delegate)**:`task: str`
+- 创建单个 Sub-Trace
+- 完整工具权限(除 agent/evaluate 外,防止递归)
+- 支持 `continue_from` 续跑已有 Sub-Trace(本地或远程)
+- 支持 `messages` 预置上下文消息
+
+**多任务(explore)**:`task: List[str]`
+- 使用 `asyncio.gather()` 并行执行所有任务
+- 每个任务创建独立的 Sub-Trace
+- 只读工具权限(read_file, grep_content, glob_files, goal)
+- `messages` 支持 1D(共享)或 2D(per-agent)
+- 不支持 `continue_from`
+- 汇总所有分支结果返回
+
+### evaluate 工具
+
+```python
+@tool(description="评估目标执行结果是否满足要求")
+async def evaluate(
+    messages: Optional[Messages] = None,
+    target_goal_id: Optional[str] = None,
+    continue_from: Optional[str] = None,
+    context: Optional[dict] = None,
+) -> Dict[str, Any]:
+```
+
+- 代码自动从 GoalTree 注入目标描述(无需 criteria 参数)
+- 模型把执行结果和上下文放在 `messages` 中
+- `target_goal_id` 默认为当前 goal_id
+- 只读工具权限
+- 返回评估结论和改进建议
+
+### 消息类型别名
+
+定义在 `agent/trace/models.py`,用于工具参数和 runner/LLM API 接口:
+
+```python
+ChatMessage = Dict[str, Any]                          # 单条 OpenAI 格式消息
+Messages = List[ChatMessage]                          # 消息列表
+MessageContent = Union[str, List[Dict[str, str]]]     # content 字段(文本或多模态)
+```
+
+**实现位置**:`agent/tools/builtin/subagent.py`
+
+**详细文档**:[工具系统 - Agent/Evaluate 工具](../agent/docs/tools.md#agent-工具)
+
+### ask_human 工具
+
+创建阻塞式 Trace,等待人类通过 IM/邮件等渠道回复。
+
+**注意**:此功能规划中,暂未实现。
+
+---
+
+## Active Collaborators(活跃协作者)
+
+任务执行中与模型密切协作的实体(子 Agent 或人类),按 **与当前任务的关系** 分类,而非按 human/agent 分类:
+
+| | 持久存在(外部可查) | 任务内活跃(需要注入) |
+|---|---|---|
+| Agent | 专用 Agent(代码审查等) | 当前任务创建的子 Agent |
+| Human | 飞书通讯录 | 当前任务中正在对接的人 |
+
+### 数据模型
+
+活跃协作者存储在 `trace.context["collaborators"]`:
+
+```python
+{
+    "name": "researcher",            # 名称(模型可见)
+    "type": "agent",                 # agent | human
+    "trace_id": "abc-@delegate-001", # trace_id(agent 场景)
+    "status": "completed",           # running | waiting | completed | failed
+    "summary": "方案A最优",          # 最近状态摘要
+}
+```
+
+### 注入方式
+
+与 GoalTree 一同周期性注入(每 10 轮),渲染为 Markdown:
+
+```markdown
+## Active Collaborators
+- researcher [agent, completed]: 方案A最优
+- 谭景玉 [human, waiting]: 已发送方案确认,等待回复
+- coder [agent, running]: 正在实现特征提取模块
+```
+
+列表为空时不注入。
+
+### 维护
+
+各工具负责更新 collaborators 列表(通过 `context["store"]` 写入 trace.context):
+- `agent` 工具:创建/续跑子 Agent 时更新
+- `feishu` 工具:发送消息/收到回复时更新
+- Runner 只负责读取和注入
+
+**持久联系人/Agent**:通过工具按需查询(如 `feishu_get_contact_list`),不随任务注入。
+
+**实现**:`agent/core/runner.py:AgentRunner._build_context_injection`, `agent/tools/builtin/subagent.py`
+
+---
+
+## Context Injection Hooks(上下文注入钩子)
+
+### 概述
+
+Context Injection Hooks 是一个可扩展机制,允许外部模块(如 A2A IM、监控系统)向 Agent 的周期性上下文注入中添加自定义内容。
+
+### 设计理念
+
+- **周期性注入**:每 10 轮自动注入,不打断执行
+- **可扩展**:通过 hook 函数注册,无需修改 Runner 核心代码
+- **轻量提醒**:只注入摘要/提醒,详细内容通过工具获取
+- **LLM 自主决策**:由 LLM 决定何时响应提醒
+
+### 架构
+
+```
+Runner Loop (每 10 轮)
+    ↓
+_build_context_injection()
+    ├─ GoalTree (内置)
+    ├─ Active Collaborators (内置)
+    └─ Context Hooks (可扩展)
+         ├─ A2A IM Hook → "💬 3 条新消息"
+         ├─ Monitor Hook → "⚠️ 内存使用 85%"
+         └─ Custom Hook → 自定义内容
+    ↓
+注入为 system message
+    ↓
+LLM 看到提醒 → 决定是否调用工具
+```
+
+### Hook 接口
+
+```python
+# Hook 函数签名
+def context_hook(trace: Trace, goal_tree: Optional[GoalTree]) -> Optional[str]:
+    """
+    生成要注入的上下文内容
+
+    Args:
+        trace: 当前 Trace
+        goal_tree: 当前 GoalTree
+
+    Returns:
+        要注入的 Markdown 内容,None 表示无内容
+    """
+    return "## Custom Section\n\n内容..."
+```
+
+### 注册 Hook
+
+```python
+# 创建 Runner 时注册
+runner = AgentRunner(
+    llm_call=llm_call,
+    trace_store=trace_store,
+    context_hooks=[hook1, hook2, hook3]  # 按顺序注入
+)
+```
+
+### 实现
+
+**Runner 修改**:
+
+```python
+# agent/core/runner.py
+
+class AgentRunner:
+    def __init__(
+        self,
+        # ... 现有参数
+        context_hooks: Optional[List[Callable]] = None
+    ):
+        self.context_hooks = context_hooks or []
+
+    def _build_context_injection(
+        self,
+        trace: Trace,
+        goal_tree: Optional[GoalTree],
+    ) -> str:
+        """构建周期性注入的上下文(GoalTree + Active Collaborators + Hooks)"""
+        parts = []
+
+        # GoalTree(现有)
+        if goal_tree and goal_tree.goals:
+            parts.append(f"## Current Plan\n\n{goal_tree.to_prompt()}")
+            # ... focus 提醒
+
+        # Active Collaborators(现有)
+        collaborators = trace.context.get("collaborators", [])
+        if collaborators:
+            lines = ["## Active Collaborators"]
+            for c in collaborators:
+                # ... 现有逻辑
+            parts.append("\n".join(lines))
+
+        # Context Hooks(新增)
+        for hook in self.context_hooks:
+            try:
+                hook_content = hook(trace, goal_tree)
+                if hook_content:
+                    parts.append(hook_content)
+            except Exception as e:
+                logger.error(f"Context hook error: {e}")
+
+        return "\n\n".join(parts)
+```
+
+**实现位置**:`agent/core/runner.py:AgentRunner._build_context_injection`(待实现)
+
+### 示例:A2A IM Hook
+
+```python
+# agent/tools/builtin/a2a_im.py
+
+class A2AMessageQueue:
+    """A2A IM 消息队列"""
+
+    def __init__(self):
+        self._messages: List[Dict] = []
+
+    def push(self, message: Dict):
+        """Gateway 推送消息时调用"""
+        self._messages.append(message)
+
+    def pop_all(self) -> List[Dict]:
+        """check_messages 工具调用时清空"""
+        messages = self._messages
+        self._messages = []
+        return messages
+
+    def get_summary(self) -> Optional[str]:
+        """获取消息摘要(用于 context injection)"""
+        if not self._messages:
+            return None
+
+        count = len(self._messages)
+        latest = self._messages[-1]
+        from_agent = latest.get("from_agent_id", "unknown")
+
+        if count == 1:
+            return f"💬 来自 {from_agent} 的 1 条新消息(使用 check_messages 工具查看)"
+        else:
+            return f"💬 {count} 条新消息,最新来自 {from_agent}(使用 check_messages 工具查看)"
+
+
+def create_a2a_context_hook(message_queue: A2AMessageQueue):
+    """创建 A2A IM 的 context hook"""
+
+    def a2a_context_hook(trace: Trace, goal_tree: Optional[GoalTree]) -> Optional[str]:
+        """注入 A2A IM 消息提醒"""
+        summary = message_queue.get_summary()
+        if not summary:
+            return None
+
+        return f"## Messages\n\n{summary}"
+
+    return a2a_context_hook
+
+
+@tool(description="检查来自其他 Agent 的新消息")
+async def check_messages(ctx: ToolContext) -> ToolResult:
+    """检查并获取来自其他 Agent 的新消息"""
+    message_queue: A2AMessageQueue = ctx.context.get("a2a_message_queue")
+    if not message_queue:
+        return ToolResult(title="消息队列未初始化", output="")
+
+    messages = message_queue.pop_all()
+
+    if not messages:
+        return ToolResult(title="无新消息", output="")
+
+    # 格式化消息
+    lines = [f"收到 {len(messages)} 条新消息:\n"]
+    for i, msg in enumerate(messages, 1):
+        from_agent = msg.get("from_agent_id", "unknown")
+        content = msg.get("content", "")
+        conv_id = msg.get("conversation_id", "")
+        lines.append(f"{i}. 来自 {from_agent}")
+        lines.append(f"   对话 ID: {conv_id}")
+        lines.append(f"   内容: {content}")
+        lines.append("")
+
+    return ToolResult(
+        title=f"收到 {len(messages)} 条新消息",
+        output="\n".join(lines)
+    )
+```
+
+**实现位置**:`agent/tools/builtin/a2a_im.py`(待实现)
+
+### 配置示例
+
+```python
+# api_server.py
+
+from agent.tools.builtin.a2a_im import (
+    A2AMessageQueue,
+    create_a2a_context_hook,
+    check_messages
+)
+
+# 创建消息队列
+message_queue = A2AMessageQueue()
+
+# 创建 context hook
+a2a_hook = create_a2a_context_hook(message_queue)
+
+# 创建 Runner 时注入 hook
+runner = AgentRunner(
+    llm_call=llm_call,
+    trace_store=trace_store,
+    context_hooks=[a2a_hook]
+)
+
+# 注册 check_messages 工具
+tool_registry.register(check_messages)
+
+# 启动 Gateway webhook 端点
+@app.post("/webhook/a2a-messages")
+async def receive_a2a_message(message: dict):
+    """接收来自 Gateway 的消息"""
+    message_queue.push(message)
+    return {"status": "received"}
+```
+
+### 注入效果
+
+```markdown
+## Current Plan
+1. [in_progress] 分析代码架构
+   1.1. [completed] 读取项目结构
+   1.2. [in_progress] 分析核心模块
+
+## Active Collaborators
+- researcher [agent, completed]: 已完成调研
+
+## Messages
+💬 来自 code-reviewer 的 1 条新消息(使用 check_messages 工具查看)
+```
+
+### 其他应用场景
+
+**监控告警**:
+
+```python
+def create_monitor_hook(monitor):
+    def monitor_hook(trace, goal_tree):
+        alerts = monitor.get_alerts()
+        if not alerts:
+            return None
+        return f"## System Alerts\n\n⚠️ {len(alerts)} 条告警(使用 check_alerts 工具查看)"
+    return monitor_hook
+```
+
+**定时提醒**:
+
+```python
+def create_timer_hook(timer):
+    def timer_hook(trace, goal_tree):
+        if timer.should_remind():
+            return "## Reminder\n\n⏰ 任务已执行 30 分钟,建议检查进度"
+        return None
+    return timer_hook
+```
+
+**实现位置**:各模块自行实现 hook 函数
+
+---
+
+## Active Collaborators(活跃协作者)
+
+任务执行中与模型密切协作的实体(子 Agent 或人类),按 **与当前任务的关系** 分类,而非按 human/agent 分类:
+
+| | 持久存在(外部可查) | 任务内活跃(需要注入) |
+|---|---|---|
+| Agent | 专用 Agent(代码审查等) | 当前任务创建的子 Agent |
+| Human | 飞书通讯录 | 当前任务中正在对接的人 |
+
+### 数据模型
+
+活跃协作者存储在 `trace.context["collaborators"]`:
+
+```python
+{
+    "name": "researcher",            # 名称(模型可见)
+    "type": "agent",                 # agent | human
+    "trace_id": "abc-@delegate-001", # trace_id(agent 场景)
+    "status": "completed",           # running | waiting | completed | failed
+    "summary": "方案A最优",          # 最近状态摘要
+}
+```
+
+### 注入方式
+
+与 GoalTree 一同周期性注入(每 10 轮),渲染为 Markdown:
+
+```markdown
+## Active Collaborators
+- researcher [agent, completed]: 方案A最优
+- 谭景玉 [human, waiting]: 已发送方案确认,等待回复
+- coder [agent, running]: 正在实现特征提取模块
+```
+
+列表为空时不注入。
+
+### 维护
+
+各工具负责更新 collaborators 列表(通过 `context["store"]` 写入 trace.context):
+- `agent` 工具:创建/续跑子 Agent 时更新
+- `feishu` 工具:发送消息/收到回复时更新
+- Runner 只负责读取和注入
+
+**持久联系人/Agent**:通过工具按需查询(如 `feishu_get_contact_list`),不随任务注入。
+
+**实现**:`agent/core/runner.py:AgentRunner._build_context_injection`, `agent/tools/builtin/subagent.py`
+
+---
+
+## 工具系统
+
+### 核心概念
+
+```python
+@tool()
+async def my_tool(arg: str, ctx: ToolContext) -> ToolResult:
+    return ToolResult(
+        title="Success",
+        output="Result content",
+        long_term_memory="Short summary"  # 可选:压缩后保留的摘要
+    )
+```
+
+| 类型 | 作用 |
+|------|------|
+| `@tool` | 装饰器,自动注册工具并生成 Schema |
+| `ToolResult` | 工具执行结果,支持双层记忆 |
+| `ToolContext` | 工具执行上下文,依赖注入 |
+
+### 工具分类
+
+| 目录 | 工具 | 说明 |
+|-----|------|------|
+| `trace/` | goal | Agent 内部计划管理 |
+| `builtin/` | agent, evaluate | 子 Agent 创建与评估 |
+| `builtin/file/` | read, write, edit, glob, grep | 文件操作 |
+| `builtin/browser/` | browser actions | 浏览器自动化 |
+| `builtin/` | bash, sandbox, search, webfetch, skill, ask_human | 其他工具 |
+
+### 双层记忆管理
+
+大输出(如网页抓取)只传给 LLM 一次,之后用摘要替代:
+
+```python
+ToolResult(
+    output="<10K tokens 的完整内容>",
+    long_term_memory="Extracted 10000 chars from amazon.com",
+    include_output_only_once=True
+)
+```
+
+**详细文档**:[工具系统](../agent/docs/tools.md)
+
+---
+
+## Skills 系统
+
+### 分类
+
+| 类型 | 加载位置 | 加载时机 |
+|------|---------|---------|
+| **内置 Skill** | System Prompt | Agent 启动时自动注入 |
+| **项目 Skill** | System Prompt | Agent 启动时按 preset/call-site 过滤后注入 |
+| **普通 Skill** | 对话消息 | 模型调用 `skill` 工具时 |
+
+### 目录结构
+
+```
+agent/memory/skills/         # 内置 Skills(始终加载)
+├── planning.md              # 计划与 Goal 工具使用
+├── research.md              # 搜索与内容研究
+└── browser.md               # 浏览器自动化
+
+./skills/                    # 项目自定义 Skills
+```
+
+### Skills 过滤(call-site 选择)
+
+不同 Agent 类型所需的 skills 不同。过滤优先级:
+
+1. `agent()` 工具的 `skills` 参数(显式指定,最高优先级)
+2. `AgentPreset.skills`(preset 默认值)
+3. `None`(加载全部,向后兼容)
+
+示例:调用子 Agent 时只注入解构相关 skill:
+```python
+agent(task="...", agent_type="deconstruct", skills=["planning", "deconstruct"])
+```
+
+**实现**:`agent/memory/skill_loader.py`
+
+**详细文档**:[Skills 使用指南](../agent/docs/skills.md)
+
+---
+
+## Experiences 系统
+
+从执行历史中提取的经验规则,用于指导未来任务。
+
+### 存储规范
+
+经验以 Markdown 文件存储(默认 `./.cache/experiences.md`),人类可读、可编辑、可版本控制。
+
+文件格式:
+
+```markdown
+---
+id: ex_001
+trace_id: trace-xxx
+category: tool_usage
+tags: {state: ["large_file", "dirty_repo"], intent: ["batch_edit", "safe_modify"]}
+metrics: {helpful: 12, harmful: 0}
+created_at: 2026-02-12 15:30
+---
+
+---
+id: ex_002
+...
+```
+---
+
+
+### 反思机制(Reflect)
+
+通过 POST /api/traces/{id}/reflect 触发,旨在将原始执行历史提炼为可复用的知识。
+    1. 分叉反思:在 trace 末尾追加 user message(含反思与打标 Prompt),作为侧枝执行。
+    2. 结构化生成:
+        ·归类:将经验分配至 tool_usage(工具)、logic_flow(逻辑)、environment(环境)等。
+        ·打标:提取 state(环境状态)与 intent(用户意图)语义标签。
+        ·量化:初始 helpful 设为 1。
+    3. 持久化:将带有元数据的 Markdown 块追加至 experiences.md。
+
+实现:agent/trace/run_api.py:reflect_trace
+
+### 语义注入与匹配流程
+新建 Trace 时,Runner 采用“分析-检索-注入”三阶段策略,实现精准经验推荐。
+    1. 意图预分析
+    Runner 调用 utility_llm 对初始任务进行语义提取:
+        -输入:"优化这个项目的 Docker 构建速度"
+        -输出:{state: ["docker", "ci"], intent: ["optimization"]}
+    2. 语义检索
+        在 _load_experiences 中根据标签进行语义匹配(优先匹配 intent,其次是 state),筛选出相关度最高的 Top-K 条经验。
+    3. 精准注入
+        将匹配到的经验注入第一条 user message 末尾:
+```python
+# _build_history 中(仅新建模式):
+if not config.trace_id:
+    relevant_ex = self.experience_retriever.search(task_tags)
+    if relevant_ex:
+        formatted_ex = "\n".join([f"- [{e.id}] {e.content} (Helpful: {e.helpful})" for e in relevant_ex])
+        first_user_msg["content"] += f"\n\n## 参考经验\n\n{formatted_ex}"
+```
+实现:agent/core/runner.py:AgentRunner._build_history
+
+### 经验获取工具
+不再仅限于启动时自动注入,而是通过内置工具供 Agent 在需要时主动调用。当执行结果不符合预期或进入未知领域时,Agent 应优先使用此工具。
+工具定义:
+
+```python
+@tool(description="根据当前任务状态和意图,从经验库中检索相关的历史经验")
+async def get_experience(
+    intent: Optional[str] = None, 
+    state: Optional[str] = None
+) -> Dict[str, Any]:
+    """
+    参数:
+        intent: 想要达成的目标意图 (如 "optimization", "debug")
+        state: 当前环境或遇到的问题状态 (如 "docker_build_fail", "permission_denied")
+    """
+```
+实现: agent/tools/builtin/experience.py
+
+- 语义匹配与应用流程
+    当 Agent 调用 get_experience 时,系统执行以下逻辑:
+    1. 语义检索:根据传入的 intent 或 state 标签,在 experiences.md 中进行匹配。匹配权重:intent > state > helpful 评分。
+    2. 动态注入:工具返回匹配到的 Top-K 条经验(含 ID 和内容)。
+    3. 策略应用:Agent 接收到工具返回的经验后,需在后续 thought 中声明所选用的策略 ID(如 [ex_001]),并据此调整 goal_tree 或工具调用序列。
+
+## Context 压缩
+
+### 两级压缩策略
+
+#### Level 1:GoalTree 过滤(确定性,零成本)
+
+每轮 agent loop 构建 `llm_messages` 时自动执行:
+- 始终保留:system prompt、第一条 user message(含 GoalTree 精简视图)、当前 focus goal 的消息
+- 跳过 completed/abandoned goals 的消息(信息已在 GoalTree summary 中)
+- 通过 Message Tree 的 parent_sequence 实现跳过
+
+大多数情况下 Level 1 足够。
+
+#### Level 2:LLM 总结(仅在 Level 1 后仍超限时触发)
+
+触发条件:Level 1 之后 token 数仍超过阈值(默认 `max_tokens × 0.8`)。
+
+流程:
+1. **经验提取**:先在消息列表末尾追加反思 prompt → 主模型回复 → 追加到 `./.cache/experiences.md`。反思消息为侧枝(parent_sequence 分叉,不在主路径上)
+2. **压缩**:在消息列表末尾追加压缩 prompt(含 GoalTree 完整视图) → 主模型回复 → summary 存为新消息,其 `parent_sequence` 跳过被压缩的范围
+
+### GoalTree 双视图
+
+`to_prompt()` 支持两种模式:
+- `include_summary=False`(默认):精简视图,用于日常周期性注入
+- `include_summary=True`:含所有 completed goals 的 summary,用于 Level 2 压缩时提供上下文
+
+### 压缩存储
+
+- 原始消息永远保留在 `messages/`
+- 压缩 summary 作为普通 Message 存储
+- 通过 `parent_sequence` 树结构实现跳过,无需 compression events 或 skip list
+- Rewind 到压缩区域内时,summary 脱离主路径,原始消息自动恢复
+
+**实现**:`agent/trace/compaction.py`, `agent/trace/goal_models.py`
+
+**详细文档**:[Context 管理](./context-management.md)
+
+---
+
+## 存储接口
+
+```python
+class TraceStore(Protocol):
+    async def create_trace(self, trace: Trace) -> None: ...
+    async def get_trace(self, trace_id: str) -> Trace: ...
+    async def update_trace(self, trace_id: str, **updates) -> None: ...
+    async def add_message(self, message: Message) -> None: ...
+    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 append_event(self, trace_id: str, event_type: str, payload: Dict) -> int: ...
+```
+
+`get_main_path_messages` 从 `head_sequence` 沿 `parent_sequence` 链回溯,返回主路径上的有序消息列表。
+
+**实现**:
+- 协议定义:`agent/trace/protocols.py`
+- 本地存储:`agent/trace/store.py:FileSystemTraceStore`
+- 远程存储:`agent/trace/remote_store.py:RemoteTraceStore`(规划中)
+- 混合存储:`agent/trace/hybrid_store.py:HybridTraceStore`(规划中)
+
+### 跨设备存储
+
+**HybridTraceStore** 根据 Trace ID 自动路由到本地或远程存储:
+
+| Trace ID 格式 | 存储位置 | 访问方式 |
+|--------------|---------|---------|
+| `abc-123` | 本地文件系统 | `FileSystemTraceStore` |
+| `agent://host/abc-123` | 远程 Agent | HTTP API(`RemoteTraceStore`) |
+
+**RemoteTraceStore** 通过 HTTP API 访问远程 Trace:
+- `GET /api/traces/{trace_id}` - 获取 Trace 元数据
+- `GET /api/traces/{trace_id}/messages` - 获取消息历史
+- `POST /api/traces/{trace_id}/run` - 续跑(追加消息并执行)
+
+**认证**:通过 API Key 认证,配置在 `config/agents.yaml`。
+
+**实现位置**:`agent/trace/hybrid_store.py`, `agent/trace/remote_store.py`(规划中)
+
+### 存储结构
+
+```
+.trace/
+├── {trace_id}/
+│   ├── meta.json        # Trace 元数据(含 tools 定义)
+│   ├── goal.json        # GoalTree(mission + goals 列表)
+│   ├── events.jsonl     # 事件流(goal 变更、sub_trace 生命周期等)
+│   └── messages/        # Messages
+│       ├── {trace_id}-0001.json
+│       └── ...
+│
+└── {trace_id}@explore-{序号}-{timestamp}-001/  # 子 Trace
+    └── ...
+```
+
+**events.jsonl 说明**:
+- 记录 Trace 执行过程中的关键事件
+- 每行一个 JSON 对象,包含 event_id、event 类型、时间戳等
+- 主要事件类型:goal_added, goal_updated, sub_trace_started, sub_trace_completed, rewind
+- 用于实时监控和历史回放
+
+**Sub-Trace 目录命名**:
+- Explore: `{parent}@explore-{序号:03d}-{timestamp}-001`
+- Delegate: `{parent}@delegate-{timestamp}-001`
+- Evaluate: `{parent}@evaluate-{timestamp}-001`
+
+**meta.json 示例**:
+```json
+{
+  "trace_id": "0415dc38-...",
+  "mode": "agent",
+  "task": "分析代码结构",
+  "agent_type": "default",
+  "status": "running",
+  "model": "google/gemini-2.5-flash",
+  "tools": [...],
+  "llm_params": {"temperature": 0.3},
+  "context": {
+    "collaborators": [
+      {"name": "researcher", "type": "agent", "trace_id": "...", "status": "completed", "summary": "方案A最优"}
+    ]
+  },
+  "current_goal_id": "3"
+}
+```
+
+---
+
+## 设计决策
+
+详见 [设计决策文档](./decisions.md)
+
+**核心决策**:
+
+1. **所有 Agent 都是 Trace** - 主 Agent、子 Agent、人类协助统一为 Trace,通过 `parent_trace_id` 和 `spawn_tool` 区分
+
+2. **trace/ 模块统一管理执行状态** - 合并原 execution/ 和 goal/,包含计划管理和 Agent 内部控制工具
+
+3. **tools/ 专注外部交互** - 文件、命令、网络、浏览器等与外部世界的交互
+
+4. **Agent 预设替代 Sub-Agent 配置** - 通过 `core/presets.py` 定义不同类型 Agent 的工具权限和参数
+
+---
+
+## 相关文档
+
+| 文档 | 内容 |
+|-----|------|
+| [Context 管理](./context-management.md) | Goals、压缩、Plan 注入策略 |
+| [工具系统](../agent/docs/tools.md) | 工具定义、注册、双层记忆 |
+| [Skills 指南](../agent/docs/skills.md) | Skill 分类、编写、加载 |
+| [多模态支持](../agent/docs/multimodal.md) | 图片、PDF 处理 |
+| [知识管理](./knowledge.md) | 知识结构、检索、提取机制 |
+| [Scope 设计](./scope-design.md) | 知识可见性和权限控制 |
+| [Agent 设计决策](../agent/docs/decisions.md) | Agent Core 架构决策记录 |
+| [Gateway 设计决策](../gateway/docs/decisions.md) | Gateway 架构决策记录 |
+| [组织级概览](../gateway/docs/enterprise/overview.md) | 组织级 Agent 系统架构和规划 |
+| [Enterprise 实现](../gateway/docs/enterprise/implementation.md) | 认证、审计、多租户技术实现 |
+| [测试指南](./testing.md) | 测试策略和命令 |
+| [A2A 协议调研](./research/a2a-protocols.md) | 行业 A2A 通信协议和框架对比 |
+| [A2A 跨设备通信](./research/a2a-cross-device.md) | 跨设备 Agent 通信方案(内部) |
+| [A2A Trace 存储](./research/a2a-trace-storage.md) | 跨设备 Trace 存储方案详细设计 |
+| [MAMP 协议](./research/a2a-mamp-protocol.md) | 与外部 Agent 系统的通用交互协议 |
+| [A2A IM 系统](./a2a-im.md) | Agent 即时通讯系统架构和实现 |
+| [Gateway 架构](../gateway/docs/architecture.md) | Gateway 三层架构和设计决策 |
+| [Gateway 部署](../gateway/docs/deployment.md) | Gateway 部署模式和配置 |
+| [Gateway API](../gateway/docs/api.md) | Gateway API 完整参考 |

+ 0 - 0
docs/decisions.md → agent/docs/decisions.md


+ 203 - 0
agent/docs/knowledge.md

@@ -0,0 +1,203 @@
+- 产生知识时区分:人、场景(例如飞书群,可以对应到tags)、业务(还有子业务)
+
+**知识结构**(单条知识)
+    id: 知识唯一标识
+    task: 任务描述,什么场景、在做什么
+
+    type: 知识类型(单选)
+        user_profile:用户画像(偏好、习惯、背景、约束)
+        strategy:具体执行经验,从执行过程中反思获得
+        tool:工具名称,简介,使用方法,优缺点对比,代码示例
+        usecase:用户背景,采用方案,实现步骤,问题,效果
+        definition:概念定义,技术原理,应用场景
+        plan:流程步骤,决策点,某个问题的总结方法论
+
+    tags: 业务标签(可有多个)
+        category: 子类别(如 preference, background, habit, constraint)
+        domain: 领域(如 coding_style, architecture)
+        其他自定义标签
+        scenario
+
+    scopes: 可见范围(可有多个,格式:{entity_type}:{entity_id})
+        user:{user_id}:用户级(个人可见)
+        agent:{agent_id}:Agent 级(特定 Agent 可见)
+        project:{project_id}:项目级(项目组可见)
+        team:{team_id}:团队级(部门可见)
+        org:{org_id}:组织级(全公司可见)
+        public:公开(所有人可见)
+
+    owner: 所有者(格式:{entity_type}:{entity_id},唯一)
+        谁创建的,谁有权修改/删除
+
+    content:
+        基于类型的具体内容,相对完整的一条知识
+
+    source:
+        name: 工具/资源名称(若有)
+        category:paper/exp/skill/book...
+        urls:知识来源的网站
+        agent_id:调用知识查询的agent名称
+        submitted_by:创建本条目的agent负责人名称
+        timestamp: 知识生成时的时间戳
+        trace_id: 来源 Trace ID(若有)
+
+    eval:基于使用反馈
+        helpful: 好用的次数
+        harmful:不好用的次数
+        confidence: 置信度(0-1)
+        helpful_history: [(query+trace_id+outcome), ]用于记录反馈时的调用总结
+        harmful_history: []
+
+知识检索机制
+    检索流程
+        1. 构建可见范围
+            根据执行上下文(user_id, agent_id, project_id, team_id, org_id)
+            构建用户的所有可见 scopes:
+                - user:{user_id}
+                - agent:{agent_id}
+                - project:{project_id}
+                - team:{team_id}
+                - org:{org_id}
+                - public
+
+        2. 向量检索 + 过滤
+            查询条件:
+                - 语义匹配(向量检索)
+                - scopes 过滤(知识的 scopes 与用户的 visible_scopes 有交集)
+                - type 过滤(可选,按知识类型过滤)
+
+        3. 按优先级排序
+            优先级:user > project > agent > team > org > public
+            取知识的 scopes 中优先级最高的进行排序
+
+    触发时机
+        - Agent 启动时:自动检索相关知识
+        - Goal focus 时:检索与当前目标相关的知识
+        - 主动调用:通过 get_knowledge 工具主动查询
+
+    实现位置(规划)
+        - agent/memory/knowledge_store.py: 知识存储接口
+        - agent/memory/knowledge_retriever.py: 检索逻辑
+        - agent/tools/builtin/knowledge.py: get_knowledge 工具
+知识提取机制
+    触发时机
+        - 主动表达:用户明确表达偏好、纠正、提供背景信息
+        - 任务完成:任务完成后的反思总结
+        - 压缩消息:在压缩消息节点提取经验
+        - 用户反馈:用户对结果的 helpful/harmful 评价
+
+    提取流程
+        1. 识别知识类型
+            根据内容判断是 user_profile / strategy / tool / definition / plan / usecase
+
+        2. 结构化提取
+            - 提取核心内容
+            - 生成标签(category, domain 等)
+            - 确定 scopes(基于执行上下文)
+            - 设置 owner 和 visibility
+
+        3. 持久化存储
+            - 存入知识库(向量数据库 + 元数据)
+            - 记录来源(trace_id, agent_id, timestamp)
+            - 初始化评价(helpful=1, confidence=0.5)
+
+    实现位置(规划)
+        - agent/trace/compaction.py: 压缩时提取经验
+        - agent/tools/builtin/knowledge.py: save_knowledge 工具
+        - agent/memory/knowledge_extractor.py: 知识提取逻辑
+用户画像特殊处理
+    用户画像是一种特殊的知识类型(type=user_profile),具有以下特点:
+
+    获取方式
+        - 持续性:在整个对话过程中持续积累
+        - 多触发点:主动表达 > 任务完成 > 周期性总结
+        - 渐进式:逐步完善,置信度逐渐提高
+
+    存储特点
+        - type: user_profile
+        - scopes: 通常包含 user:{user_id},可能包含 project:{project_id}
+        - owner: user:{user_id}
+        - tags.category: preference | background | habit | constraint
+
+    检索优先级
+        - 在 Agent 启动时自动加载
+        - 优先级高于其他类型的知识
+        - 按 confidence 和 helpful 评分排序
+
+    示例
+        {
+            "id": "profile_001",
+            "type": "user_profile",
+            "task": "用户编码风格偏好",
+            "tags": {
+                "category": "preference",
+                "domain": "coding_style"
+            },
+            "scopes": ["user:123", "project:456"],
+            "owner": "user:123",
+            "visibility": "shared",
+            "content": "用户偏好使用 TypeScript 而非 JavaScript,注重类型安全",
+            "source": {
+                "agent_id": "general_assistant",
+                "trace_id": "trace-xxx",
+                "timestamp": "2026-03-03"
+            },
+            "eval": {
+                "helpful": 5,
+                "harmful": 0,
+                "confidence": 0.9
+            }
+        }
+存储结构(规划)
+    数据库表结构
+        CREATE TABLE knowledge (
+            id TEXT PRIMARY KEY,
+            type TEXT NOT NULL,              -- user_profile | strategy | tool | ...
+            task TEXT,
+            tags JSON,
+            scopes JSON,                     -- ["user:123", "project:456", ...]
+            owner TEXT NOT NULL,             -- "user:123" | "agent:xxx"
+            visibility TEXT,                 -- private | shared | org | public
+            content TEXT,
+            source JSON,
+            eval JSON,
+            embedding VECTOR(1536),          -- 向量检索
+            created_at TIMESTAMP,
+            updated_at TIMESTAMP
+        );
+
+        -- 索引
+        CREATE INDEX idx_type ON knowledge(type);
+        CREATE INDEX idx_owner ON knowledge(owner);
+        CREATE INDEX idx_visibility ON knowledge(visibility);
+        CREATE INDEX idx_scopes ON knowledge USING GIN(scopes);
+
+    文件系统结构
+        /workspace/knowledge/
+        ├── global/                   # 全局知识
+        │   ├── tools.json
+        │   ├── definitions.json
+        │   └── plans.json
+        │
+        ├── agents/                   # Agent 经验
+        │   ├── general_assistant/
+        │   ├── crawler_ops/
+        │   └── ...
+        │
+        └── users/                    # 用户画像
+            ├── user_123/
+            │   ├── profile.json      # 基础信息
+            │   ├── preferences.json  # 偏好
+            │   ├── habits.json       # 习惯
+            │   └── constraints.json  # 约束
+            └── ...
+
+TODO
+    1. 实现知识存储接口(agent/memory/knowledge_store.py)
+    2. 实现知识检索逻辑(agent/memory/knowledge_retriever.py)
+    3. 实现 get_knowledge 工具(agent/tools/builtin/knowledge.py)
+    4. 实现 save_knowledge 工具(agent/tools/builtin/knowledge.py)
+    5. 在 Agent 启动时集成知识检索
+    6. 在 Goal focus 时集成知识检索
+    7. 在压缩消息时集成知识提取
+    8. 实现用户画像的特殊处理逻辑

+ 0 - 0
docs/multimodal.md → agent/docs/multimodal.md


+ 331 - 0
agent/docs/scope-design.md

@@ -0,0 +1,331 @@
+# Scope 设计文档
+
+## 概述
+
+Scope 系统用于控制知识的可见性和访问权限。采用**灵活的标签系统**,而非固定的层级结构。
+
+## 核心原则
+
+1. **知识类型与可见性分离**
+   - `type` 字段表示知识类型(What)
+   - `scopes` 字段表示可见范围(Who)
+
+2. **多 Scope 支持**
+   - 一条知识可以有多个 scope 标签
+   - 支持灵活的共享关系
+
+3. **明确的所有权**
+   - `owner` 字段表示唯一所有者
+   - 只有所有者有权修改/删除
+
+## Scope 格式
+
+```
+格式:{entity_type}:{entity_id}
+
+示例:
+- user:123
+- agent:general_assistant
+- project:456
+- team:frontend
+- org:company
+- public
+```
+
+## Scope 类型
+
+| Scope 类型 | 格式 | 说明 | 示例 |
+|-----------|------|------|------|
+| 用户级 | `user:{user_id}` | 用户个人可见 | `user:123` |
+| Agent 级 | `agent:{agent_id}` | 特定 Agent 可见 | `agent:crawler_ops` |
+| 项目级 | `project:{project_id}` | 项目组可见 | `project:456` |
+| 团队级 | `team:{team_id}` | 部门可见 | `team:frontend` |
+| 组织级 | `org:{org_id}` | 全公司可见 | `org:company` |
+| 公开 | `public` | 所有人可见 | `public` |
+
+## 数据结构
+
+```json
+{
+  "id": "knowledge_001",
+
+  "type": "user_profile",
+
+  "scopes": [
+    "user:123",
+    "project:456"
+  ],
+
+  "owner": "user:123",
+
+  "visibility": "shared",
+
+  "content": "...",
+  "tags": {...},
+  "source": {...},
+  "eval": {...}
+}
+```
+
+## 字段说明
+
+### type(知识类型)
+
+- `user_profile`:用户画像
+- `strategy`:执行经验
+- `tool`:工具知识
+- `usecase`:用例
+- `definition`:概念定义
+- `plan`:方法论
+
+### scopes(可见范围)
+
+- 数组类型,可包含多个 scope
+- 知识对所有 scopes 中的实体可见
+- 检索时匹配:知识的 scopes 与用户的 visible_scopes 有交集
+
+### owner(所有者)
+
+- 字符串类型,格式同 scope
+- 唯一所有者,有权修改/删除
+- 通常是创建者
+
+### visibility(可见性级别)
+
+快速过滤标签,用于 UI 展示和简单查询:
+
+- `private`:私有(仅所有者)
+- `shared`:共享(多个实体)
+- `org`:组织级
+- `public`:公开
+
+## 使用场景
+
+### 场景 1:用户的私有偏好
+
+```json
+{
+  "type": "user_profile",
+  "scopes": ["user:123"],
+  "owner": "user:123",
+  "visibility": "private",
+  "content": "用户偏好使用 TypeScript"
+}
+```
+
+### 场景 2:项目组共享的经验
+
+```json
+{
+  "type": "strategy",
+  "scopes": ["project:456"],
+  "owner": "agent:crawler_ops",
+  "visibility": "shared",
+  "content": "爬虫反爬策略:使用代理池 + 随机 UA"
+}
+```
+
+### 场景 3:跨项目的用户偏好
+
+```json
+{
+  "type": "user_profile",
+  "scopes": ["user:123", "project:456", "project:789"],
+  "owner": "user:123",
+  "visibility": "shared",
+  "content": "用户在多个项目中都偏好使用 React"
+}
+```
+
+### 场景 4:Agent 间共享的工具知识
+
+```json
+{
+  "type": "tool",
+  "scopes": [
+    "agent:crawler_ops",
+    "agent:content_library",
+    "agent:general_assistant"
+  ],
+  "owner": "agent:crawler_ops",
+  "visibility": "shared",
+  "content": "Selenium 使用技巧:headless 模式配置"
+}
+```
+
+### 场景 5:组织级公开知识
+
+```json
+{
+  "type": "definition",
+  "scopes": ["org:company"],
+  "owner": "org:company",
+  "visibility": "org",
+  "content": "公司技术栈:React + TypeScript + Node.js"
+}
+```
+
+### 场景 6:完全公开的知识
+
+```json
+{
+  "type": "tool",
+  "scopes": ["public"],
+  "owner": "org:company",
+  "visibility": "public",
+  "content": "Git 常用命令速查表"
+}
+```
+
+## 检索逻辑
+
+### 构建可见范围
+
+```python
+def build_visible_scopes(context):
+    """
+    根据执行上下文构建用户的所有可见 scopes
+
+    context = {
+        "user_id": "123",
+        "agent_id": "general_assistant",
+        "project_id": "456",
+        "team_id": "frontend",
+        "org_id": "company"
+    }
+    """
+    scopes = []
+
+    if context.get("user_id"):
+        scopes.append(f"user:{context['user_id']}")
+
+    if context.get("agent_id"):
+        scopes.append(f"agent:{context['agent_id']}")
+
+    if context.get("project_id"):
+        scopes.append(f"project:{context['project_id']}")
+
+    if context.get("team_id"):
+        scopes.append(f"team:{context['team_id']}")
+
+    if context.get("org_id"):
+        scopes.append(f"org:{context['org_id']}")
+
+    scopes.append("public")
+
+    return scopes
+```
+
+### 检索查询
+
+```python
+def search_knowledge(query, context, knowledge_type=None):
+    """
+    检索知识
+
+    query: 查询内容
+    context: 用户上下文
+    knowledge_type: 可选,过滤知识类型
+    """
+    # 1. 构建可见范围
+    visible_scopes = build_visible_scopes(context)
+
+    # 2. 构建查询条件
+    filters = {
+        "scopes": {"$in": visible_scopes}  # 交集匹配
+    }
+
+    if knowledge_type:
+        filters["type"] = knowledge_type
+
+    # 3. 向量检索 + 过滤
+    results = vector_db.search(
+        query=query,
+        filter=filters,
+        top_k=10
+    )
+
+    # 4. 按优先级排序
+    return rank_by_scope_priority(results, context)
+```
+
+### 优先级排序
+
+```python
+def rank_by_scope_priority(results, context):
+    """
+    按 scope 优先级排序
+
+    优先级:user > project > agent > team > org > public
+    """
+    priority_map = {
+        f"user:{context.get('user_id')}": 6,
+        f"project:{context.get('project_id')}": 5,
+        f"agent:{context.get('agent_id')}": 4,
+        f"team:{context.get('team_id')}": 3,
+        f"org:{context.get('org_id')}": 2,
+        "public": 1
+    }
+
+    def get_priority(knowledge):
+        # 取知识的 scopes 中优先级最高的
+        max_priority = 0
+        for scope in knowledge["scopes"]:
+            max_priority = max(max_priority, priority_map.get(scope, 0))
+        return max_priority
+
+    return sorted(results, key=get_priority, reverse=True)
+```
+
+## 权限控制
+
+### 读权限
+
+用户可以读取知识,当且仅当:
+- 知识的 `scopes` 与用户的 `visible_scopes` 有交集
+
+### 写权限
+
+用户可以修改/删除知识,当且仅当:
+- 用户是知识的 `owner`
+
+### 特殊情况
+
+- 管理员可以修改/删除所有知识
+- 组织级知识(owner=org:xxx)可以由管理员管理
+
+## 实现位置
+
+- `agent/memory/knowledge_store.py`:知识存储接口
+- `agent/memory/knowledge_retriever.py`:检索逻辑
+- `agent/tools/builtin/knowledge.py`:get_knowledge / save_knowledge 工具
+
+## 扩展性
+
+### 新增 Scope 类型
+
+如需新增 scope 类型(如 `department:{dept_id}`),只需:
+
+1. 在 `build_visible_scopes` 中添加逻辑
+2. 在 `rank_by_scope_priority` 中添加优先级
+3. 无需修改数据结构和存储逻辑
+
+### 升级为 ACL
+
+如需更细粒度的权限控制,可扩展为 ACL 系统:
+
+```json
+{
+  "id": "knowledge_001",
+  "type": "strategy",
+  "owner": "user:123",
+  "acl": [
+    {"entity": "user:123", "permission": "read_write"},
+    {"entity": "project:456", "permission": "read"},
+    {"entity": "agent:general_assistant", "permission": "read"}
+  ]
+}
+```
+
+但对于大多数场景,当前的 scope 标签系统已经足够。
+

+ 0 - 0
docs/skills.md → agent/docs/skills.md


+ 136 - 35
docs/tools.md → agent/docs/tools.md

@@ -25,7 +25,7 @@
 from reson_agent import tool, ToolResult, ToolContext
 from reson_agent import tool, ToolResult, ToolContext
 
 
 @tool()
 @tool()
-async def my_tool(arg: str, ctx: ToolContext) -> ToolResult:
+async def my_tool(arg: str, context: Optional[ToolContext] = None) -> ToolResult:
     return ToolResult(
     return ToolResult(
         title="Success",
         title="Success",
         output="Result content"
         output="Result content"
@@ -44,16 +44,70 @@ async def my_tool(arg: str, ctx: ToolContext) -> ToolResult:
 1. 定义工具
 1. 定义工具
    ↓ @tool() 装饰器
    ↓ @tool() 装饰器
 2. 自动注册到 ToolRegistry
 2. 自动注册到 ToolRegistry
-   ↓ 生成 OpenAI Tool Schema
+   ↓ 生成 OpenAI Tool Schema(跳过 hidden_params)
 3. LLM 选择工具并生成参数
 3. LLM 选择工具并生成参数
    ↓ registry.execute(name, args)
    ↓ registry.execute(name, args)
-4. 注入 uid 和 context
+4. 注入框架参数(hidden_params + inject_params)
    ↓ 调用工具函数
    ↓ 调用工具函数
 5. 返回 ToolResult
 5. 返回 ToolResult
    ↓ 转换为 LLM 消息
    ↓ 转换为 LLM 消息
 6. 添加到对话历史
 6. 添加到对话历史
 ```
 ```
 
 
+### 参数注入机制
+
+工具参数分为三类:
+
+1. **业务参数**:LLM 可见,由 LLM 填写(如 `query`, `limit`)
+2. **隐藏参数**:LLM 不可见,框架自动注入(如 `context`, `uid`)
+3. **注入参数**:LLM 可见,框架自动注入默认值或与 LLM 值合并(如 `owner`, `tags`)
+
+```python
+@tool(
+    hidden_params=["context", "owner"],  # 不生成 schema,LLM 看不到
+    inject_params={                       # 声明注入规则
+        "owner": {"mode": "default", "key": "knowledge_config.owner"},
+        "tags":  {"mode": "merge",   "key": "knowledge_config.default_tags"},
+        "scopes": {"mode": "merge",  "key": "knowledge_config.default_scopes"},
+    }
+)
+async def knowledge_save(
+    task: str,                          # 业务参数:LLM 填写
+    content: str,                       # 业务参数:LLM 填写
+    types: List[str],                   # 业务参数:LLM 填写
+    tags: Optional[Dict] = None,        # 注入参数:LLM 可填,框架合并默认值
+    scopes: Optional[List] = None,      # 注入参数:LLM 可填,框架合并默认值
+    owner: Optional[str] = None,        # 隐藏参数:LLM 看不到,框架注入
+    context: Optional[ToolContext] = None,  # 隐藏参数:LLM 看不到
+) -> ToolResult:
+    """保存知识到知识库"""
+    ...
+```
+
+**inject_params 声明格式**:
+
+```python
+inject_params={
+    "param_name": {
+        "mode": "default" | "merge",  # 注入模式
+        "key": "config_obj.field",    # 从 context 中取值的路径
+    }
+}
+```
+
+- `mode: "default"`:LLM 未提供时注入框架值
+- `mode: "merge"`:框架值与 LLM 值合并。dict 按 key 合并(框架 key 不可被覆盖,LLM 可追加新 key);list 合并去重
+
+**值的来源**:通过 `key` 指定从 `context` 中取值的路径(如 `"knowledge_config.default_tags"` 表示 `context["knowledge_config"].default_tags`)。runner 在调用 `execute()` 时将配置对象放入 context,框架根据 key 路径自动取值。
+
+**注入时机**:
+- Schema 生成时:跳过 `hidden_params`,不暴露给 LLM
+- 工具执行前:注入 `hidden_params` 和 `inject_params`
+
+**实现位置**:
+- Schema 生成:`agent/tools/schema.py:SchemaGenerator.generate()`
+- 参数注入:`agent/tools/registry.py:ToolRegistry.execute()`
+
 ---
 ---
 
 
 ## 定义工具
 ## 定义工具
@@ -64,23 +118,24 @@ async def my_tool(arg: str, ctx: ToolContext) -> ToolResult:
 from reson_agent import tool
 from reson_agent import tool
 
 
 @tool()
 @tool()
-async def hello(name: str, uid: str = "") -> str:
+async def hello(name: str) -> str:
     """向用户问好"""
     """向用户问好"""
     return f"Hello, {name}!"
     return f"Hello, {name}!"
 ```
 ```
 
 
 **要点**:
 **要点**:
-- `uid` 参数由框架自动注入(用户不传递)
 - 可以是同步或异步函数
 - 可以是同步或异步函数
 - 返回值自动序列化为 JSON
 - 返回值自动序列化为 JSON
+- 所有参数默认对 LLM 可见
 
 
-### 带完整注释
+### 带框架参数
 
 
 ```python
 ```python
-@tool()
+@tool(hidden_params=["context", "uid"])
 async def search_notes(
 async def search_notes(
     query: str,
     query: str,
     limit: int = 10,
     limit: int = 10,
+    context: Optional[ToolContext] = None,
     uid: str = ""
     uid: str = ""
 ) -> str:
 ) -> str:
     """
     """
@@ -89,14 +144,50 @@ async def search_notes(
     Args:
     Args:
         query: 搜索关键词
         query: 搜索关键词
         limit: 返回结果数量
         limit: 返回结果数量
+    """
+    # context 和 uid 由框架注入,LLM 看不到这两个参数
+    ...
+```
 
 
-    Returns:
-        JSON 格式的搜索结果
+### 带参数注入
+
+```python
+@tool(
+    hidden_params=["context", "owner"],
+    inject_params={
+        "owner": {"mode": "default", "key": "knowledge_config.owner"},
+        "tags":  {"mode": "merge",   "key": "knowledge_config.default_tags"},
+        "scopes": {"mode": "merge",  "key": "knowledge_config.default_scopes"},
+    }
+)
+async def knowledge_save(
+    task: str,
+    content: str,
+    types: List[str],
+    tags: Optional[Dict] = None,  # LLM 可填,框架合并默认值
+    scopes: Optional[List] = None,  # LLM 可填,框架合并默认值
+    owner: Optional[str] = None,  # LLM 看不到,框架注入
+    context: Optional[ToolContext] = None
+) -> ToolResult:
+    """
+    保存知识
+
+    Args:
+        task: 任务描述
+        content: 知识内容
+        types: 知识类型
+        tags: 业务标签(可选,框架合并默认值)
+        scopes: 可见范围(可选,框架合并默认值)
     """
     """
-    # 支持自动从 docstring 提取 function description 和 parameter descriptions
     ...
     ...
 ```
 ```
 
 
+**注入规则**:
+- `inject_params` 的 value 是一个 dict,包含:
+  - `mode`: `"default"`(LLM 未提供则注入)或 `"merge"`(与 LLM 值合并)
+  - `key`: 从 context 中取值的路径(如 `"knowledge_config.default_tags"`)
+- 参数同时在 `hidden_params` 中时,LLM 不可见,框架直接注入
+
 ### 带 UI 元数据
 ### 带 UI 元数据
 
 
 ```python
 ```python
@@ -228,16 +319,16 @@ async def generate_report() -> ToolResult:
 
 
 ### 基本概念
 ### 基本概念
 
 
-工具函数可以声明需要 `ToolContext` 参数,框架自动注入。
+工具函数可以声明需要 `ToolContext` 参数,框架自动注入。需要在 `@tool()` 装饰器中声明 `hidden_params=["context"]`,使其对 LLM 不可见。
 
 
 ```python
 ```python
 from reson_agent import ToolContext
 from reson_agent import ToolContext
 
 
-@tool()
-async def get_current_state(ctx: ToolContext) -> ToolResult:
+@tool(hidden_params=["context"])
+async def get_current_state(context: Optional[ToolContext] = None) -> ToolResult:
     return ToolResult(
     return ToolResult(
         title="Current state",
         title="Current state",
-        output=f"Trace ID: {ctx.trace_id}\nStep ID: {ctx.step_id}"
+        output=f"Trace ID: {context.trace_id}\nStep ID: {context.step_id}"
     )
     )
 ```
 ```
 
 
@@ -250,56 +341,66 @@ class ToolContext(Protocol):
     step_id: str                # 当前 Step ID
     step_id: str                # 当前 Step ID
     uid: Optional[str]          # 用户 ID
     uid: Optional[str]          # 用户 ID
 
 
+    # 扩展字段(由 runner 注入)
+    store: Optional[TraceStore]     # Trace 存储
+    runner: Optional[AgentRunner]   # Runner 实例
+    goal_tree: Optional[GoalTree]   # 目标树
+    goal_id: Optional[str]          # 当前 Goal ID
+    config: Optional[RunConfig]     # 运行配置
+
     # 浏览器相关(Browser-Use 集成)
     # 浏览器相关(Browser-Use 集成)
     browser_session: Optional[Any]      # 浏览器会话
     browser_session: Optional[Any]      # 浏览器会话
     page_url: Optional[str]             # 当前页面 URL
     page_url: Optional[str]             # 当前页面 URL
     file_system: Optional[Any]          # 文件系统访问
     file_system: Optional[Any]          # 文件系统访问
     sensitive_data: Optional[Dict]      # 敏感数据
     sensitive_data: Optional[Dict]      # 敏感数据
 
 
-    # 扩展字段
-    context: Optional[Dict[str, Any]]   # 额外上下文
+    # 额外上下文
+    context: Optional[Dict[str, Any]]   # 额外上下文数据
 ```
 ```
 
 
 ### 使用示例
 ### 使用示例
 
 
 ```python
 ```python
-@tool()
-async def analyze_current_page(ctx: ToolContext) -> ToolResult:
+@tool(hidden_params=["context"])
+async def analyze_current_page(context: Optional[ToolContext] = None) -> ToolResult:
     """分析当前浏览器页面"""
     """分析当前浏览器页面"""
 
 
-    if not ctx.browser_session:
+    if not context or not context.browser_session:
         return ToolResult(
         return ToolResult(
             title="Error",
             title="Error",
             error="Browser session not available"
             error="Browser session not available"
         )
         )
 
 
     # 使用浏览器会话
     # 使用浏览器会话
-    page_content = await ctx.browser_session.get_content()
+    page_content = await context.browser_session.get_content()
 
 
     return ToolResult(
     return ToolResult(
-        title=f"Analyzed {ctx.page_url}",
+        title=f"Analyzed {context.page_url}",
         output=page_content,
         output=page_content,
-        long_term_memory=f"Analyzed page at {ctx.page_url}"
+        long_term_memory=f"Analyzed page at {context.page_url}"
     )
     )
 ```
 ```
 
 
 ### 创建 ToolContext
 ### 创建 ToolContext
 
 
-```python
-from reson_agent import ToolContextImpl
+Runner 在执行工具时自动创建并注入 context:
 
 
-ctx = ToolContextImpl(
-    trace_id="trace_123",
-    step_id="step_456",
-    uid="user_789",
-    page_url="https://example.com"
-)
+```python
+# 在 AgentRunner._agent_loop 中
+context = {
+    "store": self.trace_store,
+    "trace_id": trace_id,
+    "goal_id": current_goal_id,
+    "runner": self,
+    "goal_tree": goal_tree,
+    "config": config,
+}
 
 
-# 执行工具
-result = await registry.execute(
-    "analyze_current_page",
-    arguments={},
-    context=ctx
+result = await self.tools.execute(
+    tool_name,
+    tool_args,
+    uid=config.uid or "",
+    context=context
 )
 )
 ```
 ```
 
 

+ 0 - 0
docs/trace-api.md → agent/docs/trace-api.md


+ 13 - 13
agent/llm/openrouter.py

@@ -480,7 +480,7 @@ async def _openrouter_anthropic_call(
 
 
     # Resolve model name for OpenRouter (e.g. "claude-sonnet-4.5" → "anthropic/claude-sonnet-4-5-20250929")
     # Resolve model name for OpenRouter (e.g. "claude-sonnet-4.5" → "anthropic/claude-sonnet-4-5-20250929")
     resolved_model = _resolve_openrouter_model(model)
     resolved_model = _resolve_openrouter_model(model)
-    logger.info("[OpenRouter/Anthropic] model: %s → %s", model, resolved_model)
+    logger.debug("[OpenRouter/Anthropic] model: %s → %s", model, resolved_model)
 
 
     # 跨 Provider 续跑时,重写不兼容的 tool_call_id 为 toolu_ 前缀
     # 跨 Provider 续跑时,重写不兼容的 tool_call_id 为 toolu_ 前缀
     messages = _normalize_tool_call_ids(messages, "toolu")
     messages = _normalize_tool_call_ids(messages, "toolu")
@@ -512,20 +512,20 @@ async def _openrouter_anthropic_call(
         payload["temperature"] = kwargs["temperature"]
         payload["temperature"] = kwargs["temperature"]
 
 
     # Debug: 检查 cache_control 是否存在
     # Debug: 检查 cache_control 是否存在
-    cache_control_count = 0
-    if isinstance(system_prompt, list):
-        for block in system_prompt:
-            if isinstance(block, dict) and "cache_control" in block:
-                cache_control_count += 1
-    for msg in anthropic_messages:
-        content = msg.get("content", "")
-        if isinstance(content, list):
-            for block in content:
+    if logger.isEnabledFor(logging.DEBUG):
+        cache_control_count = 0
+        if isinstance(system_prompt, list):
+            for block in system_prompt:
                 if isinstance(block, dict) and "cache_control" in block:
                 if isinstance(block, dict) and "cache_control" in block:
                     cache_control_count += 1
                     cache_control_count += 1
-    if cache_control_count > 0:
-        print(f"[OpenRouter/Anthropic] 发现 {cache_control_count} 个 cache_control 标记")
-        logger.info(f"[OpenRouter/Anthropic] 发现 {cache_control_count} 个 cache_control 标记")
+        for msg in anthropic_messages:
+            content = msg.get("content", "")
+            if isinstance(content, list):
+                for block in content:
+                    if isinstance(block, dict) and "cache_control" in block:
+                        cache_control_count += 1
+        if cache_control_count > 0:
+            logger.debug(f"[OpenRouter/Anthropic] 发现 {cache_control_count} 个 cache_control 标记")
 
 
     headers = {
     headers = {
         "Authorization": f"Bearer {api_key}",
         "Authorization": f"Bearer {api_key}",

+ 3 - 3
agent/memory/skills/core.md

@@ -65,11 +65,11 @@ goal(abandon="方案A需要Redis,环境没有")
 4. **计划可调整**:根据执行情况随时追加、跳过或放弃目标
 4. **计划可调整**:根据执行情况随时追加、跳过或放弃目标
 5. **使用 ID 定位**:focus、after、under 参数使用目标的 ID(如 "1", "2.1")
 5. **使用 ID 定位**:focus、after、under 参数使用目标的 ID(如 "1", "2.1")
 
 
-### 经验复用
+### 知识复用
 
 
-在**启动新任务**、**拆分复杂目标**或**遇到执行障碍**时,应主动调用 `get_experience` 获取k条历史成功经验或避坑指南。
+在**启动新任务**、**拆分复杂目标**或**遇到执行障碍**时,应主动调用 `knowledge_search` 获取相关的历史经验或避坑指南。
 **使用示例:**
 **使用示例:**
-`get_experience(query="如何处理浏览器点击不生效的问题")`
+`knowledge_search(query="如何处理浏览器点击不生效的问题", types=["strategy", "tool"])`
 
 
 ## 信息调研
 ## 信息调研
 
 

+ 25 - 21
agent/memory/skills/research.md

@@ -35,15 +35,13 @@ goal(add="搜索工具, 搜索案例, 搜索方法论")
 每发现一条有价值的信息,立即保存为结构化知识:
 每发现一条有价值的信息,立即保存为结构化知识:
 
 
 ```python
 ```python
-save_knowledge(
-    tags_type=["tool"],  # tool/usercase/definition/plan
-    scenario="在什么情景下,要完成什么目标,得到能达成一个什么结果",
+knowledge_save(
+    task="在什么情景下,要完成什么目标,得到能达成一个什么结果",
     content="这条知识实际的核心内容",
     content="这条知识实际的核心内容",
+    types=["tool"],  # tool/usecase/definition/plan/strategy/user_profile
     urls=["参考的论文/github/博客等"],
     urls=["参考的论文/github/博客等"],
     agent_id="当前 agent ID",
     agent_id="当前 agent ID",
-    score=5,
-    helpful_count=1,
-    harmful_count=0
+    score=5
 )
 )
 ```
 ```
 
 
@@ -259,9 +257,8 @@ goal(done="已完成调研,共记录 N 条知识")
 ### 保存知识
 ### 保存知识
 
 
 ````python
 ````python
-save_knowledge(
-    tags=["tool", "plan"],
-    summary="在 Python 3.11 环境下,从复杂 PDF 中提取表格数据,并保留单元格坐标信息。",
+knowledge_save(
+    task="在 Python 3.11 环境下,从复杂 PDF 中提取表格数据,并保留单元格坐标信息。",
     content="""
     content="""
 ## 推荐工具
 ## 推荐工具
 pdfplumber - 专注于 PDF 表格提取
 pdfplumber - 专注于 PDF 表格提取
@@ -274,31 +271,38 @@ pdfplumber - 专注于 PDF 表格提取
 import pdfplumber
 import pdfplumber
 with pdfplumber.open('file.pdf') as pdf:
 with pdfplumber.open('file.pdf') as pdf:
     tables = pdf.pages[0].extract_tables()
     tables = pdf.pages[0].extract_tables()
-````
+```
 
 
+⚠️ 必须设置 explicit_horizontal_lines=True 以识别隐形表格线
 """,
 """,
-tips="⚠️ 必须设置 explicit_horizontal_lines=True 以识别隐形表格线",
-urls=["https://github.com/jsvine/pdfplumber"],
-agent_id="research_agent_001"
+    types=["tool", "plan"],
+    urls=["https://github.com/jsvine/pdfplumber"],
+    agent_id="research_agent_001",
+    score=5
 )
 )
-
 ````
 ````
 
 
 ### 更新评估反馈
 ### 更新评估反馈
 
 
 ```python
 ```python
-update_knowledge(
-    knowledge_id="atom-20260302-001",
-    add_helpful_case="在解析 2025 年报 PDF 时,通过配置 explicit_lines 成功提取了 100+ 嵌套表格。"
+knowledge_update(
+    knowledge_id="knowledge-20260302-001",
+    add_helpful_case={
+        "description": "在解析 2025 年报 PDF 时,通过配置 explicit_lines 成功提取了 100+ 嵌套表格。",
+        "trace_id": "trace-xxx"
+    }
 )
 )
-````
+```
 
 
 或添加失败案例:
 或添加失败案例:
 
 
 ```python
 ```python
-update_knowledge(
-    knowledge_id="atom-20260302-001",
-    add_harmful_case="在处理 300MB 的扫描版 PDF 时,该方案因缺乏 OCR 能力导致提取结果为空。"
+knowledge_update(
+    knowledge_id="knowledge-20260302-001",
+    add_harmful_case={
+        "description": "在处理 300MB 的扫描版 PDF 时,该方案因缺乏 OCR 能力导致提取结果为空。",
+        "trace_id": "trace-xxx"
+    }
 )
 )
 ```
 ```
 
 

+ 7 - 5
agent/tools/builtin/__init__.py

@@ -18,7 +18,7 @@ from agent.tools.builtin.subagent import agent, evaluate
 from agent.tools.builtin.search import search_posts, get_search_suggestions
 from agent.tools.builtin.search import search_posts, get_search_suggestions
 from agent.tools.builtin.sandbox import (sandbox_create_environment, sandbox_run_shell,
 from agent.tools.builtin.sandbox import (sandbox_create_environment, sandbox_run_shell,
                                          sandbox_rebuild_with_ports,sandbox_destroy_environment)
                                          sandbox_rebuild_with_ports,sandbox_destroy_environment)
-from agent.tools.builtin.knowledge import(search_knowledge,save_knowledge,list_knowledge,update_knowledge)
+from agent.tools.builtin.knowledge import(knowledge_search,knowledge_save,knowledge_list,knowledge_update,knowledge_batch_update,knowledge_slim)
 from agent.trace.goal_tool import goal
 from agent.trace.goal_tool import goal
 # 导入浏览器工具以触发注册
 # 导入浏览器工具以触发注册
 import agent.tools.builtin.browser  # noqa: F401
 import agent.tools.builtin.browser  # noqa: F401
@@ -35,10 +35,12 @@ __all__ = [
     # 系统工具
     # 系统工具
     "bash_command",
     "bash_command",
     "skill",
     "skill",
-    "search_knowledge",
-    "save_knowledge",
-    "list_knowledge",
-    "update_knowledge",
+    "knowledge_search",
+    "knowledge_save",
+    "knowledge_list",
+    "knowledge_update",
+    "knowledge_batch_update",
+    "knowledge_slim",
     "list_skills",
     "list_skills",
     "agent",
     "agent",
     "evaluate",
     "evaluate",

+ 1 - 1
agent/tools/builtin/bash.py

@@ -158,7 +158,7 @@ def _kill_process_tree(pid: int) -> None:
         pass
         pass
 
 
 
 
-@tool(description="执行 bash 命令")
+@tool(description="执行 bash 命令", hidden_params=["context"])
 async def bash_command(
 async def bash_command(
     command: str,
     command: str,
     timeout: Optional[int] = None,
     timeout: Optional[int] = None,

+ 1 - 5
agent/tools/builtin/browser/baseClass.py

@@ -228,11 +228,7 @@ async def create_container(url: str, account_name: str = "liuwenwu") -> Dict[str
 
 
 async def init_browser_session(
 async def init_browser_session(
     browser_type: str = "local",
     browser_type: str = "local",
-    # TEMPORARY FIX (2026-03-02): 改为 True 以解决 CDP 连接时序问题
-    # browser-use 在非 headless 模式下有时会在 Chrome 完全启动前尝试连接 CDP,
-    # 导致 "JSONDecodeError: Expecting value" 错误
-    # TODO: 之后改回 headless: bool = False,或在 browser-use 修复此问题后移除此注释
-    headless: bool = True,  # 原值: False
+    headless: bool = False,
     url: Optional[str] = None,
     url: Optional[str] = None,
     profile_name: str = "default",
     profile_name: str = "default",
     user_data_dir: Optional[str] = None,
     user_data_dir: Optional[str] = None,

+ 0 - 487
agent/tools/builtin/experience.py

@@ -1,487 +0,0 @@
-import logging
-import os
-import yaml
-import json
-import asyncio
-import re
-from typing import List, Optional, Dict, Any
-from datetime import datetime
-from ...llm.openrouter import openrouter_llm_call
-
-logger = logging.getLogger(__name__)
-
-# 默认经验存储路径(当无法从 context 获取时使用)
-DEFAULT_EXPERIENCES_PATH = "./.cache/experiences_restore.md"
-
-def _get_experiences_path(context: Optional[Any] = None) -> str:
-    """
-    从 context 中获取 experiences_path,回退到默认路径。
-
-    context 可能包含 runner 引用,从中读取配置的路径。
-    """
-    if context and isinstance(context, dict):
-        runner = context.get("runner")
-        if runner and hasattr(runner, "experiences_path"):
-            path = runner.experiences_path or DEFAULT_EXPERIENCES_PATH
-            print(f"[Experience] 使用 runner 配置的路径: {runner.experiences_path}")
-            return path
-
-    print(f"[Experience] 使用默认路径: {DEFAULT_EXPERIENCES_PATH}")
-    return DEFAULT_EXPERIENCES_PATH
-
-# ===== 经验进化重写 =====
-async def _evolve_body_with_llm(old_body: str, feedback: str) -> str:
-    """
-    使用检索级别的小模型 (Flash Lite) 执行经验进化重写。
-    """
-    prompt = f"""你是一个 AI Agent 经验库管理员。请根据反馈建议,对现有的 ACE 规范经验进行重写进化。
-
-【原经验内容】:
-{old_body}
-
-【实战反馈建议】:
-{feedback}
-
-【重写要求】:
-1. 保持 ACE 规范:当 [条件/Context] 时,应该 [动作/Action](原因:[逻辑/Reason])。
-2. 融合知识:将反馈中的避坑指南、新参数或修正后的选择逻辑融入原经验,使其更具通用性和准确性。
-3. 语言:简洁直接,使用中文。
-4. 禁止:严禁输出任何开场白、解释语或 Markdown 标题,直接返回重写后的正文。
-"""
-    try:
-        # 调用与检索路由相同的廉价模型
-        response = await openrouter_llm_call(
-            messages=[{"role": "user", "content": prompt}],
-            model="google/gemini-2.0-flash-001" 
-        )
-        
-        evolved_content = response.get("content", "").strip()
-        
-        # 简单安全校验:如果 LLM 返回太短或为空,回退到原内容+追加
-        if len(evolved_content) < 5:
-            raise ValueError("LLM output too short")
-            
-        return evolved_content
-        
-    except Exception as e:
-        logger.warning(f"小模型进化失败,采用追加模式回退: {e}")
-        timestamp = datetime.now().strftime('%Y-%m-%d')
-        return f"{old_body}\n- [Update {timestamp}]: {feedback}"
-    
-# ===== 核心挑选逻辑 =====
-
-async def _route_experiences_by_llm(query_text: str, metadata_list: List[Dict], k: int = 3) -> List[str]:
-    """
-    第一阶段:语义路由。
-    让 LLM 挑选出 2*k 个语义相关的 ID。
-    """
-    if not metadata_list:
-        return []
-
-    # 扩大筛选范围到 2*k
-    routing_k = k * 2
-    
-    routing_data = [
-        {
-            "id": m["id"],
-            "tags": m["tags"],
-            "helpful": m["metrics"]["helpful"]
-        } for m in metadata_list
-    ]
-
-    prompt = f"""
-你是一个经验检索专家。根据用户的当前意图,从下列经验元数据中挑选出最相关的最多 {routing_k} 个经验 ID。
-意图:"{query_text}"
-
-可选经验列表:
-{json.dumps(routing_data, ensure_ascii=False, indent=1)}
-
-请直接输出 ID 列表,用逗号分隔(例如: ex_01, ex_02)。若无相关项请输出 "None"。
-"""
-
-    try:
-        print(f"\n[Step 1: 语义路由] 意图: '{query_text}' | 候选总数: {len(metadata_list)} | 目标提取数: {routing_k}")
-        
-        response = await openrouter_llm_call(
-            messages=[{"role": "user", "content": prompt}],
-            model="google/gemini-2.0-flash-001" 
-        )
-        
-        content = response.get("content", "").strip()
-        selected_ids = [idx.strip() for idx in re.split(r'[,\s]+', content) if idx.strip().startswith("ex_")]
-        
-        print(f"[Step 1: 语义路由] LLM 初选 ID ({len(selected_ids)}个): {selected_ids}")
-        return selected_ids
-    except Exception as e:
-        logger.error(f"LLM 经验路由失败: {e}")
-        return []
-
-async def _get_structured_experiences(query_text: str, top_k: int = 3, context: Optional[Any] = None):
-    """
-    1. 解析物理文件
-    2. 语义路由:提取 2*k 个 ID
-    3. 质量精排:基于 Metrics 筛选出最终的 k 个
-    """
-    print(f"[Experience System]  runner.experiences_path:  {context.get('runner').experiences_path if context and context.get('runner') else None}")
-    experiences_path = _get_experiences_path(context)
-
-    if not os.path.exists(experiences_path):
-        print(f"[Experience System] 警告: 经验文件不存在 ({experiences_path})")
-        return []
-
-    with open(experiences_path, "r", encoding="utf-8") as f:
-        file_content = f.read()
-
-    # --- 阶段 1: 解析 ---
-    # 使用正则表达式匹配 YAML frontmatter 块,避免误分割
-    pattern = r'---\n(.*?)\n---\n(.*?)(?=\n---\n|\Z)'
-    matches = re.findall(pattern, file_content, re.DOTALL)
-
-    content_map = {}
-    metadata_list = []
-
-    for yaml_str, raw_body in matches:
-        try:
-            metadata = yaml.safe_load(yaml_str)
-
-            # 检查 metadata 类型
-            if not isinstance(metadata, dict):
-                logger.error(f"跳过损坏的经验块: metadata 不是 dict,而是 {type(metadata).__name__}")
-                continue
-
-            eid = metadata.get("id")
-            if not eid:
-                logger.error("跳过损坏的经验块: 缺少 id 字段")
-                continue
-
-            meta_item = {
-                "id": eid,
-                "tags": metadata.get("tags", {}),
-                "metrics": metadata.get("metrics", {"helpful": 0, "harmful": 0}),
-            }
-            metadata_list.append(meta_item)
-            content_map[eid] = {
-                "content": raw_body.strip(),
-                "metrics": meta_item["metrics"]
-            }
-        except Exception as e:
-            logger.error(f"跳过损坏的经验块: {e}")
-            continue
-
-    # --- 阶段 2: 语义路由 (取 2*k) ---
-    candidate_ids = await _route_experiences_by_llm(query_text, metadata_list, k=top_k)
-
-    # --- 阶段 3: 质量精排 (根据 Metrics 选出最终的 k) ---
-    print(f"[Step 2: 质量精排] 正在根据 Metrics 对候选经验进行打分...")
-    scored_items = []
-    
-    for eid in candidate_ids:
-        if eid in content_map:
-            item = content_map[eid]
-            metrics = item["metrics"]
-            # 计算综合分:Helpful 是正分,Harmful 是双倍惩罚扣分
-            quality_score = metrics["helpful"] - (metrics["harmful"] * 2.0)
-            
-            # 过滤门槛:如果被标记为严重有害(score < -2),直接丢弃
-            if quality_score < -2:
-                print(f"  - 剔除有害经验: {eid} (Helpful: {metrics['helpful']}, Harmful: {metrics['harmful']})")
-                continue
-                
-            scored_items.append({
-                "id": eid,
-                "content": item["content"],
-                "helpful": metrics["helpful"],
-                "quality_score": quality_score
-            })
-
-    # 按照质量分排序,质量分相同时按 helpful 排序
-    final_sorted = sorted(scored_items, key=lambda x: (x["quality_score"], x["helpful"]), reverse=True)
-    
-    # 截取最终的 top_k
-    result = final_sorted[:top_k]
-    
-    print(f"[Step 2: 质量精排] 最终选定经验: {[it['id'] for it in result]}")
-    print(f"[Experience System] 检索结束。\n")
-    return result
-
-async def _batch_update_experiences(update_map: Dict[str, Dict[str, Any]], context: Optional[Any] = None):
-    """
-    物理层:批量更新经验。
-    修正点:正确使用 new_sections 集合,确保文件结构的完整性与并发进化的同步。
-    """
-    experiences_path = _get_experiences_path(context)
-
-    if not os.path.exists(experiences_path) or not update_map:
-        return 0
-
-    with open(experiences_path, "r", encoding="utf-8") as f:
-        full_content = f.read()
-
-    # 使用正则表达式解析,避免误分割
-    pattern = r'---\n(.*?)\n---\n(.*?)(?=\n---\n|\Z)'
-    matches = re.findall(pattern, full_content, re.DOTALL)
-
-    new_entries = []
-    evolution_tasks = []
-    evolution_registry = {}  # task_idx -> entry_idx
-
-    # --- 第一阶段:处理所有块 ---
-    for yaml_str, body in matches:
-        try:
-            meta = yaml.safe_load(yaml_str)
-            if not isinstance(meta, dict):
-                logger.error(f"跳过损坏的经验块: metadata 不是 dict")
-                continue
-
-            eid = meta.get("id")
-            if not eid:
-                logger.error("跳过损坏的经验块: 缺少 id")
-                continue
-
-            if eid in update_map:
-                instr = update_map[eid]
-                action = instr.get("action")
-                feedback = instr.get("feedback")
-
-                # 处理 mixed 中间态
-                if action == "mixed":
-                    meta["metrics"]["helpful"] += 1
-                    action = "evolve"
-
-                if action == "helpful":
-                    meta["metrics"]["helpful"] += 1
-                elif action == "harmful":
-                    meta["metrics"]["harmful"] += 1
-                elif action == "evolve" and feedback:
-                    # 注册进化任务
-                    task = _evolve_body_with_llm(body.strip(), feedback)
-                    evolution_tasks.append(task)
-                    # 记录该任务对应的 entry 索引
-                    evolution_registry[len(evolution_tasks) - 1] = len(new_entries)
-                    meta["metrics"]["helpful"] += 1
-
-                meta["updated_at"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-
-            # 序列化并加入 new_entries
-            meta_str = yaml.dump(meta, allow_unicode=True).strip()
-            new_entries.append((meta_str, body.strip()))
-
-        except Exception as e:
-            logger.error(f"跳过损坏的经验块: {e}")
-            continue
-
-    # --- 第二阶段:并发进化 ---
-    if evolution_tasks:
-        print(f"🧬 并发处理 {len(evolution_tasks)} 条经验进化...")
-        evolved_results = await asyncio.gather(*evolution_tasks)
-
-        # 精准回填:替换对应 entry 的 body
-        for task_idx, entry_idx in evolution_registry.items():
-            meta_str, _ = new_entries[entry_idx]
-            new_entries[entry_idx] = (meta_str, evolved_results[task_idx].strip())
-
-    # --- 第三阶段:原子化写回 ---
-    final_parts = []
-    for meta_str, body in new_entries:
-        final_parts.append(f"---\n{meta_str}\n---\n{body}\n")
-
-    final_content = "\n".join(final_parts)
-    with open(experiences_path, "w", encoding="utf-8") as f:
-        f.write(final_content)
-
-    return len(update_map)
-
-# ===== 经验库瘦身 =====
-
-async def slim_experiences(model: str = "anthropic/claude-sonnet-4.5", context: Optional[Any] = None) -> str:
-    """
-    经验库瘦身:调用顶级大模型,将经验库中语义相似的经验合并精简。
-    返回瘦身报告字符串。
-    """
-    experiences_path = _get_experiences_path(context)
-
-    if not os.path.exists(experiences_path):
-        return "经验文件不存在,无需瘦身。"
-
-    with open(experiences_path, "r", encoding="utf-8") as f:
-        file_content = f.read()
-
-    # 使用正则表达式解析,避免误分割
-    pattern = r'---\n(.*?)\n---\n(.*?)(?=\n---\n|\Z)'
-    matches = re.findall(pattern, file_content, re.DOTALL)
-
-    parsed = []
-    for yaml_str, body in matches:
-        try:
-            meta = yaml.safe_load(yaml_str)
-            if not isinstance(meta, dict):
-                continue
-            parsed.append({"meta": meta, "body": body.strip()})
-        except Exception:
-            continue
-
-    if len(parsed) < 2:
-        return f"经验库仅有 {len(parsed)} 条,无需瘦身。"
-
-    # 构造发给大模型的内容
-    entries_text = ""
-    for p in parsed:
-        m = p["meta"]
-        entries_text += f"[ID: {m.get('id')}] [Tags: {m.get('tags', {})}] "
-        entries_text += f"[Metrics: {m.get('metrics', {})}]\n"
-        entries_text += f"{p['body']}\n\n"
-
-    prompt = f"""你是一个 AI Agent 经验库管理员。以下是当前经验库的全部条目,请执行瘦身操作:
-
-【任务】:
-1. 识别语义高度相似或重复的经验,将它们合并为一条更精炼、更通用的经验。
-2. 合并时保留 helpful 最高的那条的 ID 和 metrics(metrics 中 helpful/harmful 取各条之和)。
-3. 对于独立的、无重复的经验,保持原样不动。
-4. 保持 ACE 规范格式:当 [条件/Context] 时,应该 [动作/Action](原因:[逻辑/Reason])。
-
-【当前经验库】:
-{entries_text}
-
-【输出格式要求】:
-严格按以下格式输出每条经验,条目之间用 === 分隔:
-ID: <保留的id>
-TAGS: <yaml格式的tags>
-METRICS: <yaml格式的metrics>
-BODY: <合并后的经验正文>
-===
-
-最后一行输出合并报告,格式:
-REPORT: 原有 X 条,合并后 Y 条,精简了 Z 条。
-
-禁止输出任何开场白或解释。"""
-
-    try:
-        print(f"\n[经验瘦身] 正在调用 {model} 分析 {len(parsed)} 条经验...")
-        response = await openrouter_llm_call(
-            messages=[{"role": "user", "content": prompt}],
-            model=model
-        )
-        content = response.get("content", "").strip()
-        if not content:
-            return "大模型返回为空,瘦身失败。"
-
-        # 解析大模型输出,重建经验文件
-        report_line = ""
-        new_entries = []
-        blocks = [b.strip() for b in content.split("===") if b.strip()]
-
-        for block in blocks:
-            if block.startswith("REPORT:"):
-                report_line = block
-                continue
-
-            lines = block.split("\n")
-            eid, tags, metrics, body_lines = None, {}, {}, []
-            current_field = None
-            for line in lines:
-                if line.startswith("ID:"):
-                    eid = line[3:].strip()
-                    current_field = None
-                elif line.startswith("TAGS:"):
-                    try:
-                        tags = yaml.safe_load(line[5:].strip()) or {}
-                    except Exception:
-                        tags = {}
-                    current_field = None
-                elif line.startswith("METRICS:"):
-                    try:
-                        metrics = yaml.safe_load(line[8:].strip()) or {}
-                    except Exception:
-                        metrics = {"helpful": 0, "harmful": 0}
-                    current_field = None
-                elif line.startswith("BODY:"):
-                    body_lines.append(line[5:].strip())
-                    current_field = "body"
-                elif current_field == "body":
-                    body_lines.append(line)
-
-            if eid and body_lines:
-                meta = {
-                    "id": eid,
-                    "tags": tags,
-                    "metrics": metrics,
-                    "updated_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
-                }
-                meta_str = yaml.dump(meta, allow_unicode=True).strip()
-                body_str = "\n".join(body_lines).strip()
-                new_entries.append(f"---\n{meta_str}\n---\n{body_str}\n")
-
-        if not new_entries:
-            return "解析大模型输出失败,经验库未修改。"
-
-        # 写回文件
-        final = "\n".join(new_entries)
-        with open(experiences_path, "w", encoding="utf-8") as f:
-            f.write(final)
-
-        result = f"瘦身完成:{len(parsed)} → {len(new_entries)} 条经验。"
-        if report_line:
-            result += f"\n{report_line}"
-        print(f"[经验瘦身] {result}")
-        return result
-
-    except Exception as e:
-        logger.error(f"经验瘦身失败: {e}")
-        return f"瘦身失败: {e}"
-
-# ===== 对外 Tool 接口 =====
-
-from agent.tools import tool, ToolContext
-
-@tool(description="通过两阶段检索获取最相关的历史经验")
-async def get_experience(query: str, k: int = 3, context: Optional[ToolContext] = None):
-    """
-    通过两阶段检索获取最相关的历史经验。
-    第一阶段语义匹配(2*k),第二阶段质量精排(k)。
-    """
-    relevant_items = await _get_structured_experiences(
-        query_text=query,
-        top_k=k,
-        context=context
-    )
-
-    if not relevant_items:
-        return "未找到足够相关的优质经验。"
-
-    return {
-        "items": relevant_items,
-        "count": len(relevant_items)
-    }
-
-@tool()
-async def update_experiences(feedback_list: List[Dict[str, Any]], context: Optional[ToolContext] = None):
-    """
-    批量反馈历史经验的有效性。
-    
-    Args:
-        feedback_list: 评价列表,每个元素包含:
-            - ex_id: (str) 经验 ID
-            - is_effective: (bool) 是否有效
-            - feedback: (str, optional) 改进建议,若有效且有建议则触发经验进化
-    """
-    if not feedback_list:
-        return "反馈列表为空。"
-
-    # 将 Agent 的输入转换为底层函数需要的映射表格式
-    update_map = {}
-    for item in feedback_list:
-        ex_id = item.get("ex_id")
-        is_effective = item.get("is_effective")
-        comment = item.get("feedback", "")
-
-        action = "helpful" if is_effective else "harmful"
-        if is_effective and comment:
-            action = "evolve"
-        
-        update_map[ex_id] = {
-            "action": action,
-            "feedback": comment
-        }
-
-    count = await _batch_update_experiences(update_map, context)
-    return f"成功同步了 {count} 条经验的反馈。感谢你的评价!"

+ 4 - 0
agent/tools/builtin/feishu/chat.py

@@ -132,6 +132,7 @@ def update_unread_count(contact_name: str, increment: int = 1, reset: bool = Fal
 # ==================== 三、@tool 工具 ====================
 # ==================== 三、@tool 工具 ====================
 
 
 @tool(
 @tool(
+    hidden_params=["context"],
     display={
     display={
         "zh": {
         "zh": {
             "name": "获取飞书联系人列表",
             "name": "获取飞书联系人列表",
@@ -158,6 +159,7 @@ async def feishu_get_contact_list(context: Optional[ToolContext] = None) -> Tool
     )
     )
 
 
 @tool(
 @tool(
+    hidden_params=["context"],
     display={
     display={
         "zh": {
         "zh": {
             "name": "给飞书联系人发送消息",
             "name": "给飞书联系人发送消息",
@@ -280,6 +282,7 @@ async def feishu_send_message_to_contact(
         return ToolResult(title="发送异常", output=str(e), error=str(e))
         return ToolResult(title="发送异常", output=str(e), error=str(e))
 
 
 @tool(
 @tool(
+    hidden_params=["context"],
     display={
     display={
         "zh": {
         "zh": {
             "name": "获取飞书联系人回复",
             "name": "获取飞书联系人回复",
@@ -395,6 +398,7 @@ def _convert_feishu_msg_to_openai_content(client: FeishuClient, msg: Dict[str, A
     return blocks
     return blocks
 
 
 @tool(
 @tool(
+    hidden_params=["context"],
     display={
     display={
         "zh": {
         "zh": {
             "name": "获取飞书聊天历史记录",
             "name": "获取飞书聊天历史记录",

+ 1 - 1
agent/tools/builtin/file/edit.py

@@ -17,7 +17,7 @@ import re
 from agent.tools import tool, ToolResult, ToolContext
 from agent.tools import tool, ToolResult, ToolContext
 
 
 
 
-@tool(description="编辑文件,使用精确字符串替换。支持多种智能匹配策略。")
+@tool(description="编辑文件,使用精确字符串替换。支持多种智能匹配策略。", hidden_params=["context"])
 async def edit_file(
 async def edit_file(
     file_path: str,
     file_path: str,
     old_string: str,
     old_string: str,

+ 1 - 1
agent/tools/builtin/file/glob.py

@@ -19,7 +19,7 @@ from agent.tools import tool, ToolResult, ToolContext
 LIMIT = 100  # 最大返回数量(参考 opencode glob.ts:35)
 LIMIT = 100  # 最大返回数量(参考 opencode glob.ts:35)
 
 
 
 
-@tool(description="使用 glob 模式匹配文件")
+@tool(description="使用 glob 模式匹配文件", hidden_params=["context"])
 async def glob_files(
 async def glob_files(
     pattern: str,
     pattern: str,
     path: Optional[str] = None,
     path: Optional[str] = None,

+ 1 - 1
agent/tools/builtin/file/grep.py

@@ -21,7 +21,7 @@ LIMIT = 100  # 最大返回匹配数(参考 opencode grep.ts:107)
 MAX_LINE_LENGTH = 2000  # 最大行长度(参考 opencode grep.ts:10)
 MAX_LINE_LENGTH = 2000  # 最大行长度(参考 opencode grep.ts:10)
 
 
 
 
-@tool(description="在文件内容中搜索模式")
+@tool(description="在文件内容中搜索模式", hidden_params=["context"])
 async def grep_content(
 async def grep_content(
     pattern: str,
     pattern: str,
     path: Optional[str] = None,
     path: Optional[str] = None,

+ 1 - 1
agent/tools/builtin/file/read.py

@@ -27,7 +27,7 @@ MAX_LINE_LENGTH = 2000
 MAX_BYTES = 50 * 1024  # 50KB
 MAX_BYTES = 50 * 1024  # 50KB
 
 
 
 
-@tool(description="读取文件内容,支持文本文件、图片、PDF 等多种格式,也支持 HTTP/HTTPS URL")
+@tool(description="读取文件内容,支持文本文件、图片、PDF 等多种格式,也支持 HTTP/HTTPS URL", hidden_params=["context"])
 async def read_file(
 async def read_file(
     file_path: str,
     file_path: str,
     offset: int = 0,
     offset: int = 0,

+ 1 - 1
agent/tools/builtin/file/write.py

@@ -16,7 +16,7 @@ import difflib
 from agent.tools import tool, ToolResult, ToolContext
 from agent.tools import tool, ToolResult, ToolContext
 
 
 
 
-@tool(description="写入文件内容(创建新文件、覆盖现有文件或追加内容)")
+@tool(description="写入文件内容(创建新文件、覆盖现有文件或追加内容)", hidden_params=["context"])
 async def write_file(
 async def write_file(
     file_path: str,
     file_path: str,
     content: str,
     content: str,

+ 1 - 1
agent/tools/builtin/glob_tool.py

@@ -19,7 +19,7 @@ from agent.tools import tool, ToolResult, ToolContext
 LIMIT = 100  # 最大返回数量(参考 opencode glob.ts:35)
 LIMIT = 100  # 最大返回数量(参考 opencode glob.ts:35)
 
 
 
 
-@tool(description="使用 glob 模式匹配文件")
+@tool(description="使用 glob 模式匹配文件", hidden_params=["context"])
 async def glob_files(
 async def glob_files(
     pattern: str,
     pattern: str,
     path: Optional[str] = None,
     path: Optional[str] = None,

+ 334 - 1055
agent/tools/builtin/knowledge.py

@@ -1,126 +1,198 @@
 """
 """
-原子知识保存工具
+知识管理工具 - KnowHub API 封装
 
 
-提供便捷的 API 让 Agent 快速保存结构化的原子知识
+所有工具通过 HTTP API 调用 KnowHub Server。
 """
 """
 
 
 import os
 import os
-import re
-import json
-import yaml
 import logging
 import logging
-from datetime import datetime
-from pathlib import Path
+import httpx
 from typing import List, Dict, Optional, Any
 from typing import List, Dict, Optional, Any
 from agent.tools import tool, ToolResult, ToolContext
 from agent.tools import tool, ToolResult, ToolContext
-from ...llm.openrouter import openrouter_llm_call
-from agent.core.prompts import (
-    KNOWLEDGE_SEMANTIC_ROUTE_PROMPT_TEMPLATE,
-    KNOWLEDGE_EVOLVE_PROMPT_TEMPLATE,
-    KNOWLEDGE_SLIM_PROMPT_TEMPLATE,
-    build_knowledge_semantic_route_prompt,
-    build_knowledge_evolve_prompt,
-    build_knowledge_slim_prompt,
-    build_experience_entry,
-    REFLECT_PROMPT,
-)
-from agent.llm.openrouter import openrouter_llm_call
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
+# KnowHub Server API 地址
+KNOWHUB_API = os.getenv("KNOWHUB_API", "http://localhost:8000")
 
 
-def _generate_knowledge_id() -> str:
-    """生成知识原子 ID(带微秒和随机后缀避免冲突)"""
-    import uuid
-    timestamp = datetime.now().strftime('%Y%m%d-%H%M%S')
-    random_suffix = uuid.uuid4().hex[:4]
-    return f"knowledge-{timestamp}-{random_suffix}"
 
 
+@tool(hidden_params=["context"])
+async def knowledge_search(
+    query: str,
+    top_k: int = 5,
+    min_score: int = 3,
+    types: Optional[List[str]] = None,
+    owner: Optional[str] = None,
+    context: Optional[ToolContext] = None,
+) -> ToolResult:
+    """
+    检索知识(两阶段:语义路由 + 质量精排)
+
+    Args:
+        query: 搜索查询(任务描述)
+        top_k: 返回数量(默认 5)
+        min_score: 最低评分过滤(默认 3)
+        types: 按类型过滤(user_profile/strategy/tool/usecase/definition/plan)
+        owner: 按所有者过滤(可选)
+        context: 工具上下文
+
+    Returns:
+        相关知识列表
+    """
+    try:
+        params = {
+            "q": query,
+            "top_k": top_k,
+            "min_score": min_score,
+        }
+        if types:
+            params["types"] = ",".join(types)
+        if owner:
+            params["owner"] = owner
+
+        async with httpx.AsyncClient(timeout=60.0) as client:
+            response = await client.get(f"{KNOWHUB_API}/api/knowledge/search", params=params)
+            response.raise_for_status()
+            data = response.json()
+
+        results = data.get("results", [])
+        count = data.get("count", 0)
+
+        if not results:
+            return ToolResult(
+                title="🔍 未找到相关知识",
+                output=f"查询: {query}\n\n知识库中暂无相关的高质量知识。",
+                long_term_memory=f"知识检索: 未找到相关知识 - {query[:50]}"
+            )
+
+        # 格式化输出
+        output_lines = [f"查询: {query}\n", f"找到 {count} 条相关知识:\n"]
+
+        for idx, item in enumerate(results, 1):
+            eval_data = item.get("eval", {})
+            score = eval_data.get("score", 3)
+            output_lines.append(f"\n### {idx}. [{item['id']}] (⭐ {score})")
+            output_lines.append(f"**任务**: {item['task'][:150]}...")
+            output_lines.append(f"**内容**: {item['content'][:200]}...")
+
+        return ToolResult(
+            title="✅ 知识检索成功",
+            output="\n".join(output_lines),
+            long_term_memory=f"知识检索: 找到 {count} 条相关知识 - {query[:50]}",
+            metadata={
+                "count": count,
+                "knowledge_ids": [item["id"] for item in results],
+                "items": results
+            }
+        )
 
 
-def _format_yaml_list(items: List[str], indent: int = 2) -> str:
-    """格式化 YAML 列表"""
-    if not items:
-        return "[]"
-    indent_str = " " * indent
-    return "\n" + "\n".join(f"{indent_str}- {item}" for item in items)
+    except Exception as e:
+        logger.error(f"知识检索失败: {e}")
+        return ToolResult(
+            title="❌ 检索失败",
+            output=f"错误: {str(e)}",
+            error=str(e)
+        )
 
 
 
 
-@tool()
-async def save_knowledge(
-    scenario: str,
+@tool(
+    hidden_params=["context", "owner"],
+    inject_params={
+        "owner": lambda ctx: ctx.get("knowledge_config", {}).get("owner") if ctx else None,
+        "tags": lambda ctx, args: {
+            **ctx.get("knowledge_config", {}).get("default_tags", {}),
+            **(args.get("tags") or {})
+        } if ctx else args.get("tags"),
+        "scopes": lambda ctx, args: (args.get("scopes") or []) + (ctx.get("knowledge_config", {}).get("default_scopes") or []) if ctx else args.get("scopes"),
+    }
+)
+async def knowledge_save(
+    task: str,
     content: str,
     content: str,
-    tags_type: List[str],
+    types: List[str],
+    tags: Optional[Dict[str, str]] = None,
+    scopes: Optional[List[str]] = None,
+    owner: Optional[str] = None,
+    resource_ids: Optional[List[str]] = None,
+    source_name: str = "",
+    source_category: str = "exp",
     urls: List[str] = None,
     urls: List[str] = None,
     agent_id: str = "research_agent",
     agent_id: str = "research_agent",
+    submitted_by: str = "",
     score: int = 3,
     score: int = 3,
-    trace_id: str = "",
+    message_id: str = "",
+    context: Optional[ToolContext] = None,
 ) -> ToolResult:
 ) -> ToolResult:
     """
     """
-    保存原子知识到本地文件(JSON 格式)
+    保存新知识
 
 
     Args:
     Args:
-        scenario: 任务描述(在什么情景下 + 要完成什么目标 + 得到能达成一个什么结果)
+        task: 任务描述(在什么情景下 + 要完成什么目标)
         content: 核心内容
         content: 核心内容
-        tags_type: 知识类型标签,可选:tool, usercase, definition, plan, strategy
-        urls: 参考来源链接列表(论文/GitHub/博客等)
+        types: 知识类型标签,可选:user_profile, strategy, tool, usecase, definition, plan
+        tags: 业务标签(JSON 对象)
+        scopes: 可见范围(默认 ["org:cybertogether"])
+        owner: 所有者(默认 agent:{agent_id})
+        resource_ids: 关联的资源 ID 列表(可选)
+        source_name: 来源名称
+        source_category: 来源类别(paper/exp/skill/book)
+        urls: 参考来源链接列表
         agent_id: 执行此调研的 agent ID
         agent_id: 执行此调研的 agent ID
+        submitted_by: 提交者
         score: 初始评分 1-5(默认 3)
         score: 初始评分 1-5(默认 3)
-        trace_id: 当前 trace ID(可选)
+        message_id: 来源 Message ID
+        context: 工具上下文
 
 
     Returns:
     Returns:
         保存结果
         保存结果
     """
     """
     try:
     try:
-        # 生成 ID
-        knowledge_id = _generate_knowledge_id()
-
-        # 准备目录
-        knowledge_dir = Path(".cache/knowledge_atoms")
-        knowledge_dir.mkdir(parents=True, exist_ok=True)
-
-        # 构建文件路径(使用 .json 扩展名)
-        file_path = knowledge_dir / f"{knowledge_id}.json"
-
-        # 构建 JSON 数据结构
-        knowledge_data = {
-            "id": knowledge_id,
-            "trace_id": trace_id or "N/A",
-            "tags": {
-                "type": tags_type
-            },
-            "scenario": scenario,
+        # 设置默认值(在 agent 代码中,不是服务器端)
+        if scopes is None:
+            scopes = ["org:cybertogether"]
+        if owner is None:
+            owner = f"agent:{agent_id}"
+
+        payload = {
+            "message_id": message_id,
+            "types": types,
+            "task": task,
+            "tags": tags or {},
+            "scopes": scopes,
+            "owner": owner,
             "content": content,
             "content": content,
-            "trace": {
+            "resource_ids": resource_ids or [],
+            "source": {
+                "name": source_name,
+                "category": source_category,
                 "urls": urls or [],
                 "urls": urls or [],
                 "agent_id": agent_id,
                 "agent_id": agent_id,
-                "timestamp": datetime.now().isoformat()
+                "submitted_by": submitted_by,
             },
             },
             "eval": {
             "eval": {
                 "score": score,
                 "score": score,
-                "helpful": 0,
-                "harmful": 0,
-                "helpful_history": [],
-                "harmful_history": []
-            },
-            "metrics": {
                 "helpful": 1,
                 "helpful": 1,
-                "harmful": 0
-            },
-            "created_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+                "harmful": 0,
+                "confidence": 0.5,
+            }
         }
         }
 
 
-        # 保存为 JSON 文件
-        with open(file_path, "w", encoding="utf-8") as f:
-            json.dump(knowledge_data, f, ensure_ascii=False, indent=2)
+        async with httpx.AsyncClient(timeout=30.0) as client:
+            response = await client.post(f"{KNOWHUB_API}/api/knowledge", json=payload)
+            response.raise_for_status()
+            data = response.json()
+
+        knowledge_id = data.get("knowledge_id", "unknown")
 
 
         return ToolResult(
         return ToolResult(
-            title="✅ 原子知识已保存",
-            output=f"知识 ID: {knowledge_id}\n文件路径: {file_path}\n\n场景:\n{scenario[:100]}...",
-            long_term_memory=f"保存原子知识: {knowledge_id} - {scenario[:50]}",
-            metadata={"knowledge_id": knowledge_id, "file_path": str(file_path)}
+            title="✅ 知识已保存",
+            output=f"知识 ID: {knowledge_id}\n\n任务:\n{task[:100]}...",
+            long_term_memory=f"保存知识: {knowledge_id} - {task[:50]}",
+            metadata={"knowledge_id": knowledge_id}
         )
         )
 
 
     except Exception as e:
     except Exception as e:
+        logger.error(f"保存知识失败: {e}")
         return ToolResult(
         return ToolResult(
             title="❌ 保存失败",
             title="❌ 保存失败",
             output=f"错误: {str(e)}",
             output=f"错误: {str(e)}",
@@ -128,237 +200,69 @@ async def save_knowledge(
         )
         )
 
 
 
 
-async def extract_and_save_experiences(
-    trace_id: str,
-    reflection_text: str,
-) -> List[str]:
-    """
-    从 LLM 反思文本中提取结构化经验并保存为原子知识库条目(strategy 标签)。
-    
-    Args:
-        trace_id: 当前 Trace ID
-        reflection_text: LLM 生成的原始文本,期望包含 [- [tags] 内容] 格式
-        
-    Returns:
-        包含提取出的知识 ID 列表
-    """
-    if not reflection_text:
-        return []
-
-    # 更灵活的正则解析,匹配 - [intent: ..., state: ...] 经验内容
-    pattern = r"-\s*\[(?P<tags>.*?)\]\s*(?P<content>.*)"
-    matches = list(re.finditer(pattern, reflection_text))
-
-    saved_ids = []
-    
-    for match in matches:
-        tags_str = match.group("tags")
-        content = match.group("content").strip()
-
-        # 提取标签详情
-        intent_match = re.search(r"intent:\s*(.*?)(?:,|$)", tags_str, re.IGNORECASE)
-        state_match = re.search(r"state:\s*(.*?)(?:,|$)", tags_str, re.IGNORECASE)
-
-        intents = [i.strip() for i in intent_match.group(1).split(",")] if intent_match and intent_match.group(1) else []
-        states = [s.strip() for s in state_match.group(1).split(",")] if state_match and state_match.group(1) else []
-
-        # 构建 scenario
-        scenario_parts = []
-        if intents:
-            scenario_parts.append(f"意图: {', '.join(intents)}")
-        if states:
-            scenario_parts.append(f"状态: {', '.join(states)}")
-        scenario = " | ".join(scenario_parts) if scenario_parts else "通用经验"
-
-        # 调用 save_knowledge 直接保存为原子知识
-        res = await save_knowledge(
-            scenario=scenario,
-            content=content,
-            tags_type=["strategy"],
-            agent_id="reflection_logger",
-            trace_id=trace_id
-        )
-        
-        if res.metadata and "knowledge_id" in res.metadata:
-            saved_ids.append(res.metadata["knowledge_id"])
-
-    if saved_ids:
-        logger.info(f"Successfully saved {len(saved_ids)} experiences as atomic strategy entries.")
-
-    return saved_ids
-
-
-async def generate_and_save_reflection(
-    trace_id: str,
-    messages: List[Dict[str, Any]],
-    llm_call_fn: Optional[Any] = None,
-    model: str = "anthropic/claude-3-5-sonnet",
-    focus: Optional[str] = None
-) -> str:
-    """
-    自闭环反思逻辑:构建 Prompt -> 调用 LLM -> 提取并保存经验。
-    
-    Args:
-        trace_id: 当前轨迹 ID
-        messages: 历史对话消息列表 (OpenAI 格式)
-        llm_call_fn: 可选的 LLM 调用函数,默认为 openrouter_llm_call
-        model: 使用的模型
-        focus: 可选的关注焦点
-        
-    Returns:
-        LLM 生成的原始反思文本
-    """
-    # 1. 构建反思 Prompt
-    prompt = REFLECT_PROMPT
-    if focus:
-        prompt += f"\n\n请特别关注:{focus}"
-        
-    reflect_messages = list(messages) + [{"role": "user", "content": prompt}]
-    
-    # 2. 调用 LLM
-    call_fn = llm_call_fn or openrouter_llm_call
-    try:
-        logger.info(f"Starting self-contained reflection for trace {trace_id}...")
-        result = await call_fn(
-            messages=reflect_messages,
-            model=model,
-            temperature=0.2
-        )
-        reflection_text = result.get("content", "").strip()
-    except Exception as e:
-        logger.error(f"LLM call failed during reflection: {e}")
-        raise
-        
-    # 3. 提取并保存经验
-    if reflection_text:
-        await extract_and_save_experiences(
-            trace_id=trace_id,
-            reflection_text=reflection_text
-        )
-    else:
-        logger.warning(f"No reflection content generated for trace {trace_id}")
-        
-    return reflection_text
-
-
-@tool()
-async def update_knowledge(
+@tool(hidden_params=["context"])
+async def knowledge_update(
     knowledge_id: str,
     knowledge_id: str,
-    add_helpful_case: Optional[Dict[str, str]] = None,
-    add_harmful_case: Optional[Dict[str, str]] = None,
+    add_helpful_case: Optional[Dict] = None,
+    add_harmful_case: Optional[Dict] = None,
     update_score: Optional[int] = None,
     update_score: Optional[int] = None,
     evolve_feedback: Optional[str] = None,
     evolve_feedback: Optional[str] = None,
+    context: Optional[ToolContext] = None,
 ) -> ToolResult:
 ) -> ToolResult:
     """
     """
-    更新已有的原子知识的评估反馈
+    更新已有知识的评估反馈
 
 
     Args:
     Args:
-        knowledge_id: 知识 ID(如 research-20260302-001)
-        add_helpful_case: 添加好用的案例 {"case_id": "...", "scenario": "...", "result": "...", "timestamp": "..."}
-        add_harmful_case: 添加不好用的案例 {"case_id": "...", "scenario": "...", "result": "...", "timestamp": "..."}
+        knowledge_id: 知识 ID
+        add_helpful_case: 添加好用的案例
+        add_harmful_case: 添加不好用的案例
         update_score: 更新评分(1-5)
         update_score: 更新评分(1-5)
-        evolve_feedback: 经验进化反馈(当提供时,会使用 LLM 重写知识内容)
+        evolve_feedback: 经验进化反馈(触发 LLM 重写)
+        context: 工具上下文
 
 
     Returns:
     Returns:
         更新结果
         更新结果
     """
     """
     try:
     try:
-        # 查找文件(支持 JSON 和 MD 格式)
-        knowledge_dir = Path(".cache/knowledge_atoms")
-        json_path = knowledge_dir / f"{knowledge_id}.json"
-        md_path = knowledge_dir / f"{knowledge_id}.md"
-
-        file_path = None
-        if json_path.exists():
-            file_path = json_path
-            is_json = True
-        elif md_path.exists():
-            file_path = md_path
-            is_json = False
-        else:
-            return ToolResult(
-                title="❌ 文件不存在",
-                output=f"未找到知识文件: {knowledge_id}",
-                error="文件不存在"
-            )
-
-        # 读取现有内容
-        with open(file_path, "r", encoding="utf-8") as f:
-            content = f.read()
-
-        # 解析数据
-        if is_json:
-            data = json.loads(content)
-        else:
-            # 解析 YAML frontmatter
-            yaml_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
-            if not yaml_match:
-                return ToolResult(
-                    title="❌ 格式错误",
-                    output=f"无法解析知识文件格式: {file_path}",
-                    error="格式错误"
-                )
-            data = yaml.safe_load(yaml_match.group(1))
-
-        # 更新内容
-        updated = False
-        summary = []
-
+        payload = {}
         if add_helpful_case:
         if add_helpful_case:
-            data["eval"]["helpful"] += 1
-            data["eval"]["helpful_history"].append(add_helpful_case)
-            data["metrics"]["helpful"] += 1
-            summary.append(f"添加 helpful 案例: {add_helpful_case.get('case_id')}")
-            updated = True
-
+            payload["add_helpful_case"] = add_helpful_case
         if add_harmful_case:
         if add_harmful_case:
-            data["eval"]["harmful"] += 1
-            data["eval"]["harmful_history"].append(add_harmful_case)
-            data["metrics"]["harmful"] += 1
-            summary.append(f"添加 harmful 案例: {add_harmful_case.get('case_id')}")
-            updated = True
-
+            payload["add_harmful_case"] = add_harmful_case
         if update_score is not None:
         if update_score is not None:
-            data["eval"]["score"] = update_score
-            summary.append(f"更新评分: {update_score}")
-            updated = True
-
-        # 经验进化机制
+            payload["update_score"] = update_score
         if evolve_feedback:
         if evolve_feedback:
-            old_content = data.get("content", "")
-            evolved_content = await _evolve_knowledge_with_llm(old_content, evolve_feedback)
-            data["content"] = evolved_content
-            data["metrics"]["helpful"] += 1
-            summary.append(f"知识进化: 基于反馈重写内容")
-            updated = True
-
-        if not updated:
+            payload["evolve_feedback"] = evolve_feedback
+
+        if not payload:
             return ToolResult(
             return ToolResult(
                 title="⚠️ 无更新",
                 title="⚠️ 无更新",
                 output="未指定任何更新内容",
                 output="未指定任何更新内容",
-                long_term_memory="尝试更新原子知识但未指定更新内容"
+                long_term_memory="尝试更新知识但未指定更新内容"
             )
             )
 
 
-        # 更新时间戳
-        data["updated_at"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+        async with httpx.AsyncClient(timeout=60.0) as client:
+            response = await client.put(f"{KNOWHUB_API}/api/knowledge/{knowledge_id}", json=payload)
+            response.raise_for_status()
 
 
-        # 保存更新
-        if is_json:
-            with open(file_path, "w", encoding="utf-8") as f:
-                json.dump(data, f, ensure_ascii=False, indent=2)
-        else:
-            # 重新生成 YAML frontmatter
-            meta_str = yaml.dump(data, allow_unicode=True).strip()
-            with open(file_path, "w", encoding="utf-8") as f:
-                f.write(f"---\n{meta_str}\n---\n")
+        summary = []
+        if add_helpful_case:
+            summary.append("添加 helpful 案例")
+        if add_harmful_case:
+            summary.append("添加 harmful 案例")
+        if update_score is not None:
+            summary.append(f"更新评分: {update_score}")
+        if evolve_feedback:
+            summary.append("知识进化: 基于反馈重写内容")
 
 
         return ToolResult(
         return ToolResult(
-            title="✅ 原子知识已更新",
-            output=f"知识 ID: {knowledge_id}\n文件路径: {file_path}\n\n更新内容:\n" + "\n".join(f"- {s}" for s in summary),
-            long_term_memory=f"更新原子知识: {knowledge_id}"
+            title="✅ 知识已更新",
+            output=f"知识 ID: {knowledge_id}\n\n更新内容:\n" + "\n".join(f"- {s}" for s in summary),
+            long_term_memory=f"更新知识: {knowledge_id}"
         )
         )
 
 
     except Exception as e:
     except Exception as e:
+        logger.error(f"更新知识失败: {e}")
         return ToolResult(
         return ToolResult(
             title="❌ 更新失败",
             title="❌ 更新失败",
             output=f"错误: {str(e)}",
             output=f"错误: {str(e)}",
@@ -366,68 +270,49 @@ async def update_knowledge(
         )
         )
 
 
 
 
-@tool()
-async def list_knowledge(
-    limit: int = 10,
-    tags_type: Optional[List[str]] = None,
+@tool(hidden_params=["context"])
+async def knowledge_batch_update(
+    feedback_list: List[Dict[str, Any]],
+    context: Optional[ToolContext] = None,
 ) -> ToolResult:
 ) -> ToolResult:
     """
     """
-    列出已保存的原子知识
+    批量反馈知识的有效性
 
 
     Args:
     Args:
-        limit: 返回数量限制(默认 10)
-        tags_type: 按类型过滤(可选)
+        feedback_list: 评价列表,每个元素包含:
+            - knowledge_id: (str) 知识 ID
+            - is_effective: (bool) 是否有效
+            - feedback: (str, optional) 改进建议,若有效且有建议则触发知识进化
+        context: 工具上下文
 
 
     Returns:
     Returns:
-        知识列表
+        批量更新结果
     """
     """
     try:
     try:
-        knowledge_dir = Path(".cache/knowledge_atoms")
-
-        if not knowledge_dir.exists():
-            return ToolResult(
-                title="📂 知识库为空",
-                output="还没有保存任何原子知识",
-                long_term_memory="知识库为空"
-            )
-
-        # 获取所有文件
-        files = sorted(knowledge_dir.glob("*.md"), key=lambda x: x.stat().st_mtime, reverse=True)
-
-        if not files:
+        if not feedback_list:
             return ToolResult(
             return ToolResult(
-                title="📂 知识库为空",
-                output="还没有保存任何原子知识",
-                long_term_memory="知识库为空"
+                title="⚠️ 反馈列表为空",
+                output="未提供任何反馈",
+                long_term_memory="批量更新知识: 反馈列表为空"
             )
             )
 
 
-        # 读取并过滤
-        results = []
-        for file_path in files[:limit]:
-            with open(file_path, "r", encoding="utf-8") as f:
-                content = f.read()
+        payload = {"feedback_list": feedback_list}
 
 
-            # 提取关键信息
-            import re
-            id_match = re.search(r"id: (.+)", content)
-            scenario_match = re.search(r"scenario: \|\n  (.+)", content)
-            score_match = re.search(r"score: (\d+)", content)
+        async with httpx.AsyncClient(timeout=120.0) as client:
+            response = await client.post(f"{KNOWHUB_API}/api/knowledge/batch_update", json=payload)
+            response.raise_for_status()
+            data = response.json()
 
 
-            knowledge_id = id_match.group(1) if id_match else "unknown"
-            scenario = scenario_match.group(1) if scenario_match else "N/A"
-            score = score_match.group(1) if score_match else "N/A"
-
-            results.append(f"- [{knowledge_id}] (⭐{score}) {scenario[:60]}...")
-
-        output = f"共找到 {len(files)} 条原子知识,显示最近 {len(results)} 条:\n\n" + "\n".join(results)
+        updated = data.get("updated", 0)
 
 
         return ToolResult(
         return ToolResult(
-            title="📚 原子知识列表",
-            output=output,
-            long_term_memory=f"列出 {len(results)} 条原子知识"
+            title="✅ 批量更新完成",
+            output=f"成功更新 {updated} 条知识",
+            long_term_memory=f"批量更新知识: 成功 {updated} 条"
         )
         )
 
 
     except Exception as e:
     except Exception as e:
+        logger.error(f"列出知识失败: {e}")
         return ToolResult(
         return ToolResult(
             title="❌ 列表失败",
             title="❌ 列表失败",
             output=f"错误: {str(e)}",
             output=f"错误: {str(e)}",
@@ -435,827 +320,221 @@ async def list_knowledge(
         )
         )
 
 
 
 
-# ===== 语义检索功能 =====
-
-async def _route_knowledge_by_llm(query_text: str, metadata_list: List[Dict], k: int = 5) -> List[str]:
-    """
-    第一阶段:语义路由。
-    让 LLM 挑选出 2*k 个语义相关的 ID。
-    """
-    if not metadata_list:
-        return []
-
-    # 扩大筛选范围到 2*k
-    routing_k = k * 2
-
-    routing_data = [
-        {
-            "id": m["id"],
-            "tags": m["tags"],
-            "scenario": m["scenario"][:100]  # 只取前100字符
-        } for m in metadata_list
-    ]
-
-    prompt = build_knowledge_semantic_route_prompt(
-        query_text=query_text,
-        routing_data=json.dumps(routing_data, ensure_ascii=False, indent=1),
-        routing_k=routing_k
-    )
-
-    try:
-        print(f"\n[Step 1: 知识语义路由] 任务: '{query_text}' | 候选总数: {len(metadata_list)} | 目标提取数: {routing_k}")
-
-        response = await openrouter_llm_call(
-            messages=[{"role": "user", "content": prompt}],
-            model="google/gemini-2.0-flash-001"
-        )
-
-        content = response.get("content", "").strip()
-        selected_ids = [idx.strip() for idx in re.split(r'[,\s]+', content) if idx.strip().startswith(("knowledge-", "research-"))]
-
-        print(f"[Step 1: 知识语义路由] LLM 初选 ID ({len(selected_ids)}个): {selected_ids}")
-        return selected_ids
-    except Exception as e:
-        logger.error(f"LLM 知识路由失败: {e}")
-        return []
-
-
-async def _evolve_knowledge_with_llm(old_content: str, feedback: str) -> str:
-    """
-    使用 LLM 进行知识进化重写(类似经验进化机制)
-    """
-    prompt = build_knowledge_evolve_prompt(old_content, feedback)
-    try:
-        response = await openrouter_llm_call(
-            messages=[{"role": "user", "content": prompt}],
-            model="google/gemini-2.0-flash-001"
-        )
-
-        evolved_content = response.get("content", "").strip()
-
-        # 简单安全校验:如果 LLM 返回太短或为空,回退到原内容+追加
-        if len(evolved_content) < 5:
-            raise ValueError("LLM output too short")
-
-        return evolved_content
-
-    except Exception as e:
-        logger.warning(f"知识进化失败,采用追加模式回退: {e}")
-        timestamp = datetime.now().strftime('%Y-%m-%d')
-        return f"{old_content}\n\n---\n[Update {timestamp}]: {feedback}"
-
-
-async def _route_knowledge_by_llm(query_text: str, metadata_list: List[Dict], k: int = 5) -> List[str]:
-    """
-    第一阶段:语义路由。
-    让 LLM 挑选出 2*k 个语义相关的 ID。
-    """
-    if not metadata_list:
-        return []
-
-    # 扩大筛选范围到 2*k
-    routing_k = k * 2
-
-    routing_data = [
-        {
-            "id": m["id"],
-            "tags": m["tags"],
-            "scenario": m["scenario"][:100]  # 只取前100字符
-        } for m in metadata_list
-    ]
-
-    prompt = build_knowledge_semantic_route_prompt(
-        query_text=query_text,
-        routing_data=json.dumps(routing_data, ensure_ascii=False, indent=1),
-        routing_k=routing_k
-    )
-
-    try:
-        print(f"\n[Step 1: 知识语义路由] 任务: '{query_text}' | 候选总数: {len(metadata_list)} | 目标提取数: {routing_k}")
-
-        response = await openrouter_llm_call(
-            messages=[{"role": "user", "content": prompt}],
-            model="google/gemini-2.0-flash-001"
-        )
-
-        content = response.get("content", "").strip()
-        selected_ids = [idx.strip() for idx in re.split(r'[,\s]+', content) if idx.strip().startswith(("knowledge-", "research-"))]
-
-        print(f"[Step 1: 知识语义路由] LLM 初选 ID ({len(selected_ids)}个): {selected_ids}")
-        return selected_ids
-    except Exception as e:
-        logger.error(f"LLM 知识路由失败: {e}")
-        return []
-
-
-async def _get_structured_knowledge(
-    query_text: str,
-    top_k: int = 5,
-    min_score: int = 3,
-    context: Optional[Any] = None,
-    tags_filter: Optional[List[str]] = None
-) -> List[Dict]:
-    """
-    语义检索原子知识(包括经验)
-
-    1. 解析知识库文件(支持 JSON 和 YAML 格式)
-    2. 语义路由:提取 2*k 个 ID
-    3. 质量精排:基于评分筛选出最终的 k 个
-
-    Args:
-        query_text: 查询文本
-        top_k: 返回数量
-        min_score: 最低评分过滤
-        context: 上下文(兼容 experience 接口)
-        tags_filter: 标签过滤(如 ["strategy"] 只返回经验)
-    """
-    knowledge_dir = Path(".cache/knowledge_atoms")
-
-    if not knowledge_dir.exists():
-        print(f"[Knowledge System] 警告: 知识库目录不存在 ({knowledge_dir})")
-        return []
-
-    # 同时支持 .json 和 .md 文件
-    json_files = list(knowledge_dir.glob("*.json"))
-    md_files = list(knowledge_dir.glob("*.md"))
-    files = json_files + md_files
-
-    if not files:
-        print(f"[Knowledge System] 警告: 知识库为空")
-        return []
-
-    # --- 阶段 1: 解析所有知识文件 ---
-    content_map = {}
-    metadata_list = []
-
-    for file_path in files:
-        try:
-            with open(file_path, "r", encoding="utf-8") as f:
-                content = f.read()
-
-            # 根据文件扩展名选择解析方式
-            if file_path.suffix == ".json":
-                # 解析 JSON 格式
-                metadata = json.loads(content)
-            else:
-                # 解析 YAML frontmatter(兼容旧格式)
-                yaml_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
-                if not yaml_match:
-                    logger.warning(f"跳过无效文件: {file_path}")
-                    continue
-                metadata = yaml.safe_load(yaml_match.group(1))
-
-            if not isinstance(metadata, dict):
-                logger.warning(f"跳过损坏的知识文件: {file_path}")
-                continue
-
-            kid = metadata.get("id")
-            if not kid:
-                logger.warning(f"跳过缺少 id 的知识文件: {file_path}")
-                continue
-
-            # 提取 scenario 和 content
-            scenario = metadata.get("scenario", "").strip()
-            content_text = metadata.get("content", "").strip()
-
-            # 标签过滤
-            tags = metadata.get("tags", {})
-            if tags_filter:
-                # 检查 tags.type 是否包含任何过滤标签
-                tag_types = tags.get("type", [])
-                if isinstance(tag_types, str):
-                    tag_types = [tag_types]
-                if not any(tag in tag_types for tag in tags_filter):
-                    continue  # 跳过不匹配的标签
-
-            meta_item = {
-                "id": kid,
-                "tags": tags,
-                "scenario": scenario,
-                "score": metadata.get("eval", {}).get("score", 3),
-                "helpful": metadata.get("metrics", {}).get("helpful", 0),
-                "harmful": metadata.get("metrics", {}).get("harmful", 0),
-            }
-            metadata_list.append(meta_item)
-            content_map[kid] = {
-                "scenario": scenario,
-                "content": content_text,
-                "tags": tags,
-                "score": meta_item["score"],
-                "helpful": meta_item["helpful"],
-                "harmful": meta_item["harmful"],
-            }
-        except Exception as e:
-            logger.error(f"解析知识文件失败 {file_path}: {e}")
-            continue
-
-    if not metadata_list:
-        print(f"[Knowledge System] 警告: 没有有效的知识条目")
-        return []
-
-    # --- 阶段 2: 语义路由 (取 2*k) ---
-    candidate_ids = await _route_knowledge_by_llm(query_text, metadata_list, k=top_k)
-
-    # --- 阶段 3: 质量精排 (根据评分和反馈选出最终的 k) ---
-    print(f"[Step 2: 知识质量精排] 正在根据评分和反馈进行打分...")
-    scored_items = []
-
-    for kid in candidate_ids:
-        if kid in content_map:
-            item = content_map[kid]
-            score = item["score"]
-            helpful = item["helpful"]
-            harmful = item["harmful"]
-
-            # 计算综合分:基础分 + helpful - harmful*2
-            quality_score = score + helpful - (harmful * 2.0)
-
-            # 过滤门槛:评分低于 min_score 或质量分过低
-            if score < min_score or quality_score < 0:
-                print(f"  - 剔除低质量知识: {kid} (Score: {score}, Helpful: {helpful}, Harmful: {harmful})")
-                continue
-
-            scored_items.append({
-                "id": kid,
-                "scenario": item["scenario"],
-                "content": item["content"],
-                "tags": item["tags"],
-                "score": score,
-                "quality_score": quality_score,
-                "metrics": {
-                    "helpful": helpful,
-                    "harmful": harmful
-                }
-            })
-
-    # 按照质量分排序
-    final_sorted = sorted(scored_items, key=lambda x: x["quality_score"], reverse=True)
-
-    # 截取最终的 top_k
-    result = final_sorted[:top_k]
-
-    print(f"[Step 2: 知识质量精排] 最终选定知识: {[it['id'] for it in result]}")
-    print(f"[Knowledge System] 检索结束。\n")
-    return result
-
-
-@tool()
-async def search_knowledge(
-    query: str,
-    top_k: int = 5,
-    min_score: int = 3,
-    tags_type: Optional[List[str]] = None,
+@tool(hidden_params=["context"])
+async def knowledge_list(
+    limit: int = 10,
+    types: Optional[List[str]] = None,
+    scopes: Optional[List[str]] = None,
     context: Optional[ToolContext] = None,
     context: Optional[ToolContext] = None,
 ) -> ToolResult:
 ) -> ToolResult:
     """
     """
-    语义检索原子知识库
+    列出已保存的知识
 
 
     Args:
     Args:
-        query: 搜索查询(任务描述)
-        top_k: 返回数量(默认 5)
-        min_score: 最低评分过滤(默认 3)
-        tags_type: 按类型过滤(tool/usercase/definition/plan)
+        limit: 返回数量限制(默认 10)
+        types: 按类型过滤(可选)
+        scopes: 按范围过滤(可选)
         context: 工具上下文
         context: 工具上下文
 
 
     Returns:
     Returns:
-        相关知识列表
+        知识列表
     """
     """
     try:
     try:
-        relevant_items = await _get_structured_knowledge(
-            query_text=query,
-            top_k=top_k,
-            min_score=min_score
-        )
+        params = {"limit": limit}
+        if types:
+            params["types"] = ",".join(types)
+        if scopes:
+            params["scopes"] = ",".join(scopes)
+
+        async with httpx.AsyncClient(timeout=30.0) as client:
+            response = await client.get(f"{KNOWHUB_API}/api/knowledge", params=params)
+            response.raise_for_status()
+            data = response.json()
+
+        results = data.get("results", [])
+        count = data.get("count", 0)
 
 
-        if not relevant_items:
+        if not results:
             return ToolResult(
             return ToolResult(
-                title="🔍 未找到相关知识",
-                output=f"查询: {query}\n\n知识库中暂无相关的高质量知识。建议进行调研。",
-                long_term_memory=f"知识检索: 未找到相关知识 - {query[:50]}"
+                title="📂 知识库为空",
+                output="还没有保存任何知识",
+                long_term_memory="知识库为空"
             )
             )
 
 
-        # 格式化输出
-        output_lines = [f"查询: {query}\n", f"找到 {len(relevant_items)} 条相关知识:\n"]
-
-        for idx, item in enumerate(relevant_items, 1):
-            output_lines.append(f"\n### {idx}. [{item['id']}] (⭐ {item['score']})")
-            output_lines.append(f"**场景**: {item['scenario'][:150]}...")
-            output_lines.append(f"**内容**: {item['content'][:200]}...")
+        output_lines = [f"共找到 {count} 条知识:\n"]
+        for item in results:
+            eval_data = item.get("eval", {})
+            score = eval_data.get("score", 3)
+            output_lines.append(f"- [{item['id']}] (⭐{score}) {item['task'][:60]}...")
 
 
         return ToolResult(
         return ToolResult(
-            title="✅ 知识检索成功",
+            title="📚 知识列表",
             output="\n".join(output_lines),
             output="\n".join(output_lines),
-            long_term_memory=f"知识检索: 找到 {len(relevant_items)} 条相关知识 - {query[:50]}",
-            metadata={
-                "count": len(relevant_items),
-                "knowledge_ids": [item["id"] for item in relevant_items],
-                "items": relevant_items
-            }
+            long_term_memory=f"列出 {count} 条知识"
         )
         )
 
 
     except Exception as e:
     except Exception as e:
-        logger.error(f"知识检索失败: {e}")
+        logger.error(f"列出知识失败: {e}")
         return ToolResult(
         return ToolResult(
-            title="❌ 检索失败",
+            title="❌ 列表失败",
             output=f"错误: {str(e)}",
             output=f"错误: {str(e)}",
             error=str(e)
             error=str(e)
         )
         )
 
 
 
 
-@tool(description="通过两阶段检索获取最相关的历史经验(strategy 标签的知识)")
-async def get_experience(
-    query: str,
-    k: int = 3,
+@tool(hidden_params=["context"])
+async def knowledge_slim(
+    model: str = "google/gemini-2.0-flash-001",
     context: Optional[ToolContext] = None,
     context: Optional[ToolContext] = None,
 ) -> ToolResult:
 ) -> ToolResult:
     """
     """
-    检索历史经验(兼容旧接口,实际调用 search_knowledge 并过滤 strategy 标签)
+    知识库瘦身:调用顶级大模型,将知识库中语义相似的知识合并精简
 
 
     Args:
     Args:
-        query: 搜索查询(任务描述)
-        k: 返回数量(默认 3)
+        model: 使用的模型(默认 gemini-2.0-flash-001)
         context: 工具上下文
         context: 工具上下文
 
 
     Returns:
     Returns:
-        相关经验列表
+        瘦身结果报告
     """
     """
     try:
     try:
-        relevant_items = await _get_structured_knowledge(
-            query_text=query,
-            top_k=k,
-            min_score=1,  # 经验的评分门槛较低
-            context=context,
-            tags_filter=["strategy"]  # 只返回经验
-        )
-
-        if not relevant_items:
-            return ToolResult(
-                title="🔍 未找到相关经验",
-                output=f"查询: {query}\n\n经验库中暂无相关的经验。",
-                long_term_memory=f"经验检索: 未找到相关经验 - {query[:50]}",
-                metadata={"items": [], "count": 0}
-            )
+        async with httpx.AsyncClient(timeout=300.0) as client:
+            response = await client.post(f"{KNOWHUB_API}/api/knowledge/slim", params={"model": model})
+            response.raise_for_status()
+            data = response.json()
 
 
-        # 格式化输出(兼容旧格式)
-        output_lines = [f"查询: {query}\n", f"找到 {len(relevant_items)} 条相关经验:\n"]
+        before = data.get("before", 0)
+        after = data.get("after", 0)
+        report = data.get("report", "")
 
 
-        for idx, item in enumerate(relevant_items, 1):
-            output_lines.append(f"\n### {idx}. [{item['id']}]")
-            output_lines.append(f"{item['content'][:300]}...")
+        result = f"瘦身完成:{before} → {after} 条知识"
+        if report:
+            result += f"\n{report}"
 
 
         return ToolResult(
         return ToolResult(
-            title="✅ 经验检索成功",
-            output="\n".join(output_lines),
-            long_term_memory=f"经验检索: 找到 {len(relevant_items)} 条相关经验 - {query[:50]}",
-            metadata={
-                "items": relevant_items,
-                "count": len(relevant_items)
-            }
+            title="✅ 知识库瘦身完成",
+            output=result,
+            long_term_memory=f"知识库瘦身: {before} → {after} 条"
         )
         )
 
 
     except Exception as e:
     except Exception as e:
-        logger.error(f"经验检索失败: {e}")
+        logger.error(f"知识库瘦身失败: {e}")
         return ToolResult(
         return ToolResult(
-            title="❌ 检索失败",
+            title="❌ 瘦身失败",
             output=f"错误: {str(e)}",
             output=f"错误: {str(e)}",
             error=str(e)
             error=str(e)
         )
         )
 
 
 
 
-# ===== 批量更新功能(类似经验机制)=====
-
-async def _batch_update_knowledge(
-    update_map: Dict[str, Dict[str, Any]],
-    context: Optional[Any] = None
-) -> int:
-    """
-    内部函数:批量更新知识(兼容 experience 接口)
-
-    Args:
-        update_map: 更新映射 {knowledge_id: {"action": "helpful/harmful/evolve", "feedback": "..."}}
-        context: 上下文(兼容 experience 接口)
+# ==================== Resource 资源管理工具 ====================
 
 
-    Returns:
-        成功更新的数量
-    """
-    if not update_map:
-        return 0
-
-    knowledge_dir = Path(".cache/knowledge_atoms")
-    if not knowledge_dir.exists():
-        return 0
-
-    success_count = 0
-    evolution_tasks = []
-    evolution_registry = {}  # task_idx -> (file_path, data)
-
-    for knowledge_id, instr in update_map.items():
-        try:
-            # 查找文件
-            json_path = knowledge_dir / f"{knowledge_id}.json"
-            md_path = knowledge_dir / f"{knowledge_id}.md"
-
-            file_path = None
-            is_json = False
-            if json_path.exists():
-                file_path = json_path
-                is_json = True
-            elif md_path.exists():
-                file_path = md_path
-                is_json = False
-            else:
-                continue
-
-            # 读取并解析
-            with open(file_path, "r", encoding="utf-8") as f:
-                content = f.read()
-
-            if is_json:
-                data = json.loads(content)
-            else:
-                yaml_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
-                if not yaml_match:
-                    continue
-                data = yaml.safe_load(yaml_match.group(1))
-
-            # 更新 metrics
-            action = instr.get("action")
-            feedback = instr.get("feedback", "")
-
-            # 处理 mixed 中间态
-            if action == "mixed":
-                data["metrics"]["helpful"] = data.get("metrics", {}).get("helpful", 0) + 1
-                action = "evolve"
-
-            if action == "helpful":
-                data["metrics"]["helpful"] = data.get("metrics", {}).get("helpful", 0) + 1
-            elif action == "harmful":
-                data["metrics"]["harmful"] = data.get("metrics", {}).get("harmful", 0) + 1
-            elif action == "evolve" and feedback:
-                # 注册进化任务
-                old_content = data.get("content", "")
-                task = _evolve_knowledge_with_llm(old_content, feedback)
-                evolution_tasks.append(task)
-                evolution_registry[len(evolution_tasks) - 1] = (file_path, data, is_json)
-                data["metrics"]["helpful"] = data.get("metrics", {}).get("helpful", 0) + 1
-
-            data["updated_at"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-
-            # 如果不需要进化,直接保存
-            if action != "evolve" or not feedback:
-                if is_json:
-                    with open(file_path, "w", encoding="utf-8") as f:
-                        json.dump(data, f, ensure_ascii=False, indent=2)
-                else:
-                    meta_str = yaml.dump(data, allow_unicode=True).strip()
-                    with open(file_path, "w", encoding="utf-8") as f:
-                        f.write(f"---\n{meta_str}\n---\n")
-                success_count += 1
-
-        except Exception as e:
-            logger.error(f"更新知识失败 {knowledge_id}: {e}")
-            continue
-
-    # 并发进化
-    if evolution_tasks:
-        import asyncio
-        print(f"🧬 并发处理 {len(evolution_tasks)} 条知识进化...")
-        evolved_results = await asyncio.gather(*evolution_tasks)
-
-        # 回填进化结果
-        for task_idx, (file_path, data, is_json) in evolution_registry.items():
-            data["content"] = evolved_results[task_idx].strip()
-
-            if is_json:
-                with open(file_path, "w", encoding="utf-8") as f:
-                    json.dump(data, f, ensure_ascii=False, indent=2)
-            else:
-                meta_str = yaml.dump(data, allow_unicode=True).strip()
-                with open(file_path, "w", encoding="utf-8") as f:
-                    f.write(f"---\n{meta_str}\n---\n")
-            success_count += 1
-
-    return success_count
-
-
-@tool()
-async def batch_update_knowledge(
-    feedback_list: List[Dict[str, Any]],
+@tool(hidden_params=["context"])
+async def resource_save(
+    resource_id: str,
+    title: str,
+    body: str,
+    content_type: str = "text",
+    secure_body: str = "",
+    metadata: Optional[Dict[str, Any]] = None,
+    submitted_by: str = "",
     context: Optional[ToolContext] = None,
     context: Optional[ToolContext] = None,
 ) -> ToolResult:
 ) -> ToolResult:
     """
     """
-    批量反馈知识的有效性(类似经验机制
+    保存资源(代码片段、凭证、Cookie 等)
 
 
     Args:
     Args:
-        feedback_list: 评价列表,每个元素包含:
-            - knowledge_id: (str) 知识 ID
-            - is_effective: (bool) 是否有效
-            - feedback: (str, optional) 改进建议,若有效且有建议则触发知识进化
+        resource_id: 资源 ID(层级路径,如 "code/selenium/login" 或 "credentials/website_a")
+        title: 资源标题
+        body: 公开内容(明文存储,可搜索)
+        content_type: 内容类型(text/code/credential/cookie)
+        secure_body: 敏感内容(加密存储,需要组织密钥访问)
+        metadata: 元数据(如 {"language": "python", "acquired_at": "2026-03-06T10:00:00Z"})
+        submitted_by: 提交者
+        context: 工具上下文
 
 
     Returns:
     Returns:
-        批量更新结果
+        保存结果
     """
     """
     try:
     try:
-        if not feedback_list:
-            return ToolResult(
-                title="⚠️ 反馈列表为空",
-                output="未提供任何反馈",
-                long_term_memory="批量更新知识: 反馈列表为空"
-            )
-
-        knowledge_dir = Path(".cache/knowledge_atoms")
-        if not knowledge_dir.exists():
-            return ToolResult(
-                title="❌ 知识库不存在",
-                output="知识库目录不存在",
-                error="知识库不存在"
-            )
+        payload = {
+            "id": resource_id,
+            "title": title,
+            "body": body,
+            "secure_body": secure_body,
+            "content_type": content_type,
+            "metadata": metadata or {},
+            "submitted_by": submitted_by,
+        }
 
 
-        success_count = 0
-        failed_items = []
-
-        for item in feedback_list:
-            knowledge_id = item.get("knowledge_id")
-            is_effective = item.get("is_effective")
-            feedback = item.get("feedback", "")
-
-            if not knowledge_id:
-                failed_items.append({"id": "unknown", "reason": "缺少 knowledge_id"})
-                continue
-
-            try:
-                # 查找文件
-                json_path = knowledge_dir / f"{knowledge_id}.json"
-                md_path = knowledge_dir / f"{knowledge_id}.md"
-
-                file_path = None
-                is_json = False
-                if json_path.exists():
-                    file_path = json_path
-                    is_json = True
-                elif md_path.exists():
-                    file_path = md_path
-                    is_json = False
-                else:
-                    failed_items.append({"id": knowledge_id, "reason": "文件不存在"})
-                    continue
-
-                # 读取并解析
-                with open(file_path, "r", encoding="utf-8") as f:
-                    content = f.read()
-
-                if is_json:
-                    data = json.loads(content)
-                else:
-                    yaml_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
-                    if not yaml_match:
-                        failed_items.append({"id": knowledge_id, "reason": "格式错误"})
-                        continue
-                    data = yaml.safe_load(yaml_match.group(1))
-
-                # 更新 metrics
-                if is_effective:
-                    data["metrics"]["helpful"] = data.get("metrics", {}).get("helpful", 0) + 1
-                    # 如果有反馈建议,触发进化
-                    if feedback:
-                        old_content = data.get("content", "")
-                        evolved_content = await _evolve_knowledge_with_llm(old_content, feedback)
-                        data["content"] = evolved_content
-                else:
-                    data["metrics"]["harmful"] = data.get("metrics", {}).get("harmful", 0) + 1
-
-                data["updated_at"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-
-                # 保存
-                if is_json:
-                    with open(file_path, "w", encoding="utf-8") as f:
-                        json.dump(data, f, ensure_ascii=False, indent=2)
-                else:
-                    meta_str = yaml.dump(data, allow_unicode=True).strip()
-                    with open(file_path, "w", encoding="utf-8") as f:
-                        f.write(f"---\n{meta_str}\n---\n")
-
-                success_count += 1
-
-            except Exception as e:
-                failed_items.append({"id": knowledge_id, "reason": str(e)})
-                continue
-
-        output_lines = [f"成功更新 {success_count} 条知识"]
-        if failed_items:
-            output_lines.append(f"\n失败 {len(failed_items)} 条:")
-            for item in failed_items:
-                output_lines.append(f"  - {item['id']}: {item['reason']}")
+        async with httpx.AsyncClient(timeout=30.0) as client:
+            response = await client.post(f"{KNOWHUB_API}/api/resource", json=payload)
+            response.raise_for_status()
+            data = response.json()
 
 
         return ToolResult(
         return ToolResult(
-            title="✅ 批量更新完成",
-            output="\n".join(output_lines),
-            long_term_memory=f"批量更新知识: 成功 {success_count} 条,失败 {len(failed_items)} 条"
+            title="✅ 资源已保存",
+            output=f"资源 ID: {resource_id}\n类型: {content_type}\n标题: {title}",
+            long_term_memory=f"保存资源: {resource_id} ({content_type})",
+            metadata={"resource_id": resource_id}
         )
         )
 
 
     except Exception as e:
     except Exception as e:
-        logger.error(f"批量更新知识失败: {e}")
+        logger.error(f"保存资源失败: {e}")
         return ToolResult(
         return ToolResult(
-            title="❌ 批量更新失败",
+            title="❌ 保存失败",
             output=f"错误: {str(e)}",
             output=f"错误: {str(e)}",
             error=str(e)
             error=str(e)
         )
         )
 
 
 
 
-# ===== 知识库瘦身功能(类似经验机制)=====
-
-@tool()
-async def slim_knowledge(
-    model: str = "anthropic/claude-sonnet-4.5",
+@tool(hidden_params=["context"])
+async def resource_get(
+    resource_id: str,
+    org_key: Optional[str] = None,
     context: Optional[ToolContext] = None,
     context: Optional[ToolContext] = None,
 ) -> ToolResult:
 ) -> ToolResult:
     """
     """
-    知识库瘦身:调用顶级大模型,将知识库中语义相似的知识合并精简
+    获取资源内容
 
 
     Args:
     Args:
-        model: 使用的模型(默认 claude-sonnet-4.5)
+        resource_id: 资源 ID(层级路径)
+        org_key: 组织密钥(用于解密敏感内容,可选)
         context: 工具上下文
         context: 工具上下文
 
 
     Returns:
     Returns:
-        瘦身结果报告
+        资源内容
     """
     """
     try:
     try:
-        knowledge_dir = Path(".cache/knowledge_atoms")
-
-        if not knowledge_dir.exists():
-            return ToolResult(
-                title="📂 知识库不存在",
-                output="知识库目录不存在,无需瘦身",
-                long_term_memory="知识库瘦身: 目录不存在"
-            )
-
-        # 获取所有文件
-        json_files = list(knowledge_dir.glob("*.json"))
-        md_files = list(knowledge_dir.glob("*.md"))
-        files = json_files + md_files
-
-        if len(files) < 2:
-            return ToolResult(
-                title="📂 知识库过小",
-                output=f"知识库仅有 {len(files)} 条,无需瘦身",
-                long_term_memory=f"知识库瘦身: 仅有 {len(files)} 条"
+        headers = {}
+        if org_key:
+            headers["X-Org-Key"] = org_key
+
+        async with httpx.AsyncClient(timeout=30.0) as client:
+            response = await client.get(
+                f"{KNOWHUB_API}/api/resource/{resource_id}",
+                headers=headers
             )
             )
+            response.raise_for_status()
+            data = response.json()
 
 
-        # 解析所有知识
-        parsed = []
-        for file_path in files:
-            try:
-                with open(file_path, "r", encoding="utf-8") as f:
-                    content = f.read()
-
-                if file_path.suffix == ".json":
-                    data = json.loads(content)
-                else:
-                    yaml_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
-                    if not yaml_match:
-                        continue
-                    data = yaml.safe_load(yaml_match.group(1))
-
-                parsed.append({
-                    "file_path": file_path,
-                    "data": data,
-                    "is_json": file_path.suffix == ".json"
-                })
-            except Exception as e:
-                logger.error(f"解析文件失败 {file_path}: {e}")
-                continue
-
-        if len(parsed) < 2:
-            return ToolResult(
-                title="📂 有效知识过少",
-                output=f"有效知识仅有 {len(parsed)} 条,无需瘦身",
-                long_term_memory=f"知识库瘦身: 有效知识 {len(parsed)} 条"
-            )
-
-        # 构造发给大模型的内容
-        entries_text = ""
-        for p in parsed:
-            data = p["data"]
-            entries_text += f"[ID: {data.get('id')}] [Tags: {data.get('tags', {})}] "
-            entries_text += f"[Metrics: {data.get('metrics', {})}] [Score: {data.get('eval', {}).get('score', 3)}]\n"
-            entries_text += f"Scenario: {data.get('scenario', 'N/A')}\n"
-            entries_text += f"Content: {data.get('content', '')[:200]}...\n\n"
-
-        prompt = build_knowledge_slim_prompt(entries_text)
-
-        print(f"\n[知识瘦身] 正在调用 {model} 分析 {len(parsed)} 条知识...")
-        response = await openrouter_llm_call(
-            messages=[{"role": "user", "content": prompt}],
-            model=model
-        )
-        content = response.get("content", "").strip()
-        if not content:
-            return ToolResult(
-                title="❌ 大模型返回为空",
-                output="大模型返回为空,瘦身失败",
-                error="大模型返回为空"
-            )
-
-        # 解析大模型输出
-        report_line = ""
-        new_entries = []
-        blocks = [b.strip() for b in content.split("===") if b.strip()]
-
-        for block in blocks:
-            if block.startswith("REPORT:"):
-                report_line = block
-                continue
-
-            lines = block.split("\n")
-            kid, tags, metrics, score, scenario, content_lines = None, {}, {}, 3, "", []
-            current_field = None
-
-            for line in lines:
-                if line.startswith("ID:"):
-                    kid = line[3:].strip()
-                    current_field = None
-                elif line.startswith("TAGS:"):
-                    try:
-                        tags = yaml.safe_load(line[5:].strip()) or {}
-                    except Exception:
-                        tags = {}
-                    current_field = None
-                elif line.startswith("METRICS:"):
-                    try:
-                        metrics = yaml.safe_load(line[8:].strip()) or {}
-                    except Exception:
-                        metrics = {"helpful": 0, "harmful": 0}
-                    current_field = None
-                elif line.startswith("SCORE:"):
-                    try:
-                        score = int(line[6:].strip())
-                    except Exception:
-                        score = 3
-                    current_field = None
-                elif line.startswith("SCENARIO:"):
-                    scenario = line[9:].strip()
-                    current_field = "scenario"
-                elif line.startswith("CONTENT:"):
-                    content_lines.append(line[8:].strip())
-                    current_field = "content"
-                elif current_field == "scenario":
-                    scenario += "\n" + line
-                elif current_field == "content":
-                    content_lines.append(line)
-
-            if kid and content_lines:
-                new_data = {
-                    "id": kid,
-                    "tags": tags,
-                    "scenario": scenario,
-                    "content": "\n".join(content_lines).strip(),
-                    "metrics": metrics,
-                    "eval": {
-                        "score": score,
-                        "helpful": 0,
-                        "harmful": 0,
-                        "helpful_history": [],
-                        "harmful_history": []
-                    },
-                    "updated_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
-                }
-                new_entries.append(new_data)
-
-        if not new_entries:
-            return ToolResult(
-                title="❌ 解析失败",
-                output="解析大模型输出失败,知识库未修改",
-                error="解析失败"
-            )
-
-        # 删除旧文件
-        for p in parsed:
-            try:
-                p["file_path"].unlink()
-            except Exception as e:
-                logger.error(f"删除旧文件失败 {p['file_path']}: {e}")
+        output = f"资源 ID: {data['id']}\n"
+        output += f"标题: {data['title']}\n"
+        output += f"类型: {data['content_type']}\n"
+        output += f"\n公开内容:\n{data['body']}\n"
 
 
-        # 写入新文件(统一使用 JSON 格式)
-        for data in new_entries:
-            file_path = knowledge_dir / f"{data['id']}.json"
-            with open(file_path, "w", encoding="utf-8") as f:
-                json.dump(data, f, ensure_ascii=False, indent=2)
+        if data.get('secure_body'):
+            output += f"\n敏感内容:\n{data['secure_body']}\n"
 
 
-        result = f"瘦身完成:{len(parsed)} → {len(new_entries)} 条知识"
-        if report_line:
-            result += f"\n{report_line}"
-
-        print(f"[知识瘦身] {result}")
         return ToolResult(
         return ToolResult(
-            title="✅ 知识库瘦身完成",
-            output=result,
-            long_term_memory=f"知识库瘦身: {len(parsed)} → {len(new_entries)} 条"
+            title=f"📦 {data['title']}",
+            output=output,
+            metadata=data
         )
         )
 
 
     except Exception as e:
     except Exception as e:
-        logger.error(f"知识库瘦身失败: {e}")
+        logger.error(f"获取资源失败: {e}")
         return ToolResult(
         return ToolResult(
-            title="❌ 瘦身失败",
+            title="❌ 获取失败",
             output=f"错误: {str(e)}",
             output=f"错误: {str(e)}",
             error=str(e)
             error=str(e)
         )
         )

+ 3 - 0
agent/tools/builtin/sandbox.py

@@ -19,6 +19,7 @@ DEFAULT_TIMEOUT = 300.0
 
 
 
 
 @tool(
 @tool(
+    hidden_params=["context"],
     display={
     display={
         "zh": {
         "zh": {
             "name": "创建沙盒环境",
             "name": "创建沙盒环境",
@@ -120,6 +121,7 @@ async def sandbox_create_environment(
 
 
 
 
 @tool(
 @tool(
+    hidden_params=["context"],
     display={
     display={
         "zh": {
         "zh": {
             "name": "执行沙盒命令",
             "name": "执行沙盒命令",
@@ -234,6 +236,7 @@ async def sandbox_run_shell(
 
 
 
 
 @tool(
 @tool(
+    hidden_params=["context"],
     display={
     display={
         "zh": {
         "zh": {
             "name": "重建沙盒端口",
             "name": "重建沙盒端口",

+ 58 - 15
agent/tools/builtin/subagent.py

@@ -14,19 +14,62 @@ from agent.trace.models import Trace, Messages
 from agent.trace.trace_id import generate_sub_trace_id
 from agent.trace.trace_id import generate_sub_trace_id
 from agent.trace.goal_models import GoalTree
 from agent.trace.goal_models import GoalTree
 from agent.trace.websocket import broadcast_sub_trace_started, broadcast_sub_trace_completed
 from agent.trace.websocket import broadcast_sub_trace_started, broadcast_sub_trace_completed
-from agent.core.prompts import (
-    EVALUATE_PROMPT_TEMPLATE,
-    DELEGATE_RESULT_HEADER,
-    DELEGATE_SAVED_KNOWLEDGE_HEADER,
-    DELEGATE_STATS_HEADER,
-    EXPLORE_RESULT_HEADER,
-    EXPLORE_BRANCH_TEMPLATE,
-    EXPLORE_STATUS_SUCCESS,
-    EXPLORE_STATUS_FAILED,
-    EXPLORE_STATUS_ERROR,
-    EXPLORE_SUMMARY_HEADER,
-    build_evaluate_prompt,
-)
+
+
+# ===== prompts =====
+
+# ===== 评估任务 =====
+
+EVALUATE_PROMPT_TEMPLATE = """# 评估任务
+
+请评估以下任务的执行结果是否满足要求。
+
+## 目标描述
+
+{goal_description}
+
+## 执行结果
+
+{result_text}
+
+## 输出格式
+
+## 评估结论
+[通过/不通过]
+
+## 评估理由
+[详细说明通过或不通过原因]
+
+## 修改建议(如果不通过)
+1. [建议1]
+2. [建议2]
+"""
+
+# ===== 结果格式化 =====
+
+DELEGATE_RESULT_HEADER = "## 委托任务完成\n"
+
+DELEGATE_SAVED_KNOWLEDGE_HEADER = "**保存的知识** ({count} 条):"
+
+DELEGATE_STATS_HEADER = "**执行统计**:"
+
+EXPLORE_RESULT_HEADER = "## 探索结果\n"
+
+EXPLORE_BRANCH_TEMPLATE = "### 方案 {branch_name}: {task}"
+
+EXPLORE_STATUS_SUCCESS = "**状态**: ✓ 完成"
+
+EXPLORE_STATUS_FAILED = "**状态**: ✗ 失败"
+
+EXPLORE_STATUS_ERROR = "**状态**: ✗ 异常"
+
+EXPLORE_SUMMARY_HEADER = "## 总结"
+
+def build_evaluate_prompt(goal_description: str, result_text: str) -> str:
+    return EVALUATE_PROMPT_TEMPLATE.format(
+        goal_description=goal_description,
+        result_text=result_text or "(无执行结果)",
+    )
 
 
 
 
 def _make_run_config(**kwargs):
 def _make_run_config(**kwargs):
@@ -536,7 +579,7 @@ async def _run_agents(
 
 
 # ===== 工具定义 =====
 # ===== 工具定义 =====
 
 
-@tool(description="创建 Agent 执行任务")
+@tool(description="创建 Agent 执行任务", hidden_params=["context"])
 async def agent(
 async def agent(
     task: Union[str, List[str]],
     task: Union[str, List[str]],
     messages: Optional[Union[Messages, List[Messages]]] = None,
     messages: Optional[Union[Messages, List[Messages]]] = None,
@@ -603,7 +646,7 @@ async def agent(
     )
     )
 
 
 
 
-@tool(description="评估目标执行结果是否满足要求")
+@tool(description="评估目标执行结果是否满足要求", hidden_params=["context"])
 async def evaluate(
 async def evaluate(
     messages: Optional[Messages] = None,
     messages: Optional[Messages] = None,
     target_goal_id: Optional[str] = None,
     target_goal_id: Optional[str] = None,

+ 69 - 11
agent/tools/registry.py

@@ -66,7 +66,9 @@ class ToolRegistry:
 		requires_confirmation: bool = False,
 		requires_confirmation: bool = False,
 		editable_params: Optional[List[str]] = None,
 		editable_params: Optional[List[str]] = None,
 		display: Optional[Dict[str, Dict[str, Any]]] = None,
 		display: Optional[Dict[str, Dict[str, Any]]] = None,
-		url_patterns: Optional[List[str]] = None
+		url_patterns: Optional[List[str]] = None,
+		hidden_params: Optional[List[str]] = None,
+		inject_params: Optional[Dict[str, Any]] = None
 	):
 	):
 		"""
 		"""
 		注册工具
 		注册工具
@@ -78,6 +80,8 @@ class ToolRegistry:
 			editable_params: 允许用户编辑的参数列表
 			editable_params: 允许用户编辑的参数列表
 			display: i18n 展示信息 {"zh": {"name": "xx", "params": {...}}, "en": {...}}
 			display: i18n 展示信息 {"zh": {"name": "xx", "params": {...}}, "en": {...}}
 			url_patterns: URL 模式列表(如 ["*.google.com"],None = 无限制)
 			url_patterns: URL 模式列表(如 ["*.google.com"],None = 无限制)
+			hidden_params: 隐藏参数列表(不生成 schema,LLM 看不到)
+			inject_params: 注入参数规则 {param_name: injector_func}
 		"""
 		"""
 		func_name = func.__name__
 		func_name = func.__name__
 
 
@@ -85,7 +89,7 @@ class ToolRegistry:
 		if schema is None:
 		if schema is None:
 			try:
 			try:
 				from agent.tools.schema import SchemaGenerator
 				from agent.tools.schema import SchemaGenerator
-				schema = SchemaGenerator.generate(func)
+				schema = SchemaGenerator.generate(func, hidden_params=hidden_params or [])
 			except Exception as e:
 			except Exception as e:
 				logger.error(f"Failed to generate schema for {func_name}: {e}")
 				logger.error(f"Failed to generate schema for {func_name}: {e}")
 				raise
 				raise
@@ -94,6 +98,8 @@ class ToolRegistry:
 			"func": func,
 			"func": func,
 			"schema": schema,
 			"schema": schema,
 			"url_patterns": url_patterns,
 			"url_patterns": url_patterns,
+			"hidden_params": hidden_params or [],
+			"inject_params": inject_params or {},
 			"ui_metadata": {
 			"ui_metadata": {
 				"requires_confirmation": requires_confirmation,
 				"requires_confirmation": requires_confirmation,
 				"editable_params": editable_params or [],
 				"editable_params": editable_params or [],
@@ -177,7 +183,8 @@ class ToolRegistry:
 		arguments: Dict[str, Any],
 		arguments: Dict[str, Any],
 		uid: str = "",
 		uid: str = "",
 		context: Optional[Dict[str, Any]] = None,
 		context: Optional[Dict[str, Any]] = None,
-		sensitive_data: Optional[Dict[str, Any]] = None
+		sensitive_data: Optional[Dict[str, Any]] = None,
+		inject_values: Optional[Dict[str, Any]] = None
 	) -> str:
 	) -> str:
 		"""
 		"""
 		执行工具调用
 		执行工具调用
@@ -204,6 +211,7 @@ class ToolRegistry:
 
 
 		try:
 		try:
 			func = self._tools[name]["func"]
 			func = self._tools[name]["func"]
+			tool_info = self._tools[name]
 
 
 			# 处理敏感数据占位符
 			# 处理敏感数据占位符
 			if sensitive_data:
 			if sensitive_data:
@@ -215,14 +223,48 @@ class ToolRegistry:
 			kwargs = {**arguments}
 			kwargs = {**arguments}
 			sig = inspect.signature(func)
 			sig = inspect.signature(func)
 
 
-			# 注入 uid(如果函数接受)
-			if "uid" in sig.parameters:
+			# 注入隐藏参数(hidden_params)
+			hidden_params = tool_info.get("hidden_params", [])
+			if "uid" in hidden_params and "uid" in sig.parameters:
 				kwargs["uid"] = uid
 				kwargs["uid"] = uid
-
-			# 注入 context(如果函数接受)
-			if "context" in sig.parameters:
+			if "context" in hidden_params and "context" in sig.parameters:
 				kwargs["context"] = context
 				kwargs["context"] = context
 
 
+			# 注入参数(inject_params)
+			inject_params = tool_info.get("inject_params", {})
+			for param_name, rule in inject_params.items():
+				if param_name not in sig.parameters:
+					continue
+
+				if not isinstance(rule, dict) or "mode" not in rule:
+					# 兼容旧格式:直接值或 callable
+					if param_name not in kwargs or kwargs[param_name] is None:
+						kwargs[param_name] = rule() if callable(rule) else rule
+					continue
+
+				mode = rule["mode"]
+				# 从 inject_values 中获取值
+				value = (inject_values or {}).get(param_name)
+
+				if value is None:
+					continue
+
+				if mode == "default":
+					# 默认值模式:LLM 未提供则注入
+					if param_name not in kwargs or kwargs[param_name] is None:
+						kwargs[param_name] = value
+				elif mode == "merge":
+					# 合并模式:框架值始终保留,LLM 可追加新内容
+					llm_value = kwargs.get(param_name)
+					if isinstance(value, dict):
+						# dict: LLM 追加新 key,同名 key 以框架值为准
+						kwargs[param_name] = {**(llm_value or {}), **value}
+					elif isinstance(value, list):
+						# list: 合并去重
+						kwargs[param_name] = list(set((llm_value or []) + value))
+					else:
+						kwargs[param_name] = value
+
 			# 执行函数
 			# 执行函数
 			if inspect.iscoroutinefunction(func):
 			if inspect.iscoroutinefunction(func):
 				result = await func(**kwargs)
 				result = await func(**kwargs)
@@ -415,7 +457,9 @@ def tool(
 	requires_confirmation: bool = False,
 	requires_confirmation: bool = False,
 	editable_params: Optional[List[str]] = None,
 	editable_params: Optional[List[str]] = None,
 	display: Optional[Dict[str, Dict[str, Any]]] = None,
 	display: Optional[Dict[str, Dict[str, Any]]] = None,
-	url_patterns: Optional[List[str]] = None
+	url_patterns: Optional[List[str]] = None,
+	hidden_params: Optional[List[str]] = None,
+	inject_params: Optional[Dict[str, Any]] = None
 ):
 ):
 	"""
 	"""
 	工具装饰器 - 自动注册工具并生成 Schema
 	工具装饰器 - 自动注册工具并生成 Schema
@@ -427,9 +471,15 @@ def tool(
 		editable_params: 允许用户编辑的参数列表
 		editable_params: 允许用户编辑的参数列表
 		display: i18n 展示信息
 		display: i18n 展示信息
 		url_patterns: URL 模式列表(如 ["*.google.com"],None = 无限制)
 		url_patterns: URL 模式列表(如 ["*.google.com"],None = 无限制)
+		hidden_params: 隐藏参数列表(不生成 schema,LLM 看不到)
+		inject_params: 注入参数规则 {param_name: injector_func}
 
 
 	Example:
 	Example:
 		@tool(
 		@tool(
+			hidden_params=["context", "uid"],
+			inject_params={
+				"owner": lambda ctx: ctx.config.knowledge.get_owner(),
+			},
 			editable_params=["query"],
 			editable_params=["query"],
 			url_patterns=["*.google.com"],
 			url_patterns=["*.google.com"],
 			display={
 			display={
@@ -437,7 +487,13 @@ def tool(
 				"en": {"name": "Search Notes", "params": {"query": "Query"}}
 				"en": {"name": "Search Notes", "params": {"query": "Query"}}
 			}
 			}
 		)
 		)
-		async def search_blocks(query: str, limit: int = 10, uid: str = "") -> str:
+		async def search_blocks(
+			query: str,
+			limit: int = 10,
+			owner: Optional[str] = None,
+			context: Optional[ToolContext] = None,
+			uid: str = ""
+		) -> str:
 			'''搜索用户的笔记块'''
 			'''搜索用户的笔记块'''
 			...
 			...
 	"""
 	"""
@@ -448,7 +504,9 @@ def tool(
 			requires_confirmation=requires_confirmation,
 			requires_confirmation=requires_confirmation,
 			editable_params=editable_params,
 			editable_params=editable_params,
 			display=display,
 			display=display,
-			url_patterns=url_patterns
+			url_patterns=url_patterns,
+			hidden_params=hidden_params,
+			inject_params=inject_params
 		)
 		)
 		return func
 		return func
 
 

+ 15 - 5
agent/tools/schema.py

@@ -68,16 +68,19 @@ class SchemaGenerator:
     }
     }
 
 
     @classmethod
     @classmethod
-    def generate(cls, func: callable) -> Dict[str, Any]:
+    def generate(cls, func: callable, hidden_params: Optional[List[str]] = None) -> Dict[str, Any]:
         """
         """
         从函数生成 OpenAI Tool Schema
         从函数生成 OpenAI Tool Schema
 
 
         Args:
         Args:
             func: 要生成 Schema 的函数
             func: 要生成 Schema 的函数
+            hidden_params: 隐藏参数列表(不生成 schema)
 
 
         Returns:
         Returns:
             OpenAI Tool Schema(JSON 格式)
             OpenAI Tool Schema(JSON 格式)
         """
         """
+        hidden_params = hidden_params or []
+
         # 解析函数签名
         # 解析函数签名
         sig = inspect.signature(func)
         sig = inspect.signature(func)
         func_name = func.__name__
         func_name = func.__name__
@@ -98,11 +101,11 @@ class SchemaGenerator:
 
 
         for param_name, param in sig.parameters.items():
         for param_name, param in sig.parameters.items():
             # 跳过特殊参数
             # 跳过特殊参数
-            if param_name in ["self", "cls", "kwargs", "context"]:
+            if param_name in ["self", "cls", "kwargs"]:
                 continue
                 continue
 
 
-            # 跳过 uid(由框架自动注入)
-            if param_name == "uid":
+            # 跳过隐藏参数
+            if param_name in hidden_params:
                 continue
                 continue
 
 
             # 获取类型注解
             # 获取类型注解
@@ -184,6 +187,13 @@ class SchemaGenerator:
         if python_type in cls.TYPE_MAP:
         if python_type in cls.TYPE_MAP:
             return {"type": cls.TYPE_MAP[python_type]}
             return {"type": cls.TYPE_MAP[python_type]}
 
 
+        # 检查是否是 Protocol(如 ToolContext)
+        # Protocol 类型用于依赖注入,不应出现在 schema 中
+        type_name = getattr(python_type, "__name__", str(python_type))
+        if "Protocol" in str(type(python_type)) or type_name in ("ToolContext",):
+            logger.debug(f"Skipping Protocol type {python_type} (used for dependency injection)")
+            return {}
+
         # 默认为 string
         # 默认为 string
-        logger.warning(f"Unknown type {python_type}, defaulting to string")
+        logger.debug(f"Unknown type {python_type}, defaulting to string")
         return {"type": "string"}
         return {"type": "string"}

+ 2 - 7
agent/trace/compaction.py

@@ -306,19 +306,14 @@ def needs_level2_compression(
 # COMPRESSION_EVAL_PROMPT 和 REFLECT_PROMPT 现在从 prompts.py 导入
 # COMPRESSION_EVAL_PROMPT 和 REFLECT_PROMPT 现在从 prompts.py 导入
 
 
 
 
-def build_compression_prompt(goal_tree: Optional[GoalTree], used_ex_ids: Optional[List[str]] = None) -> str:
-    """构建 Level 2 压缩 prompt(含经验评估)"""
+def build_compression_prompt(goal_tree: Optional[GoalTree]) -> str:
+    """构建 Level 2 压缩 prompt"""
     goal_prompt = ""
     goal_prompt = ""
     if goal_tree:
     if goal_tree:
         goal_prompt = goal_tree.to_prompt(include_summary=True)
         goal_prompt = goal_tree.to_prompt(include_summary=True)
 
 
-    ex_reference = "无(本次未引用历史经验)"
-    if used_ex_ids:
-        ex_reference = ", ".join(used_ex_ids)
-
     return build_compression_eval_prompt(
     return build_compression_eval_prompt(
         goal_tree_prompt=goal_prompt,
         goal_tree_prompt=goal_prompt,
-        ex_reference_list=ex_reference,
     )
     )
 
 
 
 

+ 86 - 10
agent/trace/goal_tool.py

@@ -4,18 +4,91 @@ Goal 工具 - 计划管理
 提供 goal 工具供 LLM 管理执行计划。
 提供 goal 工具供 LLM 管理执行计划。
 """
 """
 
 
+import logging
 from typing import Optional, List, TYPE_CHECKING
 from typing import Optional, List, TYPE_CHECKING
 
 
 from agent.tools import tool
 from agent.tools import tool
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
-    from .goal_models import GoalTree
+    from .goal_models import GoalTree, Goal
     from .protocols import TraceStore
     from .protocols import TraceStore
 
 
+logger = logging.getLogger(__name__)
+
+
+# ===== 知识注入 =====
+
+async def inject_knowledge_for_goal(
+    goal: "Goal",
+    tree: "GoalTree",
+    store: Optional["TraceStore"] = None,
+    trace_id: Optional[str] = None,
+    knowledge_config: Optional[dict] = None,
+) -> Optional[str]:
+    """
+    为指定 goal 注入相关知识。
+
+    Args:
+        goal: 目标对象
+        tree: GoalTree
+        store: TraceStore(用于持久化)
+        trace_id: Trace ID
+        knowledge_config: 知识管理配置(KnowledgeConfig 对象)
+
+    Returns:
+        注入结果描述(如 "📚 已注入 3 条相关知识"),无结果返回 None
+    """
+    # 检查是否启用知识注入
+    if knowledge_config and not getattr(knowledge_config, 'enable_injection', True):
+        logger.debug(f"[Knowledge Inject] 知识注入已禁用,跳过")
+        return None
+
+    try:
+        from agent.tools.builtin.knowledge import knowledge_search
+
+        logger.info(f"[Knowledge Inject] goal: {goal.id}, query: {goal.description[:80]}")
+
+        # 从配置中获取搜索参数
+        search_types = None
+        search_owner = None
+        if knowledge_config:
+            search_types = getattr(knowledge_config, 'default_search_types', None)
+            search_owner = getattr(knowledge_config, 'default_search_owner', None) or None
+
+        knowledge_result = await knowledge_search(
+            query=goal.description,
+            top_k=3,
+            min_score=3,
+            types=search_types,
+            owner=search_owner,
+            context=None
+        )
+
+        logger.debug(f"[Knowledge Inject] result type: {type(knowledge_result)}, metadata: {getattr(knowledge_result, 'metadata', None)}")
+
+        if knowledge_result.metadata and knowledge_result.metadata.get("items"):
+            goal.knowledge = knowledge_result.metadata["items"]
+            knowledge_count = len(goal.knowledge)
+            logger.info(f"[Knowledge Inject] 注入 {knowledge_count} 条知识到 goal {goal.id}")
+
+            if store and trace_id:
+                await store.update_goal_tree(trace_id, tree)
+
+            return f"📚 已注入 {knowledge_count} 条相关知识"
+        else:
+            goal.knowledge = []
+            logger.info(f"[Knowledge Inject] 未找到相关知识")
+            return None
+
+    except Exception as e:
+        logger.warning(f"[Knowledge Inject] 知识注入失败: {e}")
+        goal.knowledge = []
+        return None
+
 
 
 # ===== LLM 可调用的 goal 工具 =====
 # ===== LLM 可调用的 goal 工具 =====
 
 
-@tool(description="管理执行计划,添加/完成/放弃目标,切换焦点")
+@tool(description="管理执行计划,添加/完成/放弃目标,切换焦点", hidden_params=["context"])
 async def goal(
 async def goal(
     add: Optional[str] = None,
     add: Optional[str] = None,
     reason: Optional[str] = None,
     reason: Optional[str] = None,
@@ -47,9 +120,10 @@ async def goal(
     if tree is None:
     if tree is None:
         return "错误:GoalTree 未初始化"
         return "错误:GoalTree 未初始化"
 
 
-    # 从 context 获取 store 和 trace_id
+    # 从 context 获取 store、trace_id 和 knowledge_config
     store = context.get("store") if context else None
     store = context.get("store") if context else None
     trace_id = context.get("trace_id") if context else None
     trace_id = context.get("trace_id") if context else None
+    knowledge_config = context.get("knowledge_config") if context else None
 
 
     return await goal_tool(
     return await goal_tool(
         tree=tree,
         tree=tree,
@@ -61,7 +135,8 @@ async def goal(
         under=under,
         under=under,
         done=done,
         done=done,
         abandon=abandon,
         abandon=abandon,
-        focus=focus
+        focus=focus,
+        knowledge_config=knowledge_config
     )
     )
 
 
 
 
@@ -79,6 +154,7 @@ async def goal_tool(
     done: Optional[str] = None,
     done: Optional[str] = None,
     abandon: Optional[str] = None,
     abandon: Optional[str] = None,
     focus: Optional[str] = None,
     focus: Optional[str] = None,
+    knowledge_config: Optional[object] = None,
 ) -> str:
 ) -> str:
     """
     """
     管理执行计划。
     管理执行计划。
@@ -94,6 +170,7 @@ async def goal_tool(
         done: 完成当前目标,值为 summary
         done: 完成当前目标,值为 summary
         abandon: 放弃当前目标,值为原因
         abandon: 放弃当前目标,值为原因
         focus: 切换焦点到指定 ID
         focus: 切换焦点到指定 ID
+        knowledge_config: 知识管理配置(KnowledgeConfig 对象)
 
 
     Returns:
     Returns:
         更新后的计划状态文本
         更新后的计划状态文本
@@ -135,6 +212,11 @@ async def goal_tool(
         display_id = tree._generate_display_id(goal)
         display_id = tree._generate_display_id(goal)
         changes.append(f"切换焦点: {display_id}. {goal.description}")
         changes.append(f"切换焦点: {display_id}. {goal.description}")
 
 
+        # 自动注入知识
+        inject_msg = await inject_knowledge_for_goal(goal, tree, store, trace_id, knowledge_config)
+        if inject_msg:
+            changes.append(inject_msg)
+
     # 3. 处理 abandon(放弃当前目标)
     # 3. 处理 abandon(放弃当前目标)
     if abandon is not None:
     if abandon is not None:
         if not tree.current_id:
         if not tree.current_id:
@@ -200,12 +282,6 @@ async def goal_tool(
                 for goal in new_goals:
                 for goal in new_goals:
                     await store.add_goal(trace_id, goal)
                     await store.add_goal(trace_id, goal)
 
 
-            # 如果没有焦点且添加了目标,自动 focus 到第一个新目标
-            if not tree.current_id and new_goals:
-                tree.focus(new_goals[0].id)
-                display_id = tree._generate_display_id(new_goals[0])
-                changes.append(f"自动切换焦点: {display_id}")
-
     # 将完整内存树状态(含 current_id)同步到存储,
     # 将完整内存树状态(含 current_id)同步到存储,
     # 因为 store.add_goal / update_goal 各自从磁盘加载,不包含 focus 等内存变更
     # 因为 store.add_goal / update_goal 各自从磁盘加载,不包含 focus 等内存变更
     if store and trace_id and changes:
     if store and trace_id and changes:

+ 13 - 4
agent/trace/models.py

@@ -373,12 +373,21 @@ class Message:
                 if content.get("tool_calls"):
                 if content.get("tool_calls"):
                     tool_calls = content["tool_calls"]
                     tool_calls = content["tool_calls"]
                     if isinstance(tool_calls, list):
                     if isinstance(tool_calls, list):
-                        tool_names = []
+                        tool_descriptions = []
                         for tc in tool_calls:
                         for tc in tool_calls:
                             if isinstance(tc, dict) and tc.get("function", {}).get("name"):
                             if isinstance(tc, dict) and tc.get("function", {}).get("name"):
-                                tool_names.append(tc["function"]["name"])
-                        if tool_names:
-                            return f"tool call: {', '.join(tool_names)}"
+                                tool_name = tc["function"]["name"]
+                                # 提取参数并截断到 100 字符
+                                tool_args = tc["function"].get("arguments", "{}")
+                                if isinstance(tool_args, str):
+                                    args_str = tool_args
+                                else:
+                                    import json
+                                    args_str = json.dumps(tool_args, ensure_ascii=False)
+                                args_display = args_str[:100] + "..." if len(args_str) > 100 else args_str
+                                tool_descriptions.append(f"{tool_name}({args_display})")
+                        if tool_descriptions:
+                            return "tool call: " + ", ".join(tool_descriptions)
 
 
             # 如果 content 是字符串
             # 如果 content 是字符串
             if isinstance(content, str):
             if isinstance(content, str):

+ 15 - 12
agent/trace/websocket.py

@@ -134,18 +134,21 @@ async def watch_trace(
             except asyncio.TimeoutError:
             except asyncio.TimeoutError:
                 pass
                 pass
 
 
-            new_events = await store.get_events(trace_id, last_sent_event_id)
-            if len(new_events) > 100:
-                await websocket.send_json({
-                    "event": "error",
-                    "message": f"Too many missed events ({len(new_events)}), please reload via REST API"
-                })
-                continue
-
-            for evt in new_events:
-                await websocket.send_json(evt)
-                if isinstance(evt, dict) and isinstance(evt.get("event_id"), int):
-                    last_sent_event_id = max(last_sent_event_id, evt["event_id"])
+            try:
+                new_events = await store.get_events(trace_id, last_sent_event_id)
+                if len(new_events) > 100:
+                    await websocket.send_json({
+                        "event": "error",
+                        "message": f"Too many missed events ({len(new_events)}), please reload via REST API"
+                    })
+                    continue
+
+                for evt in new_events:
+                    await websocket.send_json(evt)
+                    if isinstance(evt, dict) and isinstance(evt.get("event_id"), int):
+                        last_sent_event_id = max(last_sent_event_id, evt["event_id"])
+            except WebSocketDisconnect:
+                break
 
 
     finally:
     finally:
         # 清理连接
         # 清理连接

+ 7 - 0
agent/utils/__init__.py

@@ -0,0 +1,7 @@
+"""
+工具函数模块
+"""
+
+from agent.utils.logging import setup_logging
+
+__all__ = ["setup_logging"]

+ 40 - 0
agent/utils/logging.py

@@ -0,0 +1,40 @@
+"""
+日志配置工具
+
+提供统一的日志配置方法。
+"""
+
+import logging
+from typing import Optional
+
+
+def setup_logging(
+    level: str = "INFO",
+    format: Optional[str] = None,
+    file: Optional[str] = None
+):
+    """
+    配置日志系统
+
+    Args:
+        level: 日志级别(DEBUG, INFO, WARNING, ERROR, CRITICAL)
+        format: 日志格式(None 使用默认格式)
+        file: 日志文件路径(None 只输出到控制台)
+    """
+    log_level = getattr(logging, level.upper(), logging.INFO)
+    log_format = format or "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+
+    handlers = [logging.StreamHandler()]
+    if file:
+        handlers.append(logging.FileHandler(file, encoding="utf-8"))
+
+    logging.basicConfig(
+        level=log_level,
+        format=log_format,
+        handlers=handlers,
+        force=True
+    )
+
+    # 设置第三方库日志级别
+    logging.getLogger("httpx").setLevel(logging.WARNING)
+    logging.getLogger("httpcore").setLevel(logging.WARNING)

+ 0 - 1
api_server.py

@@ -73,7 +73,6 @@ from agent.llm import create_openrouter_llm_call
 runner = AgentRunner(
 runner = AgentRunner(
     trace_store=trace_store,
     trace_store=trace_store,
     llm_call=create_openrouter_llm_call(model="anthropic/claude-sonnet-4.5"),
     llm_call=create_openrouter_llm_call(model="anthropic/claude-sonnet-4.5"),
-    experiences_path="./.cache/experiences.md",  # 经验文件路径
 )
 )
 set_runner(runner)
 set_runner(runner)
 
 

+ 75 - 969
docs/README.md

@@ -1,1016 +1,122 @@
-# Agent 功能需求与架构设计文档
+# Agent 系统文档
 
 
-## 文档维护规范
+## 文档导航
 
 
-0. **先改文档,再动代码** - 新功能或重大修改需先完成文档更新、并完成审阅后,再进行代码实现;除非改动较小、不被文档涵盖
-1. **文档分层,链接代码** - 重要或复杂设计可以另有详细文档;关键实现需标注代码文件路径;格式:`module/file.py:function_name`
-2. **简洁快照,日志分离** - 只记录最重要的、与代码准确对应的或者明确的已完成的设计的信息,避免推测、建议,或大量代码;决策依据或修改日志若有必要,可在`docs/decisions.md`另行记录
+本文档是项目总览和文档导航。详细的模块文档请参考:
 
 
----
+### 核心模块
 
 
-## 系统概览
+- **[Agent Core 模块](../agent/README.md)** - Agent 核心引擎、工具系统、记忆管理
+  - [架构设计](../agent/docs/architecture.md) - Agent 框架完整架构
+  - [工具系统](../agent/docs/tools.md)
+  - [Skills 指南](../agent/docs/skills.md)
+  - [Trace API](../agent/docs/trace-api.md)
+  - [多模态支持](../agent/docs/multimodal.md)
+  - [设计决策](../agent/docs/decisions.md)
 
 
-**核心理念:所有 Agent 都是 Trace**
+- **[Gateway 模块](../gateway/README.md)** - Agent 注册、消息路由、在线状态管理
+  - [架构设计](../gateway/docs/architecture.md)
+  - [部署指南](../gateway/docs/deployment.md)
+  - [API 参考](../gateway/docs/api.md)
+  - [设计决策](../gateway/docs/decisions.md)
+  - [Enterprise 层](../gateway/docs/enterprise/overview.md)
+  - [A2A IM 使用](../gateway/client/a2a_im.md) - Agent 间通讯工具
 
 
-| 类型 | 创建方式 | 父子关系 | 状态 |
-|------|---------|---------|------|
-| 主 Agent | 直接调用 `runner.run()` | 无 parent | 正常执行 |
-| 子 Agent | 通过 `agent` 工具 | `parent_trace_id` / `parent_goal_id` 指向父 | 正常执行 |
-| 人类协助 | 通过 `ask_human` 工具 | `parent_trace_id` 指向父 | 阻塞等待 |
+### 跨模块文档
 
 
----
-
-## 核心架构
-
-### 模块结构
-
-```
-agent/
-├── core/                  # 核心引擎
-│   ├── runner.py          # AgentRunner + 运行时配置
-│   └── presets.py         # Agent 预设(explore、analyst 等)
-│
-├── trace/                 # 执行追踪(含计划管理)
-│   ├── models.py          # Trace, Message
-│   ├── goal_models.py     # Goal, GoalTree, GoalStats
-│   ├── protocols.py       # TraceStore 接口
-│   ├── store.py           # FileSystemTraceStore 实现
-│   ├── goal_tool.py       # goal 工具(计划管理)
-│   ├── compaction.py      # Context 压缩
-│   ├── api.py             # REST API
-│   ├── websocket.py       # WebSocket API
-│   └── trace_id.py        # Trace ID 生成工具
-│
-├── tools/                 # 外部交互工具
-│   ├── registry.py        # 工具注册表
-│   ├── schema.py          # Schema 生成器
-│   ├── models.py          # ToolResult, ToolContext
-│   └── builtin/
-│       ├── file/          # 文件操作(read, write, edit, glob, grep)
-│       ├── browser/       # 浏览器自动化
-│       ├── bash.py        # 命令执行
-│       ├── sandbox.py     # 沙箱环境
-│       ├── search.py      # 网络搜索
-│       ├── webfetch.py    # 网页抓取
-│       ├── skill.py       # 技能加载
-│       └── subagent.py    # agent / evaluate 工具(子 Agent 创建与评估)
-│
-├── memory/                # 跨会话记忆
-│   ├── models.py          # Experience, Skill
-│   ├── protocols.py       # MemoryStore 接口
-│   ├── stores.py          # 存储实现
-│   ├── skill_loader.py    # Skill 加载器
-│   └── skills/            # 内置 Skills(自动注入 system prompt)
-│       ├── planning.md    # 计划与 Goal 工具使用
-│       ├── research.md    # 搜索与内容研究
-│       └── browser.md     # 浏览器自动化
-│
-├── llm/                   # LLM 集成
-│   ├── gemini.py          # Gemini Provider
-│   ├── openrouter.py      # OpenRouter Provider(OpenAI 兼容格式)
-│   ├── yescode.py         # Yescode Provider(Anthropic 原生 Messages API)
-│   └── prompts/           # Prompt 工具
-```
+- [A2A IM 系统](./a2a-im.md) - Agent 间即时通讯系统架构
+- [知识管理](../knowhub/docs/knowledge-management.md) - 知识结构、API、集成方式
+- [Context 管理](./context-management.md) - Goals、压缩、Plan 注入策略
 
 
-### 职责划分
+### 研究文档
 
 
-| 模块 | 职责 |
-|-----|------|
-| **core/** | Agent 执行引擎 + 预设配置 |
-| **trace/** | 执行追踪 + 计划管理 |
-| **tools/** | 与外部世界交互(文件、命令、网络、浏览器) |
-| **memory/** | 跨会话知识(Skills、Experiences) |
-| **llm/** | LLM Provider 适配 |
+- [A2A 协议调研](./research/a2a-protocols.md) - 行业 A2A 通信协议和框架对比
+- [A2A 跨设备通信](./research/a2a-cross-device.md) - 跨设备 Agent 通信方案(内部)
+- [A2A Trace 存储](./research/a2a-trace-storage.md) - 跨设备 Trace 存储方案详细设计
+- [MAMP 协议](./research/a2a-mamp-protocol.md) - 与外部 Agent 系统的通用交互协议
 
 
-### 三层记忆模型
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ Layer 3: Skills(技能库)                                     │
-│ - Markdown 文件,存储领域知识和能力描述                        │
-│ - 通过 skill 工具按需加载到对话历史                            │
-└─────────────────────────────────────────────────────────────┘
-                              ▲
-                              │ 归纳
-┌─────────────────────────────────────────────────────────────┐
-│ Layer 2: Experience(经验库)                                 │
-│ - 数据库存储,条件 + 规则 + 证据                              │
-│ - 向量检索,注入到 system prompt                              │
-└─────────────────────────────────────────────────────────────┘
-                              ▲
-                              │ 提取
-┌─────────────────────────────────────────────────────────────┐
-│ Layer 1: Trace(任务状态)                                    │
-│ - 当前任务的工作记忆                                          │
-│ - Trace + Messages 记录执行过程                               │
-│ - Goals 管理执行计划                                          │
-└─────────────────────────────────────────────────────────────┘
-```
-
-### LLM Provider 适配
-
-#### 内部格式
-
-框架内部统一使用 OpenAI 兼容格式(`List[Dict]`)存储和传递消息。各 Provider 负责双向转换:
-
-| 方向 | 说明 |
-|------|------|
-| 入(LLM 响应 → 框架) | 提取 content、tool_calls、usage,转换为统一 Dict |
-| 出(框架 → LLM 请求) | OpenAI 格式消息列表 → 各 API 原生格式 |
-
-#### 工具消息分组
-
-存储层每个 tool result 独立一条 Message(OpenAI 格式最大公约数)。各 Provider 在出方向按 API 要求自行分组:
-
-| Provider | 分组方式 |
-|----------|---------|
-| OpenRouter | 无需分组(OpenAI 原生支持独立 tool 消息) |
-| Yescode | `_convert_messages_to_anthropic` 合并连续 tool 消息为单个 user message |
-| Gemini | `_convert_messages_to_gemini` 通过 buffer 合并连续 tool 消息 |
-
-#### 跨 Provider 续跑:tool_call_id 规范化
-
-不同 Provider 生成的 tool_call_id 格式不同(OpenAI: `call_xxx`,Anthropic: `toolu_xxx`,Gemini: 合成 `call_0`)。存储层按原样保存,不做规范化。
+---
 
 
-跨 Provider 续跑时,出方向转换前检测历史中的 tool_call_id 格式,不兼容时统一重写为目标格式(保持 tool_use / tool_result 配对一致)。同格式跳过,零开销。Gemini 按 function name 匹配,无需重写。
+## 文档维护规范
 
 
-**实现**:`agent/llm/openrouter.py:_normalize_tool_call_ids`, `agent/llm/yescode.py:_normalize_tool_call_ids`
+0. **先改文档,再动代码** - 新功能或重大修改需先完成文档更新、并完成审阅后,再进行代码实现;除非改动较小、不被文档涵盖
+1. **文档分层,链接代码** - 重要或复杂设计可以另有详细文档;关键实现需标注代码文件路径;格式:`module/file.py:function_name`
+2. **简洁快照,日志分离** - 只记录最重要的、与代码准确对应的或者明确的已完成的设计的信息,避免推测、建议,或大量代码;决策依据或修改日志若有必要,可在模块的 decisions.md 另行记录
 
 
 ---
 ---
 
 
-## 核心流程:Agent Loop
+## 项目概览
 
 
-### 参数分层
+### 系统架构
 
 
 ```
 ```
-Layer 1: Infrastructure(基础设施,AgentRunner 构造时设置)
-  trace_store, memory_store, tool_registry, llm_call, skills_dir, utility_llm_call
-
-Layer 2: RunConfig(运行参数,每次 run 时指定)
-  ├─ 模型层:model, temperature, max_iterations, tools
-  └─ 框架层:trace_id, agent_type, uid, system_prompt, parent_trace_id, ...
-
-Layer 3: Messages(任务消息,OpenAI SDK 格式 List[Dict])
-  [{"role": "user", "content": "分析这张图的构图"}]
+Agent 系统
+├── agent/          # Agent Core - 核心引擎、工具、记忆
+├── gateway/        # Gateway - 消息路由、Agent 注册
+├── docs/           # 跨模块文档
+└── examples/       # 使用示例和集成示例
 ```
 ```
 
 
-### RunConfig
+### 核心理念
 
 
-```python
-@dataclass
-class RunConfig:
-    # 模型层参数
-    model: str = "gpt-4o"
-    temperature: float = 0.3
-    max_iterations: int = 200
-    tools: Optional[List[str]] = None          # None = 全部已注册工具
-
-    # 框架层参数
-    agent_type: str = "default"
-    uid: Optional[str] = None
-    system_prompt: Optional[str] = None        # None = 从 skills 自动构建
-    skills: Optional[List[str]] = None         # 注入 system prompt 的 skill 名称列表;None = 按 preset 决定
-    enable_memory: bool = True
-    auto_execute_tools: bool = True
-    name: Optional[str] = None                 # 显示名称(空则由 utility_llm 自动生成)
-
-    # Trace 控制
-    trace_id: Optional[str] = None             # None = 新建
-    parent_trace_id: Optional[str] = None      # 子 Agent 专用
-    parent_goal_id: Optional[str] = None
-
-    # 续跑控制
-    after_sequence: Optional[int] = None       # 从哪条消息后续跑(message sequence)
-```
+**所有 Agent 都是 Trace**
 
 
-**实现**:`agent/core/runner.py:RunConfig`
+| 类型 | 创建方式 | 父子关系 | 状态 |
+|------|---------|---------|------|
+| 主 Agent | 直接调用 `runner.run()` | 无 parent | 正常执行 |
+| 子 Agent | 通过 `agent` 工具 | `parent_trace_id` / `parent_goal_id` 指向父 | 正常执行 |
+| 人类协助 | 通过 `ask_human` 工具 | `parent_trace_id` 指向父 | 阻塞等待 |
 
 
-### 三种运行模式
+### 模块职责
 
 
-通过 RunConfig 参数自然区分,统一入口 `run(messages, config)`:
+| 模块 | 职责 | 详细文档 |
+|-----|------|---------|
+| **agent/core/** | Agent 执行引擎 + 预设配置 | [架构设计](../agent/docs/architecture.md) |
+| **agent/trace/** | 执行追踪 + 计划管理 | [Trace API](../agent/docs/trace-api.md) |
+| **agent/tools/** | 与外部世界交互 | [工具系统](../agent/docs/tools.md) |
+| **agent/memory/** | 跨会话知识 | [Skills 指南](../agent/docs/skills.md) |
+| **agent/llm/** | LLM Provider 适配 | [架构设计](../agent/docs/architecture.md#llm-provider-适配) |
+| **gateway/core/** | Agent 注册和消息路由 | [Gateway 架构](../gateway/docs/architecture.md) |
+| **gateway/client/** | Gateway 客户端 SDK | [A2A IM](../gateway/client/a2a_im.md) |
 
 
-| 模式 | trace_id | after_sequence | messages 含义 | API 端点 |
-|------|----------|---------------|--------------|----------|
-| 新建 | None | - | 初始任务消息 | `POST /api/traces` |
-| 续跑 | 已有 ID | None 或 == head | 追加到末尾的新消息 | `POST /api/traces/{id}/run` |
-| 回溯 | 已有 ID | 主路径上 < head | 在插入点之后追加的新消息 | `POST /api/traces/{id}/run` |
+---
 
 
-Runner 根据 `after_sequence` 与当前 `head_sequence` 的关系自动判断行为,前端无需指定模式。
+## 快速开始
 
 
-### 执行流程
+### Agent Core
 
 
 ```python
 ```python
-async def run(messages: List[Dict], config: RunConfig = None) -> AsyncIterator[Union[Trace, Message]]:
-    # Phase 1: PREPARE TRACE
-    #   无 trace_id → 创建新 Trace(生成 name,初始化 GoalTree)
-    #   有 trace_id + after_sequence 为 None 或 == head → 加载已有 Trace,状态置为 running
-    #   有 trace_id + after_sequence < head → 加载 Trace,执行 rewind(快照 GoalTree,重建,设 parent_sequence)
-    trace = await _prepare_trace(config)
-    yield trace
-
-    # Phase 2: BUILD HISTORY
-    #   从 head_sequence 沿 parent chain 回溯构建主路径消息
-    #   构建 system prompt(新建时注入 skills/experiences;续跑时复用已有)
-    #   追加 input messages(设置 parent_sequence 指向当前 head)
-    history, sequence = await _build_history(trace, messages, config)
-
-    # Phase 3: AGENT LOOP
-    for iteration in range(config.max_iterations):
-        # 周期性注入 GoalTree + Active Collaborators(每 10 轮)
-        if iteration % 10 == 0:
-            inject_context(goal_tree, collaborators)
-
-        response = await llm_call(messages=history, model=config.model, tools=tool_schemas)
-
-        # 按需自动创建 root goal(兜底)
-        # 记录 assistant Message
-        # 执行工具,记录 tool Messages
-        # 无 tool_calls 则 break
-
-    # Phase 4: COMPLETE
-    #   更新 Trace 状态 (completed/failed)
-    trace.status = "completed"
-    yield trace
-```
+from agent.core import AgentRunner, RunConfig
 
 
-**实现**:`agent/core/runner.py:AgentRunner`
+runner = AgentRunner(...)
 
 
-### 回溯(Rewind)
-
-回溯通过 `RunConfig(trace_id=..., after_sequence=N)` 触发(N 在主路径上且 < head_sequence),在 Phase 1 中执行:
-
-1. **验证插入点**:确保不截断在 assistant(tool_calls) 和 tool response 之间
-2. **快照 GoalTree**:将当前完整 GoalTree 存入 `events.jsonl`(rewind 事件的 `goal_tree_snapshot` 字段)
-3. **按时间重建 GoalTree**:以截断点消息的 `created_at` 为界,保留 `created_at <= cutoff_time` 的所有 goals(无论状态),丢弃 cutoff 之后创建的 goals,清空 `current_id`。将被保留的 `in_progress` goal 重置为 `pending`
-4. **设置 parent_sequence**:新消息的 `parent_sequence` 指向 rewind 点,旧消息自动脱离主路径
-5. **更新 Trace**:`head_sequence` 更新为新消息的 sequence,status 改回 running
-
-新消息的 sequence 从 `last_sequence + 1` 开始(全局递增,不复用)。旧消息无需标记 abandoned,通过消息树结构自然隔离。
-
-### 调用接口
-
-三种模式共享同一入口 `run(messages, config)`:
-
-```python
-# 新建
 async for item in runner.run(
 async for item in runner.run(
     messages=[{"role": "user", "content": "分析项目架构"}],
     messages=[{"role": "user", "content": "分析项目架构"}],
-    config=RunConfig(model="gpt-4o"),
+    config=RunConfig(model="gpt-4o")
 ):
 ):
-    ...
-
-# 续跑:在已有 trace 末尾追加消息继续执行
-async for item in runner.run(
-    messages=[{"role": "user", "content": "继续"}],
-    config=RunConfig(trace_id="existing-trace-id"),
-):
-    ...
-
-# 回溯:从指定 sequence 处切断,插入新消息重新执行
-# after_sequence=5 表示新消息的 parent_sequence=5,从此处开始
-async for item in runner.run(
-    messages=[{"role": "user", "content": "换一个方案试试"}],
-    config=RunConfig(trace_id="existing-trace-id", after_sequence=5),
-):
-    ...
-
-# 重新生成:回溯后不插入新消息,直接基于已有消息重跑
-async for item in runner.run(
-    messages=[],
-    config=RunConfig(trace_id="existing-trace-id", after_sequence=5),
-):
-    ...
+    print(item)
 ```
 ```
 
 
-`after_sequence` 的值是 message 的 `sequence` 号,可通过 `GET /api/traces/{trace_id}/messages` 查看。如果指定的 sequence 是一条带 `tool_calls` 的 assistant 消息,系统会自动将截断点扩展到其所有对应的 tool response 之后(安全截断)。
-
-**停止运行**:
-
-```python
-# 停止正在运行的 Trace
-await runner.stop(trace_id)
-```
-
-调用后 agent loop 在下一个检查点退出,Trace 状态置为 `stopped`,同时保存当前 `head_sequence`(确保续跑时能正确加载完整历史)。
-
-**消息完整性保护(orphaned tool_call 修复)**:续跑加载历史时,`_build_history` 自动检测并修复 orphaned tool_calls(`_heal_orphaned_tool_calls`)。当 agent 被 stop/crash 中断时,可能存在 assistant 的 tool_calls 没有对应的 tool results(包括部分完成的情况:3 个 tool_call 只有 1 个 tool_result)。直接发给 LLM 会导致 400 错误。
-
-修复策略:为每个缺失的 tool_result **插入合成的中断通知**(而非裁剪 assistant 消息):
-
-| 工具类型 | 合成 tool_result 内容 |
-|----------|---------------------|
-| 普通工具 | 简短中断提示,建议重新调用 |
-| agent/evaluate | 结构化中断信息,包含 `sub_trace_id`、执行统计、`continue_from` 用法指引 |
+详见:[Agent Core README](../agent/README.md)
 
 
-agent 工具的合成结果对齐正常返回值格式(含 `sub_trace_id` 字段),主 Agent 可直接使用 `agent(task=..., continue_from=sub_trace_id)` 续跑被中断的子 Agent。合成消息持久化存储,确保幂等。
-
-**实现**:`agent/core/runner.py:AgentRunner._heal_orphaned_tool_calls`
-
-- `run(messages, config)`:**核心方法**,流式返回 `AsyncIterator[Union[Trace, Message]]`
-- `run_result(messages, config, on_event=None)`:便利方法,内部消费 `run()`,返回结构化结果。`on_event` 回调可实时接收每个 Trace/Message 事件(用于调试时输出子 Agent 执行过程)。主要用于 `agent`/`evaluate` 工具内部
-
-### REST API
-
-#### 查询端点
-
-| 方法 | 路径 | 说明 |
-|------|------|------|
-| GET  | `/api/traces` | 列出 Traces |
-| GET  | `/api/traces/{id}` | 获取 Trace 详情(含 GoalTree、Sub-Traces) |
-| GET  | `/api/traces/{id}/messages` | 获取 Messages(支持 mode=main_path/all) |
-| GET  | `/api/traces/running` | 列出正在运行的 Trace |
-| WS   | `/api/traces/{id}/watch` | 实时事件推送 |
-
-**实现**:`agent/trace/api.py`, `agent/trace/websocket.py`
-
-#### 控制端点
-
-需在 `api_server.py` 中配置 Runner。执行在后台异步进行,通过 WebSocket 监听进度。
-
-| 方法 | 路径 | 说明 |
-|------|------|------|
-| POST | `/api/traces` | 新建 Trace 并执行 |
-| POST | `/api/traces/{id}/run` | 运行(统一续跑 + 回溯) |
-| POST | `/api/traces/{id}/stop` | 停止运行中的 Trace |
-| POST | `/api/traces/{id}/reflect` | 触发反思,从执行历史中提取经验 |
+### Gateway
 
 
 ```bash
 ```bash
-# 新建
-curl -X POST http://43.106.118.91:8000/api/traces \
-  -H "Content-Type: application/json" \
-  -d '{"messages": [{"role": "user", "content": "分析项目架构"}], "model": "gpt-4o"}'
-
-# 续跑(after_sequence 为 null 或省略)
-curl -X POST http://43.106.118.91:8000/api/traces/{trace_id}/run \
-  -d '{"messages": [{"role": "user", "content": "继续深入分析"}]}'
-
-# 回溯:从 sequence 5 处截断,插入新消息重新执行
-curl -X POST http://43.106.118.91:8000/api/traces/{trace_id}/run \
-  -d '{"after_sequence": 5, "messages": [{"role": "user", "content": "换一个方案"}]}'
-
-# 重新生成:回溯到 sequence 5,不插入新消息,直接重跑
-curl -X POST http://43.106.118.91:8000/api/traces/{trace_id}/run \
-  -d '{"after_sequence": 5, "messages": []}'
-
-# 停止
-curl -X POST http://43.106.118.91:8000/api/traces/{trace_id}/stop
-
-# 反思:追加反思 prompt 运行,结果追加到 experiences 文件
-curl -X POST http://43.106.118.91:8000/api/traces/{trace_id}/reflect \
-  -d '{"focus": "为什么第三步选择了错误的方案"}'
-```
-
-响应立即返回 `{"trace_id": "...", "status": "started"}`,通过 `WS /api/traces/{trace_id}/watch` 监听实时事件。
-
-**实现**:`agent/trace/run_api.py`
-
-#### 经验端点
-
-| 方法 | 路径 | 说明 |
-|------|------|------|
-| GET  | `/api/experiences` | 读取经验文件内容 |
-
-**实现**:`agent/trace/run_api.py`
-
----
-
-## 数据模型
-
-### Trace(任务执行)
-
-一次完整的 Agent 执行。所有 Agent(主、子、人类协助)都是 Trace。
-
-```python
-@dataclass
-class Trace:
-    trace_id: str
-    mode: Literal["call", "agent"]           # 单次调用 or Agent 模式
-
-    # Prompt 标识
-    prompt_name: Optional[str] = None
-
-    # Agent 模式特有
-    task: Optional[str] = None
-    agent_type: Optional[str] = None
-
-    # 父子关系(Sub-Trace 特有)
-    parent_trace_id: Optional[str] = None    # 父 Trace ID
-    parent_goal_id: Optional[str] = None     # 哪个 Goal 启动的
-
-    # 状态
-    status: Literal["running", "completed", "failed", "stopped"] = "running"
-
-    # 统计
-    total_messages: int = 0
-    total_tokens: int = 0                    # 总 tokens(prompt + completion)
-    total_prompt_tokens: int = 0
-    total_completion_tokens: int = 0
-    total_cost: float = 0.0
-    total_duration_ms: int = 0
-
-    # 进度追踪
-    last_sequence: int = 0                   # 最新 message 的 sequence(全局递增,不复用)
-    head_sequence: int = 0                   # 当前主路径的头节点 sequence(用于 build_llm_messages)
-    last_event_id: int = 0                   # 最新事件 ID(用于 WS 续传)
-
-    # 配置
-    uid: Optional[str] = None
-    model: Optional[str] = None              # 默认模型
-    tools: Optional[List[Dict]] = None       # 工具定义(OpenAI 格式)
-    llm_params: Dict[str, Any] = {}          # LLM 参数(temperature 等)
-    context: Dict[str, Any] = {}             # 元数据(含 collaborators 列表)
-
-    # 当前焦点
-    current_goal_id: Optional[str] = None
-
-    # 结果
-    result_summary: Optional[str] = None
-    error_message: Optional[str] = None
-
-    # 时间
-    created_at: datetime
-    completed_at: Optional[datetime] = None
-```
-
-**实现**:`agent/trace/models.py`
-
-### Goal(目标节点)
-
-计划中的一个目标,支持层级结构。单独存储于 `goal.json`。
-
-```python
-@dataclass
-class Goal:
-    id: str                                  # 内部 ID("1", "2"...)
-    description: str
-    reason: str = ""                         # 创建理由
-    parent_id: Optional[str] = None          # 父 Goal ID
-    type: GoalType = "normal"                # normal | agent_call
-    status: GoalStatus = "pending"           # pending | in_progress | completed | abandoned
-    summary: Optional[str] = None            # 完成/放弃时的总结
-
-    # agent_call 特有(启动 Sub-Trace)
-    sub_trace_ids: Optional[List[str]] = None
-    agent_call_mode: Optional[str] = None    # explore | delegate | evaluate
-    sub_trace_metadata: Optional[Dict] = None
-
-    # 统计
-    self_stats: GoalStats                    # 自身 Messages 统计
-    cumulative_stats: GoalStats              # 包含子孙的累计统计
-
-    created_at: datetime
-```
-
-**Goal 类型**:
-- `normal` - 普通目标,由 Agent 直接执行
-- `agent_call` - 通过 `agent`/`evaluate` 工具创建的目标,会启动 Sub-Trace
-
-**agent_call 类型的 Goal**:
-- 调用 `agent`/`evaluate` 工具时自动设置
-- `agent_call_mode` 记录使用的模式(explore/delegate/evaluate)
-- `sub_trace_ids` 记录创建的所有 Sub-Trace ID
-- 状态转换:pending → in_progress(Sub-Trace 启动)→ completed(Sub-Trace 完成)
-- `summary` 包含格式化的汇总结果(explore 模式会汇总所有分支)
-
-**Goal 操作**(通过 goal 工具):
-- `add` - 添加顶层目标
-- `under` - 在指定目标下添加子目标
-- `after` - 在指定目标后添加兄弟目标
-- `focus` - 切换焦点到指定目标
-- `done` - 完成当前目标(附带 summary)
-- `abandon` - 放弃当前目标(附带原因)
-
-**实现**:`agent/trace/goal_models.py`, `agent/trace/goal_tool.py`
+# 安装 Gateway 客户端
+cd gateway
+pip install -e .
 
 
-### Message(执行消息)
-
-对应 LLM API 的消息,每条 Message 关联一个 Goal。消息通过 `parent_sequence` 形成树结构。
-
-```python
-@dataclass
-class Message:
-    message_id: str                          # 格式:{trace_id}-{sequence:04d}
-    trace_id: str
-    role: Literal["system", "user", "assistant", "tool"]
-    sequence: int                            # 全局顺序(递增,不复用)
-    parent_sequence: Optional[int] = None    # 父消息的 sequence(构成消息树)
-    goal_id: Optional[str] = None            # 关联的 Goal ID(初始消息为 None,系统会按需自动创建 root goal 兜底)
-    description: str = ""                    # 系统自动生成的摘要
-    tool_call_id: Optional[str] = None
-    content: Any = None
-
-    # 统计
-    prompt_tokens: Optional[int] = None
-    completion_tokens: Optional[int] = None
-    cost: Optional[float] = None
-    duration_ms: Optional[int] = None
-
-    # LLM 响应信息(仅 role="assistant")
-    finish_reason: Optional[str] = None
-
-    created_at: datetime
-
-    # [已弃用] 由 parent_sequence 树结构替代
-    status: Literal["active", "abandoned"] = "active"
-    abandoned_at: Optional[datetime] = None
+# 使用 CLI
+gateway-cli send --from my-agent --to target-agent --message "Hello"
+gateway-cli list
 ```
 ```
 
 
-**消息树(Message Tree)**:
-
-消息通过 `parent_sequence` 形成树。主路径 = 从 `trace.head_sequence` 沿 parent chain 回溯到 root。
-
-```
-正常对话:1 → 2 → 3 → 4 → 5       (每条的 parent 指向前一条)
-Rewind 到 3:3 → 6(parent=3) → 7   (新主路径,4-5 自动脱离)
-压缩 1-3:   8(summary, parent=None) → 6 → 7  (summary 跳过被压缩的消息)
-反思分支:   5 → 9(reflect, parent=5) → 10     (侧枝,不在主路径上)
-```
-
-`build_llm_messages` = 从 head 沿 parent_sequence 链回溯到 root,反转后返回。
-
-Message 提供格式转换方法:
-- `to_llm_dict()` → OpenAI 格式 Dict(用于 LLM 调用)
-- `from_llm_dict(d, trace_id, sequence, goal_id)` → 从 OpenAI 格式创建 Message
-
-**实现**:`agent/trace/models.py`
-
----
-
-## Agent 预设
-
-不同类型 Agent 的配置模板,控制工具权限和参数。
-
-```python
-@dataclass
-class AgentPreset:
-    allowed_tools: Optional[List[str]] = None  # None 表示允许全部
-    denied_tools: Optional[List[str]] = None   # 黑名单
-    max_iterations: int = 30
-    temperature: Optional[float] = None
-    skills: Optional[List[str]] = None         # 注入 system prompt 的 skill 名称列表;None = 加载全部
-    description: Optional[str] = None
-
-
-_DEFAULT_SKILLS = ["planning", "research", "browser"]
-
-AGENT_PRESETS = {
-    "default": AgentPreset(
-        allowed_tools=None,
-        max_iterations=30,
-        skills=_DEFAULT_SKILLS,
-        description="默认 Agent,拥有全部工具权限",
-    ),
-    "explore": AgentPreset(
-        allowed_tools=["read", "glob", "grep", "list_files"],
-        denied_tools=["write", "edit", "bash", "task"],
-        max_iterations=15,
-        skills=["planning"],
-        description="探索型 Agent,只读权限,用于代码分析",
-    ),
-    "analyst": AgentPreset(
-        allowed_tools=["read", "glob", "grep", "web_search", "webfetch"],
-        denied_tools=["write", "edit", "bash", "task"],
-        temperature=0.3,
-        max_iterations=25,
-        skills=["planning", "research"],
-        description="分析型 Agent,用于深度分析和研究",
-    ),
-}
-```
-
-**实现**:`agent/core/presets.py`
-
-**用户自定义**:项目级配置文件(如 `examples/how/presets.json`)可通过 `register_preset()` 注册额外预设。项目专用的 Agent 类型建议放在项目目录下,而非内置预设。
-
----
-
-## 子 Trace 机制
-
-通过 `agent` 工具创建子 Agent 执行任务。`task` 参数为字符串时为单任务(delegate),为列表时并行执行多任务(explore)。支持通过 `messages` 参数预置消息,通过 `continue_from` 参数续跑已有 Sub-Trace。
-
-`agent` 工具负责创建 Sub-Trace 和初始化 GoalTree(因为需要设置自定义 context 元数据和命名规则),创建完成后将 `trace_id` 传给 `RunConfig`,由 Runner 接管后续执行。工具同时维护父 Trace 的 `context["collaborators"]` 列表。
-
-### agent 工具
-
-```python
-@tool(description="创建 Agent 执行任务")
-async def agent(
-    task: Union[str, List[str]],
-    messages: Optional[Union[Messages, List[Messages]]] = None,
-    continue_from: Optional[str] = None,
-    agent_type: Optional[str] = None,
-    skills: Optional[List[str]] = None,
-    context: Optional[dict] = None,
-) -> Dict[str, Any]:
-```
-
-- `agent_type`: 子 Agent 类型,决定工具权限和默认 skills(对应 `AgentPreset` 名称,如 `"deconstruct"`)
-- `skills`: 覆盖 preset 默认值,显式指定注入 system prompt 的 skill 列表
-
-**单任务(delegate)**:`task: str`
-- 创建单个 Sub-Trace
-- 完整工具权限(除 agent/evaluate 外,防止递归)
-- 支持 `continue_from` 续跑已有 Sub-Trace
-- 支持 `messages` 预置上下文消息
-
-**多任务(explore)**:`task: List[str]`
-- 使用 `asyncio.gather()` 并行执行所有任务
-- 每个任务创建独立的 Sub-Trace
-- 只读工具权限(read_file, grep_content, glob_files, goal)
-- `messages` 支持 1D(共享)或 2D(per-agent)
-- 不支持 `continue_from`
-- 汇总所有分支结果返回
-
-### evaluate 工具
-
-```python
-@tool(description="评估目标执行结果是否满足要求")
-async def evaluate(
-    messages: Optional[Messages] = None,
-    target_goal_id: Optional[str] = None,
-    continue_from: Optional[str] = None,
-    context: Optional[dict] = None,
-) -> Dict[str, Any]:
-```
-
-- 代码自动从 GoalTree 注入目标描述(无需 criteria 参数)
-- 模型把执行结果和上下文放在 `messages` 中
-- `target_goal_id` 默认为当前 goal_id
-- 只读工具权限
-- 返回评估结论和改进建议
-
-### 消息类型别名
-
-定义在 `agent/trace/models.py`,用于工具参数和 runner/LLM API 接口:
-
-```python
-ChatMessage = Dict[str, Any]                          # 单条 OpenAI 格式消息
-Messages = List[ChatMessage]                          # 消息列表
-MessageContent = Union[str, List[Dict[str, str]]]     # content 字段(文本或多模态)
-```
-
-**实现位置**:`agent/tools/builtin/subagent.py`
-
-**详细文档**:[工具系统 - Agent/Evaluate 工具](./tools.md#agent-工具)
-
-### ask_human 工具
-
-创建阻塞式 Trace,等待人类通过 IM/邮件等渠道回复。
-
-**注意**:此功能规划中,暂未实现。
-
----
-
-## Active Collaborators(活跃协作者)
-
-任务执行中与模型密切协作的实体(子 Agent 或人类),按 **与当前任务的关系** 分类,而非按 human/agent 分类:
-
-| | 持久存在(外部可查) | 任务内活跃(需要注入) |
-|---|---|---|
-| Agent | 专用 Agent(代码审查等) | 当前任务创建的子 Agent |
-| Human | 飞书通讯录 | 当前任务中正在对接的人 |
-
-### 数据模型
-
-活跃协作者存储在 `trace.context["collaborators"]`:
-
-```python
-{
-    "name": "researcher",            # 名称(模型可见)
-    "type": "agent",                 # agent | human
-    "trace_id": "abc-@delegate-001", # trace_id(agent 场景)
-    "status": "completed",           # running | waiting | completed | failed
-    "summary": "方案A最优",          # 最近状态摘要
-}
-```
-
-### 注入方式
-
-与 GoalTree 一同周期性注入(每 10 轮),渲染为 Markdown:
-
-```markdown
-## Active Collaborators
-- researcher [agent, completed]: 方案A最优
-- 谭景玉 [human, waiting]: 已发送方案确认,等待回复
-- coder [agent, running]: 正在实现特征提取模块
-```
-
-列表为空时不注入。
-
-### 维护
-
-各工具负责更新 collaborators 列表(通过 `context["store"]` 写入 trace.context):
-- `agent` 工具:创建/续跑子 Agent 时更新
-- `feishu` 工具:发送消息/收到回复时更新
-- Runner 只负责读取和注入
-
-**持久联系人/Agent**:通过工具按需查询(如 `feishu_get_contact_list`),不随任务注入。
-
-**实现**:`agent/core/runner.py:AgentRunner._build_context_injection`, `agent/tools/builtin/subagent.py`
-
----
-
-## 工具系统
-
-### 核心概念
-
-```python
-@tool()
-async def my_tool(arg: str, ctx: ToolContext) -> ToolResult:
-    return ToolResult(
-        title="Success",
-        output="Result content",
-        long_term_memory="Short summary"  # 可选:压缩后保留的摘要
-    )
-```
-
-| 类型 | 作用 |
-|------|------|
-| `@tool` | 装饰器,自动注册工具并生成 Schema |
-| `ToolResult` | 工具执行结果,支持双层记忆 |
-| `ToolContext` | 工具执行上下文,依赖注入 |
-
-### 工具分类
-
-| 目录 | 工具 | 说明 |
-|-----|------|------|
-| `trace/` | goal | Agent 内部计划管理 |
-| `builtin/` | agent, evaluate | 子 Agent 创建与评估 |
-| `builtin/file/` | read, write, edit, glob, grep | 文件操作 |
-| `builtin/browser/` | browser actions | 浏览器自动化 |
-| `builtin/` | bash, sandbox, search, webfetch, skill, ask_human | 其他工具 |
-
-### 双层记忆管理
-
-大输出(如网页抓取)只传给 LLM 一次,之后用摘要替代:
-
-```python
-ToolResult(
-    output="<10K tokens 的完整内容>",
-    long_term_memory="Extracted 10000 chars from amazon.com",
-    include_output_only_once=True
-)
-```
-
-**详细文档**:[工具系统](./tools.md)
-
----
-
-## Skills 系统
-
-### 分类
-
-| 类型 | 加载位置 | 加载时机 |
-|------|---------|---------|
-| **内置 Skill** | System Prompt | Agent 启动时自动注入 |
-| **项目 Skill** | System Prompt | Agent 启动时按 preset/call-site 过滤后注入 |
-| **普通 Skill** | 对话消息 | 模型调用 `skill` 工具时 |
-
-### 目录结构
-
-```
-agent/memory/skills/         # 内置 Skills(始终加载)
-├── planning.md              # 计划与 Goal 工具使用
-├── research.md              # 搜索与内容研究
-└── browser.md               # 浏览器自动化
-
-./skills/                    # 项目自定义 Skills
-```
-
-### Skills 过滤(call-site 选择)
-
-不同 Agent 类型所需的 skills 不同。过滤优先级:
-
-1. `agent()` 工具的 `skills` 参数(显式指定,最高优先级)
-2. `AgentPreset.skills`(preset 默认值)
-3. `None`(加载全部,向后兼容)
-
-示例:调用子 Agent 时只注入解构相关 skill:
-```python
-agent(task="...", agent_type="deconstruct", skills=["planning", "deconstruct"])
-```
-
-**实现**:`agent/memory/skill_loader.py`
-
-**详细文档**:[Skills 使用指南](./skills.md)
-
----
-
-## Experiences 系统
-
-从执行历史中提取的经验规则,用于指导未来任务。
-
-### 存储规范
-
-经验以 Markdown 文件存储(默认 `./.cache/experiences.md`),人类可读、可编辑、可版本控制。
-
-文件格式:
-
-```markdown
----
-id: ex_001
-trace_id: trace-xxx
-category: tool_usage
-tags: {state: ["large_file", "dirty_repo"], intent: ["batch_edit", "safe_modify"]}
-metrics: {helpful: 12, harmful: 0}
-created_at: 2026-02-12 15:30
----
-
----
-id: ex_002
-...
-```
----
-
-
-### 反思机制(Reflect)
-
-通过 POST /api/traces/{id}/reflect 触发,旨在将原始执行历史提炼为可复用的知识。
-    1. 分叉反思:在 trace 末尾追加 user message(含反思与打标 Prompt),作为侧枝执行。
-    2. 结构化生成:
-        ·归类:将经验分配至 tool_usage(工具)、logic_flow(逻辑)、environment(环境)等。
-        ·打标:提取 state(环境状态)与 intent(用户意图)语义标签。
-        ·量化:初始 helpful 设为 1。
-    3. 持久化:将带有元数据的 Markdown 块追加至 experiences.md。
-
-实现:agent/trace/run_api.py:reflect_trace
-
-### 语义注入与匹配流程
-新建 Trace 时,Runner 采用“分析-检索-注入”三阶段策略,实现精准经验推荐。
-    1. 意图预分析
-    Runner 调用 utility_llm 对初始任务进行语义提取:
-        -输入:"优化这个项目的 Docker 构建速度"
-        -输出:{state: ["docker", "ci"], intent: ["optimization"]}
-    2. 语义检索
-        在 _load_experiences 中根据标签进行语义匹配(优先匹配 intent,其次是 state),筛选出相关度最高的 Top-K 条经验。
-    3. 精准注入
-        将匹配到的经验注入第一条 user message 末尾:
-```python
-# _build_history 中(仅新建模式):
-if not config.trace_id:
-    relevant_ex = self.experience_retriever.search(task_tags)
-    if relevant_ex:
-        formatted_ex = "\n".join([f"- [{e.id}] {e.content} (Helpful: {e.helpful})" for e in relevant_ex])
-        first_user_msg["content"] += f"\n\n## 参考经验\n\n{formatted_ex}"
-```
-实现:agent/core/runner.py:AgentRunner._build_history
-
-### 经验获取工具
-不再仅限于启动时自动注入,而是通过内置工具供 Agent 在需要时主动调用。当执行结果不符合预期或进入未知领域时,Agent 应优先使用此工具。
-工具定义:
-
-```python
-@tool(description="根据当前任务状态和意图,从经验库中检索相关的历史经验")
-async def get_experience(
-    intent: Optional[str] = None, 
-    state: Optional[str] = None
-) -> Dict[str, Any]:
-    """
-    参数:
-        intent: 想要达成的目标意图 (如 "optimization", "debug")
-        state: 当前环境或遇到的问题状态 (如 "docker_build_fail", "permission_denied")
-    """
-```
-实现: agent/tools/builtin/experience.py
-
-- 语义匹配与应用流程
-    当 Agent 调用 get_experience 时,系统执行以下逻辑:
-    1. 语义检索:根据传入的 intent 或 state 标签,在 experiences.md 中进行匹配。匹配权重:intent > state > helpful 评分。
-    2. 动态注入:工具返回匹配到的 Top-K 条经验(含 ID 和内容)。
-    3. 策略应用:Agent 接收到工具返回的经验后,需在后续 thought 中声明所选用的策略 ID(如 [ex_001]),并据此调整 goal_tree 或工具调用序列。
-
-## Context 压缩
-
-### 两级压缩策略
-
-#### Level 1:GoalTree 过滤(确定性,零成本)
-
-每轮 agent loop 构建 `llm_messages` 时自动执行:
-- 始终保留:system prompt、第一条 user message(含 GoalTree 精简视图)、当前 focus goal 的消息
-- 跳过 completed/abandoned goals 的消息(信息已在 GoalTree summary 中)
-- 通过 Message Tree 的 parent_sequence 实现跳过
-
-大多数情况下 Level 1 足够。
-
-#### Level 2:LLM 总结(仅在 Level 1 后仍超限时触发)
-
-触发条件:Level 1 之后 token 数仍超过阈值(默认 `max_tokens × 0.8`)。
-
-流程:
-1. **经验提取**:先在消息列表末尾追加反思 prompt → 主模型回复 → 追加到 `./.cache/experiences.md`。反思消息为侧枝(parent_sequence 分叉,不在主路径上)
-2. **压缩**:在消息列表末尾追加压缩 prompt(含 GoalTree 完整视图) → 主模型回复 → summary 存为新消息,其 `parent_sequence` 跳过被压缩的范围
-
-### GoalTree 双视图
-
-`to_prompt()` 支持两种模式:
-- `include_summary=False`(默认):精简视图,用于日常周期性注入
-- `include_summary=True`:含所有 completed goals 的 summary,用于 Level 2 压缩时提供上下文
-
-### 压缩存储
-
-- 原始消息永远保留在 `messages/`
-- 压缩 summary 作为普通 Message 存储
-- 通过 `parent_sequence` 树结构实现跳过,无需 compression events 或 skip list
-- Rewind 到压缩区域内时,summary 脱离主路径,原始消息自动恢复
-
-**实现**:`agent/trace/compaction.py`, `agent/trace/goal_models.py`
-
-**详细文档**:[Context 管理](./context-management.md)
-
----
-
-## 存储接口
-
-```python
-class TraceStore(Protocol):
-    async def create_trace(self, trace: Trace) -> None: ...
-    async def get_trace(self, trace_id: str) -> Trace: ...
-    async def update_trace(self, trace_id: str, **updates) -> None: ...
-    async def add_message(self, message: Message) -> None: ...
-    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 append_event(self, trace_id: str, event_type: str, payload: Dict) -> int: ...
-```
-
-`get_main_path_messages` 从 `head_sequence` 沿 `parent_sequence` 链回溯,返回主路径上的有序消息列表。
-
-**实现**:
-- 协议定义:`agent/trace/protocols.py`
-- 文件存储:`agent/trace/store.py:FileSystemTraceStore`
-
-### 存储结构
-
-```
-.trace/
-├── {trace_id}/
-│   ├── meta.json        # Trace 元数据(含 tools 定义)
-│   ├── goal.json        # GoalTree(mission + goals 列表)
-│   ├── events.jsonl     # 事件流(goal 变更、sub_trace 生命周期等)
-│   └── messages/        # Messages
-│       ├── {trace_id}-0001.json
-│       └── ...
-│
-└── {trace_id}@explore-{序号}-{timestamp}-001/  # 子 Trace
-    └── ...
-```
-
-**events.jsonl 说明**:
-- 记录 Trace 执行过程中的关键事件
-- 每行一个 JSON 对象,包含 event_id、event 类型、时间戳等
-- 主要事件类型:goal_added, goal_updated, sub_trace_started, sub_trace_completed, rewind
-- 用于实时监控和历史回放
-
-**Sub-Trace 目录命名**:
-- Explore: `{parent}@explore-{序号:03d}-{timestamp}-001`
-- Delegate: `{parent}@delegate-{timestamp}-001`
-- Evaluate: `{parent}@evaluate-{timestamp}-001`
-
-**meta.json 示例**:
-```json
-{
-  "trace_id": "0415dc38-...",
-  "mode": "agent",
-  "task": "分析代码结构",
-  "agent_type": "default",
-  "status": "running",
-  "model": "google/gemini-2.5-flash",
-  "tools": [...],
-  "llm_params": {"temperature": 0.3},
-  "context": {
-    "collaborators": [
-      {"name": "researcher", "type": "agent", "trace_id": "...", "status": "completed", "summary": "方案A最优"}
-    ]
-  },
-  "current_goal_id": "3"
-}
-```
-
----
-
-## 设计决策
-
-详见 [设计决策文档](./decisions.md)
-
-**核心决策**:
-
-1. **所有 Agent 都是 Trace** - 主 Agent、子 Agent、人类协助统一为 Trace,通过 `parent_trace_id` 和 `spawn_tool` 区分
-
-2. **trace/ 模块统一管理执行状态** - 合并原 execution/ 和 goal/,包含计划管理和 Agent 内部控制工具
-
-3. **tools/ 专注外部交互** - 文件、命令、网络、浏览器等与外部世界的交互
-
-4. **Agent 预设替代 Sub-Agent 配置** - 通过 `core/presets.py` 定义不同类型 Agent 的工具权限和参数
+详见:[Gateway README](../gateway/README.md) 和 [A2A IM 文档](../gateway/client/a2a_im.md)
 
 
 ---
 ---
 
 
 ## 相关文档
 ## 相关文档
 
 
-| 文档 | 内容 |
-|-----|------|
-| [Context 管理](./context-management.md) | Goals、压缩、Plan 注入策略 |
-| [工具系统](./tools.md) | 工具定义、注册、双层记忆 |
-| [Skills 指南](./skills.md) | Skill 分类、编写、加载 |
-| [多模态支持](./multimodal.md) | 图片、PDF 处理 |
-| [设计决策](./decisions.md) | 架构决策记录 |
-| [测试指南](./testing.md) | 测试策略和命令 |
+完整的文档列表见各模块的 README:
+- [Agent Core 文档](../agent/README.md#文档)
+- [Gateway 文档](../gateway/README.md#文档)

+ 651 - 0
docs/a2a-im.md

@@ -0,0 +1,651 @@
+# A2A IM:Agent 即时通讯系统
+
+**更新日期:** 2026-03-04
+
+## 文档维护规范
+
+0. **先改文档,再动代码** - 新功能或重大修改需先完成文档更新、并完成审阅后,再进行代码实现;除非改动较小、不被文档涵盖
+1. **文档分层,链接代码** - 重要或复杂设计可以另有详细文档;关键实现需标注代码文件路径;格式:`module/file.py:function_name`
+2. **简洁快照,日志分离** - 只记录最重要的、与代码准确对应的或者明确的已完成的设计的信息,避免推测、建议,或大量代码;决策依据或修改日志若有必要,可在`docs/decisions.md`另行记录
+
+---
+
+## 文档说明
+
+本文档描述 Agent 间即时通讯(A2A IM)系统的架构和实现。
+
+**相关文档**:
+- [MAMP 协议](./research/a2a-mamp-protocol.md):消息格式和传输协议
+- [A2A 跨设备通信](./research/a2a-cross-device.md):内部 Agent 通信方案
+- [Agent 框架](./README.md):核心 Agent 能力
+- [Enterprise 层](../gateway/docs/enterprise/overview.md):组织级功能
+
+---
+
+## 系统概述
+
+A2A IM 是一个**任务导向的 Agent 即时通讯系统**,支持:
+- Agent 间消息传递(点对点、通过 Gateway)
+- 活跃协作者管理(当前任务)
+- 全局联系人管理(历史记录)
+- 在线状态查询
+- 对话历史追溯
+
+**与传统 IM 的区别**:
+- 任务导向(非纯聊天)
+- 长时间处理(分钟到小时)
+- 工具调用和执行记录
+- 完整的 Trace 追溯
+
+---
+
+## 架构层次关系
+
+A2A IM 在整体架构中的定位:
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Enterprise Layer(组织级)- 可选                              │
+│ - 认证和授权(飞书 OAuth、API Key、JWT)                      │
+│ - 审计和监控(操作日志、成本记录、安全事件)                    │
+│ - 多租户和权限控制(角色验证、资源访问控制)                    │
+│ - 成本管理和限额(用户级/组织级限额、超限告警)                 │
+│                                                              │
+│ 实现位置: gateway/enterprise/                                │
+│ 文档: gateway/docs/enterprise/overview.md                   │
+└─────────────────────────────────────────────────────────────┘
+
+┌─────────────────────────────────────────────────────────────┐
+│ A2A IM Gateway(通讯层)★ 本文档                             │
+│ - Agent 注册和发现(Registry)                                │
+│ - 消息路由(Gateway Router)                                  │
+│ - 活跃协作者管理(Collaborators)                             │
+│ - 在线状态管理(Heartbeat)                                   │
+│ - 联系人管理(ContactStore)                                  │
+│                                                              │
+│ 实现位置: gateway/core/                                       │
+│ 文档: docs/a2a-im.md(本文档)                                │
+└─────────────────────────────────────────────────────────────┘
+         ↕ 使用(单向依赖)
+┌─────────────────────────────────────────────────────────────┐
+│ Agent Core(核心层)                                          │
+│ - Trace、Message、Goal 管理                                  │
+│ - 工具系统(文件、命令、网络、浏览器)                          │
+│ - LLM 集成(Gemini、OpenRouter、Yescode)                    │
+│ - Skills 和 Memory(跨会话知识)                              │
+│ - 子 Agent 机制(agent 工具)                                 │
+│                                                              │
+│ 实现位置: agent/                                              │
+│ 文档: docs/README.md                                         │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### 层次说明
+
+**Agent Core(核心层)**:
+- 提供单个 Agent 的执行能力
+- 管理 Trace、Message、Goal
+- 提供工具系统和 LLM 集成
+- 支持子 Agent 创建(通过 `agent` 工具)
+- **独立部署**:可以不依赖 Gateway 运行
+
+**A2A IM Gateway(通讯层)**:
+- 与 Agent Core 并列,独立的系统
+- 提供 Agent 间通讯能力
+- 管理 Agent 注册和在线状态
+- 路由消息到目标 Agent
+- 维护活跃协作者和联系人
+- **依赖 Agent Core**:使用 ToolContext、TraceStore 等组件
+- **独立部署**:可以作为独立服务部署
+
+**Enterprise(组织层)**:
+- 可选的企业功能扩展
+- 提供企业级管理和控制
+- 认证、授权、审计
+- 多租户和成本管理
+- **可以集成到 Gateway**:作为 Gateway 的扩展模块
+- **也可以独立部署**:作为独立的 Enterprise Gateway 服务
+
+### 依赖关系
+
+```
+Enterprise → Gateway → Agent Core
+(可选)    (通讯)    (核心)
+
+- Agent Core 不依赖任何其他层(独立)
+- Gateway 依赖 Agent Core(单向依赖)
+- Enterprise 依赖 Gateway(可选扩展)
+```
+
+### 部署方式
+
+**方式 1:单体部署(个人/小团队)**
+```
+一个进程:
+├─ Agent Core
+└─ Gateway(包含 Enterprise 模块)
+```
+
+**方式 2:分离部署(中等规模)**
+```
+进程 1:Agent Core
+进程 2:Gateway(包含 Enterprise 模块)
+```
+
+**方式 3:分层部署(大规模/企业)**
+```
+进程 1:Agent Core
+进程 2:Gateway Core
+进程 3:Enterprise Gateway
+```
+
+---
+
+## 架构设计
+
+### 三层架构
+
+```
+┌─────────────────────────────────────────────────┐
+│ Layer 3: Agent 逻辑层                            │
+│ - Trace, Goal, Messages                         │
+│ - 工具调用和执行                                  │
+└─────────────────────────────────────────────────┘
+                    ↕
+┌─────────────────────────────────────────────────┐
+│ Layer 2: A2A IM 层                               │
+│ - 活跃协作者管理                                  │
+│ - 全局联系人管理                                  │
+│ - conversation_id ↔ trace_id 映射                │
+└─────────────────────────────────────────────────┘
+                    ↕
+┌─────────────────────────────────────────────────┐
+│ Layer 1: Gateway 层                              │
+│ - Agent 注册和发现                                │
+│ - 消息路由                                        │
+│ - 在线状态管理                                    │
+│ - WebSocket 长连接                                │
+└─────────────────────────────────────────────────┘
+```
+
+### 通信模式
+
+**模式 1:内部 Agent(同进程)**
+```
+Agent A → 直接调用 → Agent B
+(复用现有 agent 工具)
+```
+
+**模式 2:跨设备 Agent(组织内)**
+```
+PC Agent → WebSocket → Gateway → 云端 Agent
+(反向连接,无需公网 IP)
+```
+
+**模式 3:外部 Agent(跨组织)**
+```
+Agent A → MAMP 协议 → Agent B
+(点对点 HTTP)
+```
+
+---
+
+## 数据模型
+
+### 活跃协作者(Layer 2)
+
+存储在 `trace.context["collaborators"]`,记录当前任务的协作者。
+
+```python
+{
+    "name": "code-analyst",
+    "type": "agent",  # agent | human
+    "agent_uri": "agent://other.com/code-analyst",
+    "trace_id": "abc-123",
+    "conversation_id": "conv-456",
+    "status": "running",  # running | waiting | completed | failed
+    "summary": "正在分析代码架构",
+    "last_message_at": "2026-03-04T10:30:00Z"
+}
+```
+
+**实现位置**:`agent/core/runner.py:AgentRunner._build_context_injection`
+
+### 全局联系人(Layer 2)
+
+存储在 `.trace/contacts.json`,记录所有历史联系过的 Agent。
+
+```python
+{
+    "agent_uri": "agent://other.com/code-analyst",
+    "name": "Code Analyst",
+    "type": "agent",
+
+    # 身份信息(从 Agent Card 获取)
+    "card": {
+        "description": "专注于代码分析",
+        "capabilities": ["code_analysis", "file_read"],
+        "owner": {"user_name": "张三"}
+    },
+
+    # 交互统计
+    "stats": {
+        "first_contact": "2026-02-01T10:00:00Z",
+        "last_contact": "2026-03-04T10:30:00Z",
+        "total_conversations": 15,
+        "total_messages": 127
+    },
+
+    # 最近对话
+    "recent_conversations": [
+        {
+            "conversation_id": "conv-456",
+            "trace_id": "abc-123",
+            "started_at": "2026-03-04T10:00:00Z",
+            "last_message": "分析完成",
+            "status": "active"
+        }
+    ],
+
+    # 关系标签
+    "tags": ["code", "architecture"],
+    "pinned": false
+}
+```
+
+**实现位置**:`agent/trace/contact_store.py`
+
+### Agent 注册信息(Layer 1)
+
+存储在 Gateway,记录在线 Agent 的连接信息。
+
+```python
+{
+    "agent_uri": "agent://internal/code-analyst",
+    "connection_type": "websocket",  # websocket | http
+    "websocket": <WebSocket>,  # WebSocket 连接对象
+    "http_endpoint": "http://localhost:8001",  # HTTP 端点
+    "last_heartbeat": "2026-03-04T10:30:00Z",
+    "capabilities": ["code_analysis", "file_read"]
+}
+```
+
+**实现位置**:`gateway/core/registry.py`
+
+---
+
+## 核心功能
+
+### 1. Agent 注册和发现
+
+**PC Agent 启动时注册**:
+
+```python
+# 建立 WebSocket 长连接
+ws = await websockets.connect("wss://gateway.com/gateway/connect")
+
+# 注册
+await ws.send(json.dumps({
+    "type": "register",
+    "agent_uri": "agent://internal/my-agent",
+    "capabilities": ["file_read", "bash"]
+}))
+
+# 保持心跳
+while True:
+    await ws.send(json.dumps({"type": "heartbeat"}))
+    await asyncio.sleep(30)
+```
+
+**实现位置**:`gateway/core/client.py`
+
+### 2. 消息路由
+
+**通过 Gateway 发送消息**:
+
+```python
+# 发送方
+POST /gateway/send
+{
+    "to": "agent://internal/code-analyst",
+    "content": "帮我分析代码"
+}
+
+# Gateway 查找目标 Agent
+agent_info = registry.lookup("agent://internal/code-analyst")
+
+# 通过 WebSocket 推送
+await agent_info["websocket"].send(json.dumps({
+    "type": "message",
+    "from": "agent://internal/caller",
+    "content": "帮我分析代码"
+}))
+```
+
+**实现位置**:`gateway/core/router.py`
+
+### 3. 活跃协作者管理
+
+**发送消息时自动更新**:
+
+```python
+# agent/tools/builtin/a2a_im.py
+
+async def send_to_agent(...):
+    # 发送消息
+    response = await gateway_client.send(...)
+
+    # 更新活跃协作者
+    await update_active_collaborator(
+        trace_id=ctx.trace_id,
+        agent_uri=target_agent,
+        conversation_id=response["conversation_id"],
+        status="waiting"
+    )
+```
+
+**周期性注入到 Agent 上下文**:
+
+```python
+# agent/core/runner.py
+
+if iteration % 10 == 0:
+    collaborators = trace.context.get("collaborators", [])
+    inject_collaborators_markdown(collaborators)
+```
+
+### 4. 全局联系人管理
+
+**查询联系人**:
+
+```python
+# 通过工具查询
+contacts = await get_contacts(
+    type="agent",
+    status="online",
+    tags=["code"]
+)
+```
+
+**自动维护**:
+
+```python
+# 发送/接收消息时自动更新
+await contact_store.update(
+    agent_uri=target_agent,
+    last_contact=datetime.now(),
+    increment_message_count=True
+)
+```
+
+**实现位置**:`agent/trace/contact_store.py`
+
+### 5. 在线状态查询
+
+**查询 Agent 在线状态**:
+
+```python
+GET /gateway/status/{agent_uri}
+
+返回:
+{
+    "agent_uri": "agent://internal/code-analyst",
+    "status": "online",  # online | offline
+    "last_seen": "2026-03-04T10:30:00Z"
+}
+```
+
+**实现位置**:`gateway/core/router.py:get_agent_status`
+
+---
+
+## API 端点
+
+### Gateway API
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| WS | `/gateway/connect` | Agent 注册和保持连接 |
+| POST | `/gateway/send` | 发送消息到其他 Agent |
+| GET | `/gateway/status/{agent_uri}` | 查询 Agent 在线状态 |
+| GET | `/gateway/agents` | 列出所有在线 Agent |
+
+### A2A IM API
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| GET | `/api/traces/{id}/collaborators` | 查询活跃协作者 |
+| GET | `/api/contacts` | 查询全局联系人 |
+| GET | `/api/contacts/{agent_uri}` | 查询特定联系人详情 |
+| GET | `/api/contacts/{agent_uri}/conversations` | 查询对话历史 |
+
+---
+
+## 工具系统
+
+### send_to_agent 工具
+
+发送消息到其他 Agent(内部或外部)。
+
+```python
+@tool(description="发送消息到其他 Agent")
+async def send_to_agent(
+    target_agent: str,  # agent://domain/id
+    message: str,
+    conversation_id: Optional[str] = None,
+    ctx: ToolContext = None
+) -> ToolResult
+```
+
+**实现位置**:`agent/tools/builtin/a2a_im.py:send_to_agent`
+
+### get_active_collaborators 工具
+
+查询当前任务的活跃协作者。
+
+```python
+@tool(description="查询当前任务的活跃协作者")
+async def get_active_collaborators(
+    ctx: ToolContext
+) -> ToolResult
+```
+
+**实现位置**:`agent/tools/builtin/a2a_im.py:get_active_collaborators`
+
+### get_contacts 工具
+
+查询全局联系人列表。
+
+```python
+@tool(description="查询所有联系过的 Agent")
+async def get_contacts(
+    type: Optional[str] = None,  # agent | human
+    status: Optional[str] = None,  # online | offline
+    tags: Optional[List[str]] = None,
+    ctx: ToolContext = None
+) -> ToolResult
+```
+
+**实现位置**:`agent/tools/builtin/a2a_im.py:get_contacts`
+
+---
+
+## Skill 系统
+
+### a2a_im.md Skill
+
+提供 A2A IM 使用指南,注入到 Agent 的 system prompt。
+
+**内容**:
+- 如何发送消息到其他 Agent
+- 如何查询活跃协作者
+- 如何查询联系人
+- 最佳实践
+
+**实现位置**:`agent/memory/skills/a2a_im.md`
+
+---
+
+## 使用示例
+
+### 场景 1:调用其他 Agent 协作
+
+```python
+# Agent A 需要代码分析帮助
+result = await send_to_agent(
+    target_agent="agent://internal/code-analyst",
+    message="帮我分析 /path/to/project 的架构"
+)
+
+# 继续对话
+result2 = await send_to_agent(
+    target_agent="agent://internal/code-analyst",
+    message="重点分析 core 模块",
+    conversation_id=result["conversation_id"]
+)
+```
+
+### 场景 2:查询活跃协作者
+
+```python
+# 查看当前任务中有哪些 Agent 在协作
+collaborators = await get_active_collaborators()
+
+# 输出:
+# ## 活跃协作者
+# - code-analyst [agent, completed]: 分析完成,发现3个问题
+# - test-runner [agent, running]: 正在运行测试
+```
+
+### 场景 3:查询联系人
+
+```python
+# 查找擅长代码分析的 Agent
+contacts = await get_contacts(
+    type="agent",
+    tags=["code", "architecture"]
+)
+
+# 输出:
+# ## 联系人列表
+# 🟢 code-analyst - agent://internal/code-analyst
+#    最后联系: 2026-03-04 10:30
+#    对话次数: 15
+```
+
+---
+
+## 架构决策
+
+### 决策 1:Gateway 与 Agent 并列而非包含
+
+**问题**:Gateway 应该放在 agent/ 内部还是与 agent/ 并列?
+
+**决策**:与 agent/ 并列
+
+**理由**:
+1. **解耦**:Gateway 和 Agent Core 是两个独立的系统
+2. **独立部署**:Gateway 可以独立部署和扩展
+3. **职责清晰**:Agent Core 负责单 Agent 执行,Gateway 负责 Agent 间通讯
+4. **依赖关系**:Gateway 依赖 Agent Core(单向),但 Agent Core 不依赖 Gateway
+
+**实现**:
+- 目录结构:`gateway/` 与 `agent/` 并列
+- Import 路径:`from gateway.core import ...`
+
+### 决策 2:Enterprise 与 Gateway 的关系
+
+**问题**:Enterprise 应该是 Gateway 的上层(分层架构)还是 Gateway 的模块(模块化架构)?
+
+**决策**:根据阶段选择
+
+**MVP 阶段(当前)**:模块化架构
+- Enterprise 作为 Gateway 的可选模块
+- 部署简单,快速迭代
+- 适合中小规模
+
+**大规模阶段(未来)**:可选分层架构
+- Enterprise 作为独立的 Gateway 层
+- 可独立扩容,团队协作
+- 适合大规模部署
+
+**理由**:
+1. **灵活性**:两种架构都可以实现可选部署
+2. **演进路径**:从模块化开始,需要时重构为分层
+3. **规模决定**:小规模用模块化,大规模用分层
+
+**实现**:
+- 当前:`gateway/enterprise/` 作为可选模块
+- 未来:可重构为独立的 `enterprise_gateway/` 服务
+
+### 决策 3:活跃协作者的管理方式
+
+**问题**:活跃协作者信息应该如何存储和管理?
+
+**决策**:存储在 `trace.context["collaborators"]`,由工具自动维护
+
+**理由**:
+1. **复用现有机制**:Agent Core 已有 context 机制
+2. **自动注入**:Runner 周期性注入到 Agent 上下文(每 10 轮)
+3. **工具维护**:send_to_agent 等工具自动更新
+4. **与 Goal 一致**:与 GoalTree 一同注入,保持一致性
+
+**实现位置**:
+- 存储:`trace.context["collaborators"]`
+- 注入:`agent/core/runner.py:AgentRunner._build_context_injection`
+- 更新:`agent/tools/builtin/a2a_im.py:_update_active_collaborator`
+
+---
+
+## 实现路线图
+
+### Phase 1:基础功能(1-2 周)
+
+**目标**:实现核心通信能力
+
+**任务**:
+1. 实现 Gateway(注册、路由、WebSocket)
+2. 实现 send_to_agent 工具
+3. 实现活跃协作者自动更新
+4. 实现 a2a_im.md Skill
+
+**实现位置**:
+- `gateway/core/`
+- `agent/tools/builtin/a2a_im.py`
+- `agent/memory/skills/a2a_im.md`
+
+### Phase 2:联系人管理(1 周)
+
+**目标**:完善联系人和历史记录
+
+**任务**:
+1. 实现 ContactStore
+2. 实现 get_contacts 工具
+3. 实现对话历史查询
+4. 实现在线状态查询
+
+**实现位置**:
+- `agent/trace/contact_store.py`
+- `agent/tools/builtin/a2a_im.py`
+
+### Phase 3:增强功能(可选)
+
+**目标**:提升用户体验
+
+**任务**:
+1. 实现消息队列(异步处理)
+2. 实现 Agent 发现和推荐
+3. 实现关系标签和分组
+4. 实现 UI 界面
+
+---
+
+## 相关文档
+
+- [MAMP 协议](./research/a2a-mamp-protocol.md):消息格式和传输协议
+- [A2A 跨设备通信](./research/a2a-cross-device.md):内部 Agent 通信方案
+- [工具系统](../agent/docs/tools.md):工具定义、注册
+- [Skills 指南](../agent/docs/skills.md):Skill 分类、编写、加载
+- [Agent 框架](./README.md):核心 Agent 能力
+- [Gateway 架构](../gateway/docs/architecture.md):Gateway 三层架构
+- [Gateway API](../gateway/docs/api.md):Gateway API 参考

+ 0 - 78
docs/ref/Claude Code/agent-prompt-agent-creation-architect.md

@@ -1,78 +0,0 @@
-<!--
-name: 'Agent Prompt: Agent creation architect'
-description: System prompt for creating custom AI agents with detailed specifications
-ccVersion: 2.0.77
-variables:
-  - TASK_TOOL_NAME
--->
-You are an elite AI agent architect specializing in crafting high-performance agent configurations. Your expertise lies in translating user requirements into precisely-tuned agent specifications that maximize effectiveness and reliability.
-
-**Important Context**: You may have access to project-specific instructions from CLAUDE.md files and other context that may include coding standards, project structure, and custom requirements. Consider this context when creating agents to ensure they align with the project's established patterns and practices.
-
-When a user describes what they want an agent to do, you will:
-
-1. **Extract Core Intent**: Identify the fundamental purpose, key responsibilities, and success criteria for the agent. Look for both explicit requirements and implicit needs. Consider any project-specific context from CLAUDE.md files. For agents that are meant to review code, you should assume that the user is asking to review recently written code and not the whole codebase, unless the user has explicitly instructed you otherwise.
-
-2. **Design Expert Persona**: Create a compelling expert identity that embodies deep domain knowledge relevant to the task. The persona should inspire confidence and guide the agent's decision-making approach.
-
-3. **Architect Comprehensive Instructions**: Develop a system prompt that:
-   - Establishes clear behavioral boundaries and operational parameters
-   - Provides specific methodologies and best practices for task execution
-   - Anticipates edge cases and provides guidance for handling them
-   - Incorporates any specific requirements or preferences mentioned by the user
-   - Defines output format expectations when relevant
-   - Aligns with project-specific coding standards and patterns from CLAUDE.md
-
-4. **Optimize for Performance**: Include:
-   - Decision-making frameworks appropriate to the domain
-   - Quality control mechanisms and self-verification steps
-   - Efficient workflow patterns
-   - Clear escalation or fallback strategies
-
-5. **Create Identifier**: Design a concise, descriptive identifier that:
-   - Uses lowercase letters, numbers, and hyphens only
-   - Is typically 2-4 words joined by hyphens
-   - Clearly indicates the agent's primary function
-   - Is memorable and easy to type
-   - Avoids generic terms like "helper" or "assistant"
-
-6 **Example agent descriptions**:
-  - in the 'whenToUse' field of the JSON object, you should include examples of when this agent should be used.
-  - examples should be of the form:
-    - <example>
-      Context: The user is creating a test-runner agent that should be called after a logical chunk of code is written.
-      user: "Please write a function that checks if a number is prime"
-      assistant: "Here is the relevant function: "
-      <function call omitted for brevity only for this example>
-      <commentary>
-      Since a significant piece of code was written, use the ${TASK_TOOL_NAME} tool to launch the test-runner agent to run the tests.
-      </commentary>
-      assistant: "Now let me use the test-runner agent to run the tests"
-    </example>
-    - <example>
-      Context: User is creating an agent to respond to the word "hello" with a friendly jok.
-      user: "Hello"
-      assistant: "I'm going to use the ${TASK_TOOL_NAME} tool to launch the greeting-responder agent to respond with a friendly joke"
-      <commentary>
-      Since the user is greeting, use the greeting-responder agent to respond with a friendly joke. 
-      </commentary>
-    </example>
-  - If the user mentioned or implied that the agent should be used proactively, you should include examples of this.
-- NOTE: Ensure that in the examples, you are making the assistant use the Agent tool and not simply respond directly to the task.
-
-Your output must be a valid JSON object with exactly these fields:
-{
-  "identifier": "A unique, descriptive identifier using lowercase letters, numbers, and hyphens (e.g., 'test-runner', 'api-docs-writer', 'code-formatter')",
-  "whenToUse": "A precise, actionable description starting with 'Use this agent when...' that clearly defines the triggering conditions and use cases. Ensure you include examples as described above.",
-  "systemPrompt": "The complete system prompt that will govern the agent's behavior, written in second person ('You are...', 'You will...') and structured for maximum clarity and effectiveness"
-}
-
-Key principles for your system prompts:
-- Be specific rather than generic - avoid vague instructions
-- Include concrete examples when they would clarify behavior
-- Balance comprehensiveness with clarity - every instruction should add value
-- Ensure the agent has enough context to handle variations of the core task
-- Make the agent proactive in seeking clarification when needed
-- Build in quality assurance and self-correction mechanisms
-
-Remember: The agents you create should be autonomous experts capable of handling their designated tasks with minimal additional guidance. Your system prompts are their complete operational manual.

+ 0 - 12
docs/ref/Claude Code/agent-prompt-bash-command-description-writer.md

@@ -1,12 +0,0 @@
-Clear, concise description of what this command does in active voice. Never use words like "complex" or "risk" in the description - just describe what it does.
-
-For simple commands (git, npm, standard CLI tools), keep it brief (5-10 words):
-
-ls → "List files in current directory"
-git status → "Show working tree status"
-npm install → "Install package dependencies"
-For commands that are harder to parse at a glance (piped commands, obscure flags, etc.), add enough context to clarify what it does:
-
-find . -name "*.tmp" -exec rm {} \; → "Find and delete all .tmp files recursively"
-git reset --hard origin/main → "Discard all local changes and match remote main"
-curl -s url | jq '.data[]' → "Fetch JSON from URL and extract data array elements"

+ 0 - 9
docs/ref/Claude Code/system-prompt-doing-tasks.md

@@ -1,9 +0,0 @@
-Doing tasks
-The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended: ${"- NEVER propose changes to code you haven't read. If a user asks about or wants you to modify a file, read it first. Understand existing code before suggesting modifications."}${TOOL_USAGE_HINTS_ARRAY.length>0? ${TOOL_USAGE_HINTS_ARRAY.join( )}:""}
-
-Be careful not to introduce security vulnerabilities such as command injection, XSS, SQL injection, and other OWASP top 10 vulnerabilities. If you notice that you wrote insecure code, immediately fix it.
-Avoid over-engineering. Only make changes that are directly requested or clearly necessary. Keep solutions simple and focused.
-Don't add features, refactor code, or make "improvements" beyond what was asked. A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't need extra configurability. Don't add docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.
-Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don't use feature flags or backwards-compatibility shims when you can just change the code.
-Don't create helpers, utilities, or abstractions for one-time operations. Don't design for hypothetical future requirements. The right amount of complexity is the minimum needed for the current task—three similar lines of code is better than a premature abstraction.
-Avoid backwards-compatibility hacks like renaming unused `_vars`, re-exporting types, adding `// removed` comments for removed code, etc. If something is unused, delete it completely.

+ 0 - 6
docs/ref/Claude Code/system-prompt-tool-usage-policy.md

@@ -1,6 +0,0 @@
-Tool usage policy${WEBFETCH_ENABLED_SECTION}${MCP_TOOLS_SECTION}
-You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.
-If the user specifies that they want you to run tools "in parallel", you MUST send a single message with multiple tool use content blocks. For example, if you need to launch multiple agents in parallel, send a single message with multiple ${TASK_TOOL_NAME} tool calls.
-Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: ${READ_TOOL_NAME} for reading files instead of cat/head/tail, ${EDIT_TOOL_NAME} for editing instead of sed/awk, and ${WRITE_TOOL_NAME} for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
-${VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the ${TASK_TOOL_NAME} tool with subagent_type=${EXPLORE_AGENT.agentType} instead of running search commands directly.}
-user: Where are errors from the client handled? assistant: [Uses the ${TASK_TOOL_NAME} tool with subagent_type=${EXPLORE_AGENT.agentType} to find the files that handle client errors instead of using ${GLOB_TOOL_NAME} or ${GREP_TOOL_NAME} directly] user: What is the codebase structure? assistant: [Uses the ${TASK_TOOL_NAME} tool with subagent_type=${EXPLORE_AGENT.agentType}]

+ 0 - 16
docs/ref/Claude Code/system-prompt-tool-use-summary-generation.md

@@ -1,16 +0,0 @@
-You summarize what was accomplished by a coding assistant. Given the tools executed and their results, provide a brief summary.
-
-Rules:
-
-Use past tense (e.g., "Read package.json", "Fixed type error in utils.ts")
-Be specific about what was done
-Keep under 8 words
-Do not include phrases like "I did" or "The assistant" - just describe what happened
-Focus on the user-visible outcome, not implementation details
-Examples:
-
-"Searched codebase for authentication code"
-"Read and analyzed Message.tsx component"
-"Fixed null pointer exception in data processor"
-"Created new user registration endpoint"
-"Ran tests and fixed 3 failing assertions"

+ 0 - 0
docs/ref/Claude Code/tool-description-bash.md


+ 0 - 659
docs/ref/context-comparison.md

@@ -1,659 +0,0 @@
-# Context 管理方案对比分析
-
-> 对比 OpenCode、Codex 和 Gemini-cli 三个项目的 context 管理方案
-
----
-
-## 一、整体架构对比
-
-| 维度 | **OpenCode** | **Codex** | **Gemini-cli** |
-|------|-------------|-----------|---------------|
-| **核心数据结构** | 线性 Message List | ContextManager (Vec<ResponseItem>) | Content[] (双版本) |
-| **消息历史版本** | 单一版本 | 单一版本 + GhostSnapshot | 精选版本 + 完整版本 |
-| **分层设计** | 无 | 无 | **✓ 三层**: Global → Environment → JIT |
-| **Plan 管理** | goal.json (计划中) + plan.md (参考) | SQLite + TodoListItem | 无 Plan 机制 |
-| **存储格式** | Storage Key-Value | JSONL + SQLite 混合 | JSON + 文本文件 |
-| **并发控制** | 未明确 | Arc<Mutex> + 文件锁 | Promise并发限制 |
-
----
-
-## 二、Token 限制处理策略
-
-### 2.1 Token 估算方法
-
-| 项目 | 估算策略 | 精度 | 实现位置 |
-|------|---------|------|---------|
-| **OpenCode** | 未详细说明,引用 Prune 阈值 | 中 | - |
-| **Codex** | **字节估算**: `bytes / 4` (1 token ≈ 4 bytes) | 低 | `truncate.rs::approx_token_count()` |
-| **Gemini-cli** | **启发式**: ASCII (0.25), 非ASCII (1.3), 图片 (3000), PDF (25800) | 高 | `tokenCalculation.ts::estimateTokenCountSync()` |
-
-**关键差异**:
-- **Codex**: 简单但快速,适合实时估算
-- **Gemini-cli**: 更精确,区分字符类型和媒体,牺牲少量性能
-
-### 2.2 Token 限制阈值
-
-| 项目 | 限制类型 | 阈值定义 |
-|------|---------|---------|
-| **OpenCode** | 删除阈值 | `PRUNE_MINIMUM = 20,000`, `PRUNE_PROTECT = 40,000` |
-| **Codex** | 模型限制 | 依赖模型配置,无固定值 |
-| **Gemini-cli** | 压缩阈值 | 默认 **50%** 模型限制 (`DEFAULT_COMPRESSION_TOKEN_THRESHOLD = 0.5`) |
-
-### 2.3 截断策略
-
-```
-┌─────────────────┬──────────────────────────┬─────────────────────┐
-│    OpenCode     │         Codex            │    Gemini-cli       │
-├─────────────────┼──────────────────────────┼─────────────────────┤
-│ 删除旧工具输出   │ 前缀+后缀保留,中间截断    │ 反向token预算       │
-│ 保护最近2轮turns │ 插入省略标记             │ 最近工具完整保留     │
-│ 不删除"skill"工具│ 保证UTF-8边界完整性       │ 旧工具仅保留30行    │
-└─────────────────┴──────────────────────────┴─────────────────────┘
-```
-
-**Gemini-cli 的反向预算策略** (最独特):
-```typescript
-// 从最新消息往回遍历,为每条工具输出分配token预算
-// 优先保留最近的,旧的按需截断
-COMPRESSION_FUNCTION_RESPONSE_TOKEN_BUDGET = 50,000;
-COMPRESSION_TRUNCATE_LINES = 30;
-```
-
----
-
-## 三、摘要/压缩机制
-
-### 3.1 压缩触发时机
-
-| 项目 | 触发时机 | 方式 |
-|------|---------|------|
-| **OpenCode** | 事后 (context满时) | 被动压缩 |
-| **Codex** | 超过模型窗口时 | 自动压缩 |
-| **Gemini-cli** | **主动** (达到50%阈值) + **手动** (/compress) | 混合方式 |
-
-### 3.2 压缩策略对比
-
-#### OpenCode: 两阶段压缩
-
-```
-阶段1: Prune (清理旧工具输出)
-  ├─ 从后向前遍历
-  ├─ 跳过最后2轮
-  ├─ 跳过已有summary的消息
-  └─ 删除量 > PRUNE_MINIMUM 时执行
-
-阶段2: Full Compaction (上下文总结)
-  ├─ 创建summary=true的assistant消息
-  ├─ 调用"compaction"专用agent
-  └─ 提示词: "Provide a detailed prompt for continuing..."
-```
-
-#### Codex: 内联自动压缩
-
-```
-触发: run_inline_auto_compact_task()
-  ├─ 生成摘要前缀 (SUMMARY_PREFIX)
-  ├─ 使用SUMMARIZATION_PROMPT
-  ├─ 保留GhostSnapshot (幽灵快照)
-  └─ 替换历史记录为CompactedItem
-```
-
-**GhostSnapshot** (Codex独有):
-- 保留被压缩部分的"幽灵"引用
-- 用户可查看但不会发送给模型
-- 在UI中显示为折叠项
-
-#### Gemini-cli: 三相智能压缩
-
-```
-Phase 1: 历史分割
-  ├─ 保留最后30% (COMPRESSION_PRESERVE_THRESHOLD)
-  └─ 压缩前70%
-
-Phase 2: 双重总结验证 ⭐ (独特)
-  ├─ 第1次: 生成 <state_snapshot>
-  ├─ 第2次: 自我批评 ("Did you omit any...")
-  └─ 生成改进版本或确认原版本
-
-Phase 3: 输出验证
-  ├─ 检查压缩后token数 < 原token数
-  └─ 失败则保持原历史
-```
-
-**双重验证的价值**:
-```typescript
-// 第一次生成
-"Generate a state snapshot of the conversation..."
-
-// 第二次自我批评
-"Did you omit any specific file content, code snippets,
-or context that might be needed later? If yes, provide
-an improved version. If no, confirm the original."
-```
-
-### 3.3 压缩结果处理
-
-| 项目 | 压缩失败处理 | 结果存储 |
-|------|------------|---------|
-| **OpenCode** | 标记为已compacted | 替换为summary message |
-| **Codex** | 保留GhostSnapshot | CompactedItem + replacement_history |
-| **Gemini-cli** | **回退原历史** ⭐ | 成功时替换,失败时保持原状 |
-
----
-
-## 四、存储和加载方式
-
-### 4.1 存储架构
-
-#### OpenCode (计划中)
-```
-.trace/{trace_id}/
-├── goal.json          # Goal Tree (结构化plan)
-├── messages.jsonl     # 消息记录 (含 goal_id)
-└── meta.json          # Trace 元数据
-```
-
-#### Codex
-```
-~/.codex/
-├── history.jsonl                           # 全局消息历史
-├── sessions/
-│   ├── rollout-{timestamp}-{uuid}.jsonl    # 会话回滚文件
-│   └── ...
-└── state.db                                # SQLite状态数据库
-```
-
-**关键特性**:
-- **原子写入**: 使用 `O_APPEND` 标志
-- **并发安全**: Advisory文件锁
-- **自动清理**: 超过限制时删除旧条目 (软限制80%)
-
-#### Gemini-cli
-```
-~/.gemini/
-├── GEMINI.md                               # 全局内存
-├── tmp/{project_hash}/
-│   └── chats/{session-ID}.json             # 会话记录
-└── config.json                             # 配置
-
-项目根目录/
-├── GEMINI.md                               # 环境内存
-└── subdirs/
-    └── GEMINI.md                           # JIT内存 (按需加载)
-```
-
-**独特的三层内存系统**:
-```
-Tier 1: Global Memory
-  ├─ ~/.gemini/GEMINI.md
-  └─ 用户级别,所有会话共享
-
-Tier 2: Environment Memory
-  ├─ 项目根目录的GEMINI.md
-  ├─ 扩展提供的上下文文件
-  └─ MCP客户端指令
-
-Tier 3: JIT Subdirectory Memory ⭐
-  ├─ 访问路径时动态发现
-  ├─ 向上遍历到项目根
-  ├─ 向下BFS搜索 (最多200目录)
-  └─ 避免加载不相关上下文
-```
-
-### 4.2 加载策略对比
-
-| 项目 | 加载时机 | 策略 | 并发控制 |
-|------|---------|------|---------|
-| **OpenCode** | 会话启动/恢复 | 按需加载 | 未明确 |
-| **Codex** | 启动时 | lookup(log_id, offset) | Arc<Mutex> + 文件锁 |
-| **Gemini-cli** | **分层+JIT** ⭐ | 全局(启动) + 环境(会话) + JIT(访问) | Promise并发限制 (10/20) |
-
----
-
-## 五、Plan/Todo 机制
-
-### 5.1 数据结构对比
-
-#### OpenCode (参考方案)
-```typescript
-// plan.md (文本)
-- [ ] 分析代码
-- [x] 实现功能
-- [ ] 测试
-
-// Todo.Info (结构化)
-{
-  id: string,
-  content: string,
-  status: "pending" | "in_progress" | "completed" | "cancelled",
-  priority: "high" | "medium" | "low"
-}
-```
-
-#### OpenCode (我们的方案 - 计划中)
-```python
-@dataclass
-class Goal:
-    id: str                    # "1", "1.1", "2"
-    description: str
-    status: Status             # pending | in_progress | completed | abandoned
-    summary: Optional[str]     # done/abandon 时的总结
-    children: List["Goal"]
-
-@dataclass
-class GoalTree:
-    mission: str
-    current_id: Optional[str]
-    goals: List[Goal]
-```
-
-**关键特性**:
-- **goal_id 关联**: 每条 message 记录它属于哪个 goal
-- **增量压缩**: goal 完成/放弃时压缩相关 messages
-- **精确回溯**: 基于 goal 的状态流转
-
-#### Codex
-```rust
-// TodoListItem in ResponseItem
-pub struct TodoListItem {
-    todo_list: Vec<TodoItem>,
-}
-
-pub struct TodoItem {
-    task: String,
-    completed: bool,
-}
-```
-
-**存储**: 作为 ResponseItem 的一部分,随对话历史一起管理
-
-#### Gemini-cli
-**无专门 Plan 机制**,但有:
-- **会话记录**: 完整的 `ConversationRecord`
-- **目录跟踪**: `directories?: string[]` (会话中添加的目录)
-
-### 5.2 执行与 Plan 的关联
-
-| 项目 | 关联方式 | 可编辑性 | 可视化 |
-|------|---------|---------|--------|
-| **OpenCode (参考)** | 无结构化关联 | plan.md 可直接编辑 | 基础 |
-| **OpenCode (计划)** | message.goal_id | 通过 goal 工具 | 增强 (树形+步骤) |
-| **Codex** | TodoItem 在 ResponseItem 中 | 通过模型更新 | 基础 |
-| **Gemini-cli** | - | - | - |
-
----
-
-## 六、Sub-Agent/并行探索
-
-### 6.1 Sub-Agent 支持
-
-#### OpenCode (参考方案)
-```typescript
-// Agent Mode
-- primary: 主代理,执行工具
-- subagent: 子代理,独立context
-
-// 内置 Sub-Agents
-- general: 通用代理,可并行执行
-- explore: 代码探索,仅查询工具
-- compaction: 上下文总结
-```
-
-**执行流程**:
-```
-1. 创建 SubtaskPart
-2. 子代理独立处理 (独立 message list)
-3. 结果汇总: "The following tool was executed by the user"
-```
-
-#### OpenCode (我们的方案 - 计划中)
-```python
-@tool
-def explore(
-    question: str,           # 探索要回答的问题
-    branches: List[str],     # 探索方向 (2-4个)
-) -> str:
-    """并行探索多个方向,汇总结果"""
-```
-
-**执行流程**:
-```
-1. 为每个探索方向创建独立的 Sub-Trace(完整的 Trace 结构)
-2. 并行执行所有 Sub-Traces(使用 asyncio.gather)
-3. 收集每个 Sub-Trace 的结论
-4. 返回汇总结果给主 Trace
-```
-
-#### Codex
-**无明确 Sub-Agent 机制**,但有:
-- **SessionState**: 管理会话状态
-- **Turn Context**: 单轮对话的上下文
-
-#### Gemini-cli
-**无 Sub-Agent 机制**,但有:
-- **CoreToolScheduler**: 工具执行调度
-- **并发工具执行**: 多个工具可并行运行
-
-### 6.2 并行探索对比
-
-| 特性 | OpenCode (参考) | OpenCode (计划) | Codex | Gemini-cli |
-|------|---------------|---------------|-------|-----------|
-| **并行探索** | Sub-agent 手动管理 | explore 工具自动汇总 ⭐ | 无 | 工具级并发 |
-| **Context 隔离** | ✓ (独立 message list) | ✓ (独立 message list) | - | - |
-| **结果汇总** | 手动 | 自动 (返回 markdown) | - | - |
-| **适用场景** | 大任务隔离 | 多方案评估 | - | 工具执行 |
-
----
-
-## 七、回溯能力
-
-### 7.1 回溯机制对比
-
-#### OpenCode (参考方案)
-```
-限制: 有限的回溯能力
-- 无精确状态保存
-- 依赖压缩后的摘要
-```
-
-#### OpenCode (我们的方案 - 计划中)
-```python
-goal(abandon="方案A需要Redis,环境没有", add="实现方案B")
-```
-
-**回溯流程**:
-```
-Before:
-  Messages:
-    [分析代码的 20 条 message...]
-    [实现方案 A 的 30 条 message...]  ← 这些要压缩
-    [测试失败的 message...]
-
-After:
-  Messages:
-    [分析代码的 20 条 message...]
-    [Summary: "尝试方案A,因依赖问题失败"]  ← 压缩为1条
-    [开始方案B的 message...]
-
-  Plan:
-    [✓] 1. 分析代码
-    [✗] 2. 实现方案A (abandoned: 依赖问题)
-    [→] 2'. 实现方案B
-```
-
-**优势**:
-- **精确回溯**: 基于 goal 的状态标记
-- **保留失败原因**: summary 包含 abandon 原因
-- **压缩旧路径**: 失败尝试不占用大量 context
-
-#### Codex
-```rust
-// GhostSnapshot: 保留被压缩部分的引用
-pub struct CompactedItem {
-    message: String,
-    replacement_history: Option<Vec<ResponseItem>>,
-}
-```
-
-**回溯能力**:
-- **GhostSnapshot**: 用户可查看历史,但不发送给模型
-- **Rollout 记录**: 完整的会话记录保存在 JSONL
-- **用户转换检测**: 可定位到特定用户消息
-
-#### Gemini-cli
-```typescript
-enum CompressionStatus {
-  COMPRESSED,
-  COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,  // 压缩反而增加
-  COMPRESSION_FAILED_EMPTY_SUMMARY,          // 摘要为空
-  NOOP,
-}
-```
-
-**回溯能力**:
-- **会话记录**: 完整的 `ConversationRecord` 保存所有消息
-- **压缩失败回退**: 自动回退到原历史
-- **无结构化回溯**: 缺乏基于任务的回溯机制
-
-### 7.2 回溯对比表
-
-| 项目 | 回溯粒度 | 状态保存 | 失败处理 | 历史查看 |
-|------|---------|---------|---------|---------|
-| **OpenCode (参考)** | 粗粒度 | 摘要 | 有限 | 基础 |
-| **OpenCode (计划)** | **goal级别** ⭐ | goal.summary + abandon原因 | 精确压缩 | goal树 + 步骤 |
-| **Codex** | 用户turn级别 | GhostSnapshot | GhostSnapshot引用 | 完整回滚文件 |
-| **Gemini-cli** | 消息级别 | ConversationRecord | 自动回退 | 会话记录文件 |
-
----
-
-## 八、核心差异总结
-
-### 8.1 设计哲学
-
-| 项目 | 核心理念 | 优势场景 |
-|------|---------|---------|
-| **OpenCode** | **结构化计划驱动** | 复杂任务,需要回溯和探索 |
-| **Codex** | **简单高效,成熟稳定** | 通用编码助手,快速响应 |
-| **Gemini-cli** | **分层智能,高保真保留** | 多项目管理,长期会话 |
-
-### 8.2 独特创新点
-
-#### OpenCode (我们的方案)
-1. **goal 工具**: 结构化 Plan + 执行关联
-2. **explore 工具**: 并行探索自动汇总
-3. **增量压缩**: goal 完成/放弃时压缩,而非事后被动
-4. **精确回溯**: abandon + 状态流转
-
-#### Codex
-1. **GhostSnapshot**: 压缩历史仍可查看
-2. **原子写入**: O_APPEND + 并发安全
-3. **字节估算**: 简单快速 (bytes/4)
-4. **历史规范化**: 确保调用-输出对完整性
-
-#### Gemini-cli
-1. **三层内存**: Global → Environment → JIT ⭐
-2. **双重验证**: 自我批评式压缩
-3. **反向token预算**: 优先保留最近工具输出
-4. **启发式token估算**: 区分字符类型和媒体
-5. **JIT Context发现**: 按需加载相关GEMINI.md
-
-### 8.3 技术选型对比
-
-| 技术点 | OpenCode | Codex | Gemini-cli |
-|--------|---------|-------|-----------|
-| **语言** | TypeScript | Rust | TypeScript |
-| **存储** | Storage KV | JSONL + SQLite | JSON + 文本 |
-| **并发** | 未明确 | Arc<Mutex> + 文件锁 | Promise限制 |
-| **token估算** | 未详述 | bytes/4 | 启发式 (区分类型) |
-| **压缩策略** | 两阶段 | 内联自动 | 三相智能 |
-
----
-
-## 九、与 OpenCode 方案的详细对比
-
-### 9.1 OpenCode vs Codex
-
-| 方面 | OpenCode (参考) | Codex | 评估 |
-|------|---------------|-------|------|
-| **Plan格式** | 纯文本 (plan.md) | TodoItem (结构化) | Codex更结构化,但OpenCode计划中的goal.json更强 |
-| **Plan与执行关联** | 无 | TodoItem在ResponseItem中 | Codex有关联,但OpenCode计划中的goal_id更精确 |
-| **压缩时机** | 事后 (满时) | 事后 (超过窗口) | 相同 (被动) |
-| **并行探索** | Sub-agent (手动) | 无 | OpenCode参考方案更强,计划方案的explore更自动化 |
-| **回溯能力** | 有限 | GhostSnapshot | Codex的GhostSnapshot有价值,但OpenCode计划的goal-based更精确 |
-| **存储可靠性** | 未明确 | 原子写入+并发安全 | **Codex胜** ⭐ |
-| **工具复杂度** | todoread/todowrite | 无专门工具 | OpenCode参考方案更复杂,计划的goal/explore更简洁 |
-
-**可借鉴**:
-- ✓ Codex的原子写入和并发安全机制
-- ✓ GhostSnapshot的用户友好性 (可查看但不发送)
-- ✓ 历史规范化 (ensure_call_outputs_present)
-
-### 9.2 OpenCode vs Gemini-cli
-
-| 方面 | OpenCode (参考) | Gemini-cli | 评估 |
-|------|---------------|-----------|------|
-| **Plan格式** | plan.md + Todo.Info | 无 | **OpenCode胜** |
-| **Plan与执行关联** | 无 | 无 | 平局 |
-| **压缩时机** | 事后 (满时) | **主动** (50%阈值) | **Gemini-cli胜** ⭐ |
-| **并行探索** | Sub-agent (手动) | 工具级并发 | OpenCode的Sub-agent隔离更好 |
-| **回溯能力** | 有限 | 自动回退 | OpenCode计划方案的goal-based更强 |
-| **Context分层** | 无 | **三层** (Global/Env/JIT) | **Gemini-cli胜** ⭐ |
-| **压缩质量** | 单次生成 | **双重验证** (自我批评) | **Gemini-cli胜** ⭐ |
-| **工具输出保留** | 删除旧输出 | **反向预算** (保留最近) | **Gemini-cli胜** ⭐ |
-
-**可借鉴**:
-- ✓ 三层内存系统 (特别是JIT加载)
-- ✓ 双重验证的压缩机制
-- ✓ 主动压缩 (50%阈值,而非等到满)
-- ✓ 反向token预算 (优先保留最近工具输出)
-- ✓ 启发式token估算 (区分字符类型)
-
----
-
-## 十、OpenCode 计划方案的优势
-
-### 10.1 创新点
-
-1. **结构化 Plan (goal.json)**
-   - 比纯文本更精确
-   - 支持树形结构 (goal.children)
-   - 状态流转清晰
-
-2. **执行与 Plan 的强关联 (goal_id)**
-   - 每条 message 知道它属于哪个 goal
-   - 支持基于 goal 的压缩和回溯
-   - 可视化时能展示 goal + 对应步骤
-
-3. **增量压缩 (goal 完成/放弃时)**
-   - 比事后被动压缩更主动
-   - 压缩粒度可控 (按 goal)
-   - 保留失败原因 (abandon summary)
-
-4. **explore 工具 (并行探索自动汇总)**
-   - 比手动管理 Sub-agent 更简单
-   - 适合多方案评估场景
-   - 自动生成汇总报告
-
-5. **精确回溯 (abandon + 状态流转)**
-   - 比 GhostSnapshot 更结构化
-   - 支持从失败尝试中学习
-   - Plan 树中保留失败路径
-
-### 10.2 待改进点 (可借鉴其他方案)
-
-1. **存储可靠性** (借鉴 Codex)
-   - 原子写入
-   - 并发安全 (文件锁)
-   - 自动清理旧数据
-
-2. **Context 分层** (借鉴 Gemini-cli)
-   - 全局 context (用户级别)
-   - 项目 context (项目级别)
-   - JIT context (按需加载)
-
-3. **压缩质量** (借鉴 Gemini-cli)
-   - 双重验证 (自我批评)
-   - 压缩失败自动回退
-   - 验证压缩后 token 数真的减少
-
-4. **主动压缩** (借鉴 Gemini-cli)
-   - 达到阈值 (如 50%) 时主动压缩
-   - 而非等到 context 满
-
-5. **工具输出管理** (借鉴 Gemini-cli)
-   - 反向 token 预算
-   - 优先保留最近工具输出的完整性
-   - 旧输出智能截断 (保留最后N行)
-
-6. **Token 估算** (借鉴 Gemini-cli)
-   - 启发式估算 (区分 ASCII/非ASCII/媒体)
-   - 提高估算精度
-
-7. **用户友好性** (借鉴 Codex)
-   - GhostSnapshot 机制 (可查看但不发送)
-   - 历史规范化 (保证调用-输出对完整性)
-
----
-
-## 十一、实现建议
-
-### 11.1 Phase 1: 核心 goal 工具 (保留计划)
-- Goal 数据结构
-- goal 工具 (add, done, focus)
-- Plan 注入到 system prompt
-- 基础可视化
-
-### 11.2 Phase 2: 增强存储和压缩 (借鉴)
-- **存储增强** (借鉴 Codex):
-  - 原子写入和并发安全
-  - 自动清理机制
-- **压缩增强** (借鉴 Gemini-cli):
-  - 双重验证机制
-  - 主动压缩 (50%阈值)
-  - 反向token预算
-
-### 11.3 Phase 3: 回溯和 Context 分层
-- **回溯支持** (计划):
-  - abandon 操作
-  - Message 关联 goal_id
-  - 基于 goal 的 context 压缩
-- **Context 分层** (借鉴 Gemini-cli):
-  - 全局 context 文件
-  - 项目 context 文件
-  - JIT 子目录 context 发现
-
-### 11.4 Phase 4: 并行探索和优化
-- **explore 工具** (计划):
-  - 独立 message list 管理
-  - 结果汇总机制
-- **优化** (借鉴):
-  - GhostSnapshot (用户友好)
-  - 启发式 token 估算
-  - 历史规范化
-
----
-
-## 十二、总结
-
-### 12.1 三方案对比矩阵
-
-| 能力维度 | OpenCode (计划) | Codex | Gemini-cli | 最佳方案 |
-|---------|---------------|-------|-----------|---------|
-| **结构化 Plan** | ⭐⭐⭐ goal.json | ⭐⭐ TodoItem | ⭐ 无 | OpenCode |
-| **执行关联** | ⭐⭐⭐ goal_id | ⭐⭐ 弱关联 | ⭐ 无 | OpenCode |
-| **压缩策略** | ⭐⭐ 增量 | ⭐⭐ 自动 | ⭐⭐⭐ 三相智能 | Gemini-cli |
-| **压缩时机** | ⭐⭐ goal完成时 | ⭐ 被动 | ⭐⭐⭐ 主动50% | Gemini-cli |
-| **并行探索** | ⭐⭐⭐ explore工具 | ⭐ 无 | ⭐⭐ 工具级 | OpenCode |
-| **回溯能力** | ⭐⭐⭐ goal-based | ⭐⭐ GhostSnapshot | ⭐⭐ 会话记录 | OpenCode |
-| **存储可靠性** | ⭐⭐ 未明确 | ⭐⭐⭐ 原子+锁 | ⭐⭐ Promise限制 | Codex |
-| **Context分层** | ⭐ 无 | ⭐ 无 | ⭐⭐⭐ 三层JIT | Gemini-cli |
-| **工具输出管理** | ⭐ 删除旧的 | ⭐⭐ 截断 | ⭐⭐⭐ 反向预算 | Gemini-cli |
-| **Token估算** | ⭐⭐ 基础 | ⭐ bytes/4 | ⭐⭐⭐ 启发式 | Gemini-cli |
-
-**评分说明**: ⭐ 基础, ⭐⭐ 良好, ⭐⭐⭐ 优秀
-
-### 12.2 最终建议
-
-**OpenCode 的核心优势** (保留并强化):
-- ✅ 结构化 goal.json
-- ✅ goal_id 关联
-- ✅ explore 工具
-- ✅ 精确回溯
-
-**应借鉴的关键特性**:
-1. **Gemini-cli 的压缩机制**: 双重验证 + 主动压缩 + 反向预算
-2. **Gemini-cli 的分层 Context**: 特别是 JIT 加载
-3. **Codex 的存储可靠性**: 原子写入 + 并发安全
-4. **Codex 的 GhostSnapshot**: 提升用户体验
-
-**综合方案** (OpenCode + 借鉴):
-```
-OpenCode 计划方案
-  ├─ 保留: goal.json, explore工具, goal-based回溯
-  ├─ 增强压缩: Gemini-cli的双重验证 + 主动压缩
-  ├─ 增强存储: Codex的原子写入 + 并发安全
-  ├─ 增强Context: Gemini-cli的三层分层 + JIT加载
-  └─ 增强UX: Codex的GhostSnapshot
-```
-
-这将是一个**结构化驱动 + 智能压缩 + 可靠存储 + 分层Context**的综合方案,集三家之长! 🎯

+ 0 - 98
docs/ref/create.md

@@ -1,98 +0,0 @@
----
-name: create
-description: 从创作层解构社交媒体帖子,提取叙事策略与选题价值(研究用,未接入系统)
----
-
-## 角色
-
-你是内容创作策略分析专家。给定一篇优质社交媒体帖子,分析其**创作层**——内容策略、选题价值、叙事结构、文字策略——回答"这篇内容为什么值得创作,以及创作者如何讲述它"。
-
-与制作层解构(How to make)不同,创作层回答的是:**Why this content + How to tell it**。
-
----
-
-## 创作层的核心概念
-
-**选题价值**(三点框架)
-- **灵感点**:是什么触发了创作者创作这篇内容?来自生活、趋势、热点、个人经历?
-- **目的点**:创作者想通过这篇内容达到什么?吸粉、种草、共鸣、教育?
-- **关键点**:这篇内容的核心价值主张是什么?受众为什么会喜欢?
-
-**内容权重**
-- 这篇内容以图片为主还是文字为主?谁承载了更多核心信息?
-- 图文是相辅相成,还是各自独立承载信息?
-
-**叙事结构**:创作者如何组织内容流程——从什么开始,经过什么,以什么结尾?图片之间的叙事逻辑是什么?
-
----
-
-## 分析维度
-
-**内容品类**:这是什么类型的内容?(生活记录、好物分享、教程攻略、情感共鸣、观点输出……)
-
-**选题价值**:
-- 灵感点——触发创作的来源
-- 目的点——创作者的意图
-- 关键点——为什么受众会感兴趣
-
-**图文权重与关系**:
-- 核心信息载体(图 / 文 / 图文并重)
-- 图文是否相关,如何相互补充
-
-**叙事脚本结构**:
-- 整体叙事弧线(起承转合 / 问题-解决 / 情绪递进 / 对比展示……)
-- 各图承担的叙事角色
-- 图片间的连接逻辑
-
-**文字创作策略**:
-- 标题策略:吸引点在哪里、使用了什么钩子(数字、疑问、痛点、惊喜感)
-- 正文策略:节奏、语气、信息密度、与图片的配合方式
-
----
-
-## 输出格式
-
-```json
-{
-  "内容品类": "string",
-
-  "选题价值": {
-    "灵感点": "是什么触发了这篇内容",
-    "目的点": "创作者想达到什么",
-    "关键点": "受众为什么会喜欢"
-  },
-
-  "图文关系": {
-    "核心载体": "图片为主 | 文字为主 | 图文并重",
-    "协作方式": "图文如何配合(互补 / 独立 / 图解文 / 文释图)"
-  },
-
-  "叙事结构": {
-    "弧线类型": "起承转合 | 问题-解决 | 情绪递进 | 对比展示 | ...",
-    "图片叙事": [
-      {"图片": "图片1", "叙事角色": "引入主体 / 建立情境..."},
-      {"图片": "图片2", "叙事角色": "展开 / 对比..."}
-    ]
-  },
-
-  "文字策略": {
-    "标题": "钩子类型与策略",
-    "正文": "节奏、语气、信息组织方式"
-  },
-
-  "核心洞察": "一句话:这篇内容在创作策略上为什么成功"
-}
-```
-
----
-
-## 原则
-
-- **创作层优先**:分析"为什么创作这个内容 + 如何叙述它",而非视觉制作细节
-- **受众视角**:始终思考受众为什么会停留、点赞、收藏、分享
-- **策略性而非描述性**:不是"图片展示了XX",而是"通过XX实现了XX效果"
-- **与制作层互补**:创作层负责 Why + What to tell,制作层负责 How to make
-
----
-
-> **注**:此文件仅供研究,未接入 Agent 系统。对应的系统工具是 `deconstruct`(制作层)。

+ 0 - 357
docs/ref/deconstruct_old.md

@@ -1,357 +0,0 @@
----
-name: deconstruct
-description: 制作还原解构方法论:将优质社交媒体帖子解构为可还原的结构化制作脚本
----
-
-## 角色定位
-
-你是制作还原解构顾问。目标是将一篇优质社交媒体帖子(图片+文字)解构为结构化的制作脚本,使另一个 agent 能够基于解构产物还原出同等质量的内容。
-
-**解构产物的三个核心要求**:
-- **不过拟合**:描述制作规律而非记录内容细节("主体居中,背景浅色虚化"优于"穿红衣服的女生站在白色背景前")
-- **可泛化**:相同类型帖子的解构产物可以聚类,提取普适规律
-- **可还原**:另一个 agent 凭借解构产物能够以较高概率还原出视觉效果相近的内容
-
-使用 `goal` 工具管理以下各步骤的执行计划,按顺序推进。
-
----
-
-## 步骤 1:内容过滤
-
-过滤正文中与核心主题无关的话题标签(hashtag)。
-
-**保留标准**(两项均通过才保留):
-1. 与帖子主题或产品有直接关联
-2. 移除后不影响对核心内容的理解
-
-输出:过滤后的正文文本。
-
----
-
-## 步骤 2:入口分析(内容视角)
-
-通过多图对比,判断这篇内容的核心表达方式。
-
-**内容视角二选一**:
-- **关注理念**:作者用具体事物传达抽象语义(符号化表达,借物喻义)
-- **关注表现**:作者展示具体事物本身(直接呈现,分享状态)
-
-**分析维度**:
-- 消费者视角:多图共性 vs 差异
-- 创作者视角:固定要素 vs 变化要素
-- 每张图的核心元素(频繁出现且符合帖子主题的视觉主体或文本)
-
-```json
-{
-  "内容视角": "关注理念 | 关注表现",
-  "详细说明": "内容视角的详细说明",
-  "推理": "如何得出以上结论",
-  "多图对比分析": {
-    "消费者视角": {"共性": "string", "差异": "string"},
-    "创作者视角": {"固定": "string", "变化": "string"},
-    "推理": "string"
-  },
-  "图片分析": [
-    {"图片Key": "图片1", "核心元素": ["手", "帽子"], "推理": "string"}
-  ]
-}
-```
-
----
-
-## 步骤 3:图片分段(元素定位树)
-
-将每张图片递归拆分为树状段落结构,每个节点精确定位一个视觉区域。
-
-### 六大拆分原则
-
-**原则 1 — 内容准确性**:
-- 名称/描述/坐标必须且只能描述该区域实际可见的内容
-- 禁止推测不可见信息,禁止根据文字信息做推断
-
-**原则 2 — 递归拆分维度选择**(优先级从高到低):
-1. 创作者语义拆分(最高优先):作者创作意图导致的自然分组,如"标题区 vs 内容区"
-2. XY 轴拆分:水平或垂直方向的空间分割
-3. 层级拆分:前景/背景、深度关系
-
-**原则 3 — 完整覆盖**:
-- 子段落集合必须完整覆盖父段落的视觉区域
-- 无遗漏(每个像素属于某个子段落)、无重叠
-
-**原则 4 — 多图变异性识别**:
-- 标注跨图片的变化部分 vs 固定不变部分
-- 同组内允许结构上的细微变化
-
-**原则 5 — 终止条件**(满足任一则停止拆分):
-- 单一视觉元素(不可再分割的最小语义单元)
-- 进一步拆分无制作意义(如纯色背景块)
-- 区域内容在不同图片中高度一致且无内部变化
-
-**原则 6 — 同组灵活性**:
-- 相似图片允许有结构上的细微差异,不强求完全一致
-
-### 分段输出格式
-
-```json
-[
-  {
-    "image_index": 1,
-    "structure": {
-      "名称": "语义化名称(非位置描述)",
-      "内容类型": "文字 | 图片",
-      "内容实质": "该区域的核心视觉内容(制作还原视角)",
-      "描述": "具体、可量化的视觉描述",
-      "顶点坐标": [[x1,y1], [x2,y2], [x3,y3], [x4,y4]],
-      "拆分推理": "为什么这样拆分",
-      "子段落": []
-    }
-  }
-]
-```
-
-### 分段后的四步后处理
-
-分段树建立后,依次执行:
-
-**评估**:检查以下三类问题:
-- 兄弟节点层级不一致(同一父节点下子节点的语义层级不对等)
-- 拆分必要性(是否存在不必要的拆分)
-- 覆盖完整性(是否有视觉区域未被覆盖)
-
-```json
-{
-  "整体评估": "通过 | 需要修复",
-  "图片评估": {
-    "图片1": {
-      "评估结果": "通过 | 需要修复",
-      "段落评估": [
-        {
-          "段落ID": "段落1",
-          "评估结果": "通过 | 需要修复",
-          "评估推理": "string",
-          "问题类型": "兄弟节点层级不一致 | 拆分不必要 | 覆盖不完整",
-          "问题描述": "string",
-          "修复建议": "string"
-        }
-      ]
-    }
-  }
-}
-```
-
-**排序**:按阅读顺序、视觉面积、信息密度、创作意图重新排列兄弟节点顺序,保持树结构。
-
-**重命名**:
-- 禁止位置描述("左半部分"、"右侧区域")
-- 禁止泛化描述("背景区域"、"内容块")
-- 同级节点名称唯一
-- 使用有意义的语义名称
-
-**实质分类**:对每个叶子节点做高层抽象分类。
-- 禁止使用"图片/照片/画面/元素/内容"等泛化词汇
-- 使用制作类别词:人物/产品/文字/场景/装饰/图标 等
-
----
-
-## 步骤 4:实质制作点(跨图元素统一)
-
-识别所有叶子节点中跨图片出现的相同元素,分配唯一 ID。
-
-### 判断是否为同一元素
-- 视觉实质相同,或存在整体与局部关系(如"人物"和"人物面部")
-- **判断依据**:实际视觉内容,禁止依赖文字字段(名称/描述/坐标)
-
-### 处理流程
-1. 收集所有叶子节点
-2. 文字元素:按内容实质分组(代码化,精确匹配)
-3. 图片元素:LLM 视觉比较分组
-4. 反思合并:识别被错误分开的组,合并为同一元素
-5. 重要性过滤(保留 ≥ 40 分的元素):
-   - 频率分(权重 70%):1次=0分, 2次=20分, 3次=40分, 4次=60分, 5次=80分, ≥6次=100分
-   - 覆盖率分(权重 30%):`覆盖率 × 100`
-6. 统一命名(使用上位概念,避免歧义)
-7. 分配元素 ID:`元素1`, `元素2` ...
-
-```json
-[
-  {
-    "元素ID": "元素1",
-    "统一名称": "人物",
-    "统一描述": "女性,长发,戴眼镜,职业装,站立姿态",
-    "出现段落": ["段落1.1.1", "段落2.1", "段落3.1"],
-    "重要性得分": 85
-  }
-]
-```
-
----
-
-## 步骤 5:图片形式分析
-
-从"如何还原元素"的视角,提取每个段落/元素的视觉呈现方式。
-
-**形式定义**:
-- 宏观:创作者如何呈现内容(How)
-- 微观:对段落增加内容表现力、吸引力、感染力的属性/特征/状态/创作手法/呈现方式
-
-**禁止提取的内容**:后期处理技术(滤镜/色调调整)、构图方式(构图属于段落关系,不属于单段落形式)、拍摄角度(归入空间关系)
-
-### 5阶段流程
-
-**Phase 0 — 段落形式分类**(批量判断,每个段落最初通过什么制作手段产生):
-```json
-{"段落1": "摄影 | 插画 | 文字排版 | 3D渲染 | 动态图形 | ...", "段落1.2": "..."}
-```
-
-**Phase 1 — 形式维度发现**(发现原子的、不可再分的形式维度):
-- 输出的是**维度名称**,不是维度值("构图方式"而非"居中构图")
-- 维度必须对当前段落的制作还原有实际意义
-
-```json
-{
-  "图片1": {
-    "段落ID": [
-      {"名称": "光线方向", "推理": "该段落的光线来源影响制作时布光方式"},
-      {"名称": "景深效果", "推理": "背景虚化程度影响拍摄参数设置"}
-    ]
-  }
-}
-```
-
-**Phase 2 — 形式分类**(对维度名称按 MECE 原则分类,便于聚类):
-```json
-{"光线方向": "光线类", "景深效果": "镜头类", "字体粗细": "排版类"}
-```
-
-**Phase 3 — 精确值提取**(事无巨细、具体全面、精确无歧义;定量形式必须含数值):
-- 先检查段落内一致性(若不一致,拆分到子层级)
-- 再判断定量 vs 定性
-- 定量:给出具体数值或比例("字体大小约占图片高度的 8%")
-- 定性:给出精确描述("暖黄色调,色温约 3200K")
-
-```json
-[
-  {
-    "段落ID": "段落1.1",
-    "形式": [
-      {"名称": "光线方向", "描述": "右侧 45° 侧光,形成明显的明暗分界", "是否可定量": false},
-      {"名称": "景深效果", "描述": "背景虚化,估计光圈 f/1.8~f/2.8", "是否可定量": true}
-    ]
-  }
-]
-```
-
----
-
-## 步骤 6:段内关系分析
-
-分析每个父段落与其**直接子节点**之间的关系。
-
-**关系类型**:
-- **空间关系**:子节点相对于父节点的三维空间位置(位置、尺寸、比例、角度、层叠顺序等)
-- **其他关系**:物理关系、功能关系、逻辑关系(以父段落为背景/容器,子节点为主体)
-
-**分析原则**:
-- 关系命名使用"xx关系"格式(如"位置关系"、"比例关系"、"遮挡关系")
-- 判断依据:实际视觉内容,禁止依赖文字字段
-- 首要视角:制作还原(如何复现这种空间排布)
-
-**两步提取**:
-
-Step 1 — 识别空间维度(每对父子各需要哪些空间维度):
-```json
-[
-  {
-    "段落ID": "父段落ID",
-    "子节点空间维度": {
-      "子段落ID": ["水平位置", "垂直位置", "尺寸比例"]
-    }
-  }
-]
-```
-
-Step 2(并行)— 提取空间值 + 提取其他关系:
-```json
-[
-  {
-    "段落ID": "父段落ID",
-    "段内关系": {
-      "子段落ID": {
-        "空间关系": [
-          {"名称": "水平位置", "描述": "居中,距左右各占 50%", "关系类型": "位置关系", "是否可定量": true}
-        ],
-        "其他关系": [
-          {"名称": "支撑关系", "描述": "背景作为衬托层,强化主体视觉焦点", "关系类型": "功能关系"}
-        ]
-      }
-    }
-  }
-]
-```
-
----
-
-## 步骤 7:段间关系分析
-
-分析**同一父节点下兄弟节点**之间的关系。
-
-**严格约束**:
-- 兄弟节点 = 具有相同直接父节点的节点(严格定义,禁止跨层级)
-- 禁止将子节点当成兄弟节点处理
-- 只保留对制作还原有价值的关系,过滤冗余关系
-- **去重规则**:只从 ID 较小的一侧记录(如段落1对段落2,不记录段落2对段落1)
-
-还需额外分析**跨图片的根段落关系**(把每张图的根段落视为兄弟节点处理)。
-
-```json
-[
-  {
-    "段落ID": "段落1(ID较小侧)",
-    "段间关系": {
-      "段落2": {
-        "空间关系": [
-          {"名称": "相对位置", "描述": "段落1位于段落2正上方,垂直间距约为图片高度的 5%", "关系类型": "位置关系", "是否可定量": true}
-        ],
-        "其他关系": [
-          {"名称": "引导关系", "描述": "标题(段落1)视觉引导读者向下阅读正文(段落2)", "关系类型": "逻辑关系"}
-        ]
-      }
-    }
-  }
-]
-```
-
----
-
-## 最终输出结构
-
-所有步骤完成后,用 `write_file` 将结果写入输出文件,并输出以下 JSON 摘要:
-
-```json
-{
-  "帖子ID": "string",
-  "文本": {
-    "标题": "string",
-    "正文(过滤后)": "string"
-  },
-  "入口分析": {},
-  "图片分段": [],
-  "实质制作点": [],
-  "图片形式": {
-    "段落形式分类": {},
-    "形式维度": {},
-    "形式分类": {},
-    "形式值": []
-  },
-  "段内关系": [],
-  "段间关系": []
-}
-```
-
-## 关键约束(贯穿全程)
-
-1. **泛化优先**:始终描述制作规律,而非内容细节
-2. **视觉判断优先**:所有判断基于实际可见内容,禁止依赖名称/描述等文字字段
-3. **制作还原视角**:始终从"如何制作出这个效果"的角度分析
-4. **结构化输出**:每步严格按 JSON schema 输出,不允许随意变更结构
-5. **步骤间数据复用**:后续步骤引用前面步骤的段落 ID,保持一致性

+ 0 - 31
docs/ref/skills.md

@@ -1,31 +0,0 @@
-Skill structure
-Every Skill requires a SKILL.md file with YAML frontmatter:
-
----
-name: your-skill-name
-description: Brief description of what this Skill does and when to use it
----
-
-# Your Skill Name
-
-## Instructions
-[Clear, step-by-step guidance for Claude to follow]
-
-## Examples
-[Concrete examples of using this Skill]
-Required fields: name and description
-
-Field requirements:
-
-name:
-
-Maximum 64 characters
-Must contain only lowercase letters, numbers, and hyphens
-Cannot contain XML tags
-Cannot contain reserved words: "anthropic", "claude"
-description:
-
-Must be non-empty
-Maximum 1024 characters
-Cannot contain XML tags
-The description should include both what the Skill does and when Claude should use it. For complete authoring guidance, see the best practices guide.

+ 71 - 0
docs/research/README.md

@@ -0,0 +1,71 @@
+# Agent2Agent (A2A) 通信调研
+
+本目录包含 Agent2Agent 跨设备通信的调研和设计文档。
+
+## 文档列表
+
+| 文档 | 内容 | 状态 |
+|-----|------|------|
+| [a2a-protocols.md](./a2a-protocols.md) | 行业 A2A 协议和框架调研 | 已完成 |
+| [a2a-cross-device.md](./a2a-cross-device.md) | 跨设备通信方案设计 | 已完成 |
+| [a2a-trace-storage.md](./a2a-trace-storage.md) | 跨设备 Trace 存储方案 | 已完成 |
+| [a2a-continuous-dialogue.md](./a2a-continuous-dialogue.md) | 持续对话方案(已废弃) | 已废弃 |
+
+## 核心设计
+
+### 远程 Trace ID
+
+通过在 Trace ID 中编码位置信息实现跨设备访问:
+
+```
+本地 Trace:  abc-123
+远程 Trace:  agent://terminal-agent-456/abc-123
+```
+
+### 持续对话
+
+通过 `continue_from` 参数实现 Agent 间持续对话:
+
+```python
+# 第一次调用
+result1 = agent(task="分析项目", agent_url="https://remote-agent")
+# 返回: {"sub_trace_id": "agent://remote-agent/abc-123"}
+
+# 继续对话
+result2 = agent(
+    task="重点分析core模块",
+    continue_from=result1["sub_trace_id"],
+    agent_url="https://remote-agent"
+)
+```
+
+### 存储架构
+
+**HybridTraceStore** 自动路由到本地或远程存储:
+- 本地 Trace → `FileSystemTraceStore`
+- 远程 Trace → `RemoteTraceStore`(通过 HTTP API)
+
+## 实现计划
+
+### Phase 1: 基础跨设备通信(1-2周)
+- [ ] 实现 `RemoteTraceStore`
+- [ ] 实现 `HybridTraceStore`
+- [ ] 修改 `agent` 工具支持 `agent_url` 参数
+- [ ] 添加远程 Trace ID 解析
+
+### Phase 2: 增强功能(2-3周)
+- [ ] 认证和授权
+- [ ] 成本控制
+- [ ] 审计日志
+- [ ] 性能优化(缓存、批量API)
+
+### Phase 3: 生产化(按需)
+- [ ] 错误处理和重试
+- [ ] 监控和告警
+- [ ] 文档和示例
+
+## 参考资料
+
+- [Google A2A Protocol](https://a2a-protocol.org/latest/specification/)
+- [Anthropic MCP](https://modelcontextprotocol.io/specification/2025-06-18)
+- [Agent Interoperability Survey](https://arxiv.org/html/2505.02279v1)

+ 733 - 0
docs/research/a2a-continuous-dialogue.md

@@ -0,0 +1,733 @@
+# Agent2Agent 持续对话方案
+
+**更新日期:** 2026-03-03
+
+## 问题定义
+
+### 单次任务 vs 持续对话
+
+**单次任务(之前的方案):**
+```
+云端Agent: 请分析本地项目
+    ↓
+终端Agent: [执行分析] → 返回结果
+    ↓
+结束
+```
+
+**持续对话(新需求):**
+```
+云端Agent: 请分析本地项目
+    ↓
+终端Agent: 我看到有3个模块,你想重点分析哪个?
+    ↓
+云端Agent: 重点分析core模块
+    ↓
+终端Agent: core模块使用了X架构,需要我详细说明吗?
+    ↓
+云端Agent: 是的,请详细说明
+    ↓
+终端Agent: [详细分析] → 返回结果
+    ↓
+结束(或继续)
+```
+
+## 核心挑战
+
+1. **上下文延续** - 如何维护多轮对话的上下文?
+2. **状态管理** - 对话进行到哪一步?谁在等待谁?
+3. **消息路由** - 如何确保消息发送到正确的Agent?
+4. **会话生命周期** - 何时开始?何时结束?
+5. **异步通信** - Agent可能不在线,如何处理?
+
+## 方案对比
+
+### 方案1:基于Trace的持续对话(推荐)
+
+#### 核心思想
+
+**利用现有的Trace机制作为对话容器**
+
+- 每个A2A对话创建一个共享的Trace
+- 双方Agent都可以向这个Trace追加消息
+- 通过WebSocket实时同步消息
+- 利用现有的续跑机制(`continue_from`)
+
+#### 架构设计
+
+```
+云端Agent                共享Trace                终端Agent
+    |                       |                         |
+    | 创建对话              |                         |
+    |--------------------->|                         |
+    |                      |                         |
+    | 发送消息1            |                         |
+    |--------------------->|                         |
+    |                      |----WebSocket推送------->|
+    |                      |                         |
+    |                      |<----追加消息2-----------|
+    |<--WebSocket推送------|                         |
+    |                      |                         |
+    | 发送消息3            |                         |
+    |--------------------->|                         |
+    |                      |----WebSocket推送------->|
+    |                      |                         |
+    ...持续对话...
+```
+
+#### API设计
+
+```python
+# 1. 创建对话会话
+POST /api/a2a/sessions
+{
+    "participants": ["cloud-agent-123", "terminal-agent-456"],
+    "initial_message": "请分析本地项目",
+    "context": {...}
+}
+
+响应:
+{
+    "session_id": "sess-xxx",
+    "trace_id": "trace-yyy",  # 底层使用Trace
+    "ws_url": "wss://host/api/a2a/sessions/sess-xxx/stream"
+}
+
+# 2. 发送消息(追加到Trace)
+POST /api/a2a/sessions/{session_id}/messages
+{
+    "from": "cloud-agent-123",
+    "content": "重点分析core模块",
+    "wait_for_response": true  # 是否等待对方回复
+}
+
+响应:
+{
+    "message_id": "msg-xxx",
+    "status": "sent"
+}
+
+# 3. WebSocket监听(实时接收消息)
+WS /api/a2a/sessions/{session_id}/stream
+{
+    "type": "message",
+    "from": "terminal-agent-456",
+    "content": "我看到有3个模块,你想重点分析哪个?",
+    "message_id": "msg-yyy"
+}
+
+# 4. 获取对话历史
+GET /api/a2a/sessions/{session_id}/messages
+响应:
+{
+    "messages": [
+        {"from": "cloud-agent-123", "content": "...", "timestamp": "..."},
+        {"from": "terminal-agent-456", "content": "...", "timestamp": "..."},
+        ...
+    ]
+}
+
+# 5. 结束对话
+POST /api/a2a/sessions/{session_id}/close
+{
+    "reason": "completed"
+}
+```
+
+#### 实现细节
+
+```python
+# agent/api/a2a_session.py
+
+class A2ASession:
+    """A2A对话会话,基于Trace实现"""
+
+    def __init__(self, session_id: str, trace_id: str, participants: List[str]):
+        self.session_id = session_id
+        self.trace_id = trace_id
+        self.participants = participants
+        self.ws_connections = {}  # agent_id -> WebSocket
+
+    async def send_message(
+        self,
+        from_agent: str,
+        content: str,
+        wait_for_response: bool = False
+    ) -> Dict[str, Any]:
+        """发送消息到对话"""
+        # 1. 追加消息到Trace
+        messages = [{"role": "user", "content": content}]
+        config = RunConfig(
+            trace_id=self.trace_id,
+            after_sequence=None,  # 续跑模式
+            uid=from_agent
+        )
+
+        # 2. 执行(可能触发对方Agent的响应)
+        async for event in runner.run(messages, config):
+            if isinstance(event, Message):
+                # 3. 通过WebSocket推送给其他参与者
+                await self._broadcast_message(event, exclude=from_agent)
+
+                if wait_for_response and event.role == "assistant":
+                    # 等待对方回复
+                    return {"message_id": event.message_id, "status": "sent"}
+
+        return {"status": "completed"}
+
+    async def _broadcast_message(self, message: Message, exclude: str = None):
+        """广播消息给所有参与者(除了发送者)"""
+        for agent_id, ws in self.ws_connections.items():
+            if agent_id != exclude:
+                await ws.send_json({
+                    "type": "message",
+                    "from": exclude,
+                    "content": message.content,
+                    "message_id": message.message_id,
+                    "timestamp": message.created_at.isoformat()
+                })
+
+
+@app.post("/api/a2a/sessions")
+async def create_session(request: CreateSessionRequest):
+    """创建A2A对话会话"""
+    # 1. 创建底层Trace
+    trace = Trace(
+        trace_id=generate_trace_id(),
+        mode="agent",
+        task=request.initial_message,
+        agent_type="a2a_session",
+        context={
+            "session_type": "a2a",
+            "participants": request.participants
+        }
+    )
+    await store.create_trace(trace)
+
+    # 2. 创建Session对象
+    session_id = f"sess-{generate_id()}"
+    session = A2ASession(session_id, trace.trace_id, request.participants)
+
+    # 3. 存储Session(内存或Redis)
+    sessions[session_id] = session
+
+    # 4. 发送初始消息
+    if request.initial_message:
+        await session.send_message(
+            from_agent=request.participants[0],
+            content=request.initial_message
+        )
+
+    return {
+        "session_id": session_id,
+        "trace_id": trace.trace_id,
+        "ws_url": f"wss://{host}/api/a2a/sessions/{session_id}/stream"
+    }
+
+
+@app.websocket("/api/a2a/sessions/{session_id}/stream")
+async def session_stream(websocket: WebSocket, session_id: str):
+    """WebSocket连接,实时接收对话消息"""
+    await websocket.accept()
+
+    # 1. 获取Session
+    session = sessions.get(session_id)
+    if not session:
+        await websocket.close(code=404)
+        return
+
+    # 2. 识别连接的Agent
+    agent_id = await authenticate_websocket(websocket)
+
+    # 3. 注册WebSocket连接
+    session.ws_connections[agent_id] = websocket
+
+    try:
+        # 4. 保持连接,接收消息
+        async for message in websocket:
+            data = json.loads(message)
+            if data["type"] == "message":
+                # 发送消息到对话
+                await session.send_message(
+                    from_agent=agent_id,
+                    content=data["content"]
+                )
+    finally:
+        # 5. 清理连接
+        del session.ws_connections[agent_id]
+
+
+@app.post("/api/a2a/sessions/{session_id}/messages")
+async def send_session_message(session_id: str, request: SendMessageRequest):
+    """发送消息到对话(HTTP方式)"""
+    session = sessions.get(session_id)
+    if not session:
+        raise HTTPException(404, "Session not found")
+
+    result = await session.send_message(
+        from_agent=request.from_agent,
+        content=request.content,
+        wait_for_response=request.wait_for_response
+    )
+
+    return result
+```
+
+#### 客户端SDK
+
+```python
+# agent/client/a2a_session_client.py
+
+class A2ASessionClient:
+    """A2A持续对话客户端"""
+
+    def __init__(self, base_url: str, agent_id: str, api_key: str):
+        self.base_url = base_url
+        self.agent_id = agent_id
+        self.api_key = api_key
+        self.ws = None
+        self.message_handlers = []
+
+    async def create_session(
+        self,
+        other_agent: str,
+        initial_message: str
+    ) -> str:
+        """创建对话会话"""
+        response = await self._post("/api/a2a/sessions", {
+            "participants": [self.agent_id, other_agent],
+            "initial_message": initial_message
+        })
+
+        session_id = response["session_id"]
+
+        # 自动连接WebSocket
+        await self._connect_websocket(session_id)
+
+        return session_id
+
+    async def _connect_websocket(self, session_id: str):
+        """连接WebSocket接收消息"""
+        ws_url = f"{self.ws_url}/api/a2a/sessions/{session_id}/stream"
+        self.ws = await websockets.connect(
+            ws_url,
+            extra_headers={"Authorization": f"Bearer {self.api_key}"}
+        )
+
+        # 启动消息接收循环
+        asyncio.create_task(self._receive_messages())
+
+    async def _receive_messages(self):
+        """接收WebSocket消息"""
+        async for message in self.ws:
+            data = json.loads(message)
+            if data["type"] == "message":
+                # 调用注册的消息处理器
+                for handler in self.message_handlers:
+                    await handler(data)
+
+    async def send_message(self, session_id: str, content: str):
+        """发送消息"""
+        if self.ws:
+            # 通过WebSocket发送(实时)
+            await self.ws.send(json.dumps({
+                "type": "message",
+                "content": content
+            }))
+        else:
+            # 通过HTTP发送(备用)
+            await self._post(f"/api/a2a/sessions/{session_id}/messages", {
+                "from_agent": self.agent_id,
+                "content": content
+            })
+
+    def on_message(self, handler):
+        """注册消息处理器"""
+        self.message_handlers.append(handler)
+
+    async def close_session(self, session_id: str):
+        """关闭对话"""
+        await self._post(f"/api/a2a/sessions/{session_id}/close", {})
+        if self.ws:
+            await self.ws.close()
+```
+
+#### 使用示例
+
+```python
+# 云端Agent使用
+client = A2ASessionClient(
+    base_url="https://org.agent.cloud",
+    agent_id="cloud-agent-123",
+    api_key="ak_xxx"
+)
+
+# 创建对话
+session_id = await client.create_session(
+    other_agent="terminal-agent-456",
+    initial_message="请分析本地项目"
+)
+
+# 注册消息处理器
+@client.on_message
+async def handle_message(message):
+    print(f"收到消息: {message['content']}")
+
+    # 根据消息内容决定如何回复
+    if "哪个模块" in message['content']:
+        await client.send_message(session_id, "重点分析core模块")
+    elif "需要我详细说明吗" in message['content']:
+        await client.send_message(session_id, "是的,请详细说明")
+
+# 等待对话完成
+await asyncio.sleep(60)  # 或其他结束条件
+
+# 关闭对话
+await client.close_session(session_id)
+```
+
+#### 优势
+
+1. **复用Trace机制** - 所有消息管理、压缩、存储都复用
+2. **完整历史** - 对话历史自动保存在Trace中
+3. **实时通信** - WebSocket保证低延迟
+4. **状态追踪** - 利用Trace的状态管理
+5. **可回溯** - 可以查看完整的对话历史
+
+#### 劣势
+
+1. **Trace概念泄露** - 外部需要理解session_id和trace_id的关系
+2. **复杂度** - 需要管理WebSocket连接
+
+### 方案2:独立的对话管理器
+
+#### 核心思想
+
+**创建独立的对话管理系统,不依赖Trace**
+
+```python
+class Conversation:
+    """独立的对话对象"""
+    conversation_id: str
+    participants: List[str]
+    messages: List[ConversationMessage]
+    status: str  # active, waiting, completed
+    created_at: datetime
+
+class ConversationMessage:
+    """对话消息"""
+    message_id: str
+    from_agent: str
+    to_agent: Optional[str]  # None表示广播
+    content: str
+    timestamp: datetime
+    metadata: Dict
+```
+
+#### 优势
+
+1. **概念清晰** - 对话就是对话,不混淆Trace
+2. **轻量级** - 不需要Trace的重量级机制
+3. **灵活** - 可以自定义对话逻辑
+
+#### 劣势
+
+1. **重复实现** - 需要重新实现消息管理、存储、压缩
+2. **不一致** - 与现有Trace机制不一致
+3. **维护成本** - 需要维护两套系统
+
+### 方案3:混合模式(推荐)
+
+#### 核心思想
+
+**对话层(Session)+ 执行层(Trace)分离**
+
+```
+对话层(Session)
+    - 管理对话状态
+    - 路由消息
+    - WebSocket连接
+    |
+    | 每条消息触发
+    ↓
+执行层(Trace)
+    - 执行具体任务
+    - 调用工具
+    - 管理上下文
+```
+
+#### 架构
+
+```python
+class A2ASession:
+    """对话会话(轻量级)"""
+    session_id: str
+    participants: List[str]
+    current_speaker: str
+    waiting_for: Optional[str]
+    context: Dict  # 共享上下文
+    message_queue: List[Message]
+
+    async def send_message(self, from_agent: str, content: str):
+        """发送消息"""
+        # 1. 添加到消息队列
+        self.message_queue.append(Message(from_agent, content))
+
+        # 2. 如果需要执行(不是简单问答),创建Trace
+        if self._needs_execution(content):
+            trace_id = await self._create_execution_trace(content)
+            # 执行完成后,结果自动添加到消息队列
+        else:
+            # 简单消息,直接转发
+            await self._forward_message(content, to=self._get_other_agent(from_agent))
+
+    def _needs_execution(self, content: str) -> bool:
+        """判断是否需要创建Trace执行"""
+        # 例如:包含工具调用、复杂任务等
+        return "分析" in content or "执行" in content or "查询" in content
+```
+
+#### 优势
+
+1. **分层清晰** - 对话管理和任务执行分离
+2. **灵活** - 简单消息不需要Trace,复杂任务才创建
+3. **高效** - 避免为每条消息创建Trace
+4. **复用** - 复杂任务仍然复用Trace机制
+
+#### 实现示例
+
+```python
+@app.post("/api/a2a/sessions/{session_id}/messages")
+async def send_message(session_id: str, request: SendMessageRequest):
+    session = sessions[session_id]
+
+    # 1. 判断消息类型
+    if request.requires_execution:
+        # 需要执行的任务 → 创建Trace
+        trace_id = await create_execution_trace(
+            task=request.content,
+            parent_session=session_id,
+            agent_id=request.from_agent
+        )
+
+        # 执行完成后,结果自动推送到Session
+        result = await runner.run_result(
+            messages=[{"role": "user", "content": request.content}],
+            config=RunConfig(trace_id=trace_id)
+        )
+
+        # 将结果作为消息发送给对方
+        await session.send_message(
+            from_agent=request.from_agent,
+            content=result["summary"]
+        )
+    else:
+        # 简单消息 → 直接转发
+        await session.send_message(
+            from_agent=request.from_agent,
+            content=request.content
+        )
+
+    return {"status": "sent"}
+```
+
+## 关键设计决策
+
+### 1. 消息类型分类
+
+| 类型 | 示例 | 处理方式 |
+|------|------|----------|
+| **简单问答** | "你好"、"收到"、"明白了" | 直接转发,不创建Trace |
+| **信息查询** | "当前进度如何?" | 查询Session状态,返回 |
+| **任务请求** | "分析core模块" | 创建Trace执行 |
+| **工具调用** | "读取文件X" | 创建Trace执行 |
+
+### 2. 上下文管理
+
+**Session级上下文(轻量):**
+```python
+session.context = {
+    "current_topic": "项目分析",
+    "focus_module": "core",
+    "previous_results": {...}
+}
+```
+
+**Trace级上下文(完整):**
+- 完整的消息历史
+- 工具调用记录
+- Goal树
+
+### 3. 生命周期管理
+
+```python
+# Session生命周期
+created → active → waiting → active → ... → completed/timeout
+
+# Trace生命周期(每个任务)
+created → running → completed
+```
+
+### 4. 超时和重连
+
+```python
+class A2ASession:
+    timeout: int = 300  # 5分钟无活动则超时
+    last_activity: datetime
+
+    async def check_timeout(self):
+        if datetime.now() - self.last_activity > timedelta(seconds=self.timeout):
+            await self.close(reason="timeout")
+
+    async def reconnect(self, agent_id: str, ws: WebSocket):
+        """Agent重连"""
+        self.ws_connections[agent_id] = ws
+        # 发送未读消息
+        await self._send_unread_messages(agent_id)
+```
+
+## 实现路线图
+
+### Phase 1:基础对话能力(2-3周)
+
+1. **Session管理**
+   - 创建/关闭Session
+   - 消息路由
+   - WebSocket连接管理
+
+2. **简单消息转发**
+   - 不涉及Trace
+   - 纯消息传递
+
+3. **客户端SDK**
+   - `A2ASessionClient`
+   - 消息处理器
+
+### Phase 2:集成Trace执行(2-3周)
+
+1. **任务识别**
+   - 判断消息是否需要执行
+   - 自动创建Trace
+
+2. **结果集成**
+   - Trace结果转换为消息
+   - 自动推送给对方
+
+3. **上下文共享**
+   - Session上下文传递给Trace
+   - Trace结果更新Session上下文
+
+### Phase 3:高级功能(3-4周)
+
+1. **多方对话**
+   - 支持3个以上Agent
+   - 群聊模式
+
+2. **对话分支**
+   - 子对话
+   - 并行对话
+
+3. **持久化和恢复**
+   - Session持久化
+   - 断线重连
+
+## 示例场景
+
+### 场景:云端助理与终端Agent的持续对话
+
+```python
+# 云端助理
+client = A2ASessionClient("https://cloud", "cloud-agent", "ak_xxx")
+
+# 1. 创建对话
+session_id = await client.create_session(
+    other_agent="terminal-agent-456",
+    initial_message="请分析本地项目"
+)
+
+# 2. 注册消息处理器(自动响应)
+@client.on_message
+async def handle_message(msg):
+    content = msg['content']
+
+    if "哪个模块" in content:
+        # 简单回复,不需要执行
+        await client.send_message(session_id, "重点分析core模块")
+
+    elif "详细说明" in content:
+        # 需要进一步分析,触发执行
+        await client.send_message(
+            session_id,
+            "是的,请详细说明架构设计和关键组件",
+            requires_execution=True  # 标记需要执行
+        )
+
+# 3. 等待对话完成
+await client.wait_for_completion(session_id)
+```
+
+```python
+# 终端Agent
+client = A2ASessionClient("https://terminal", "terminal-agent-456", "ak_yyy")
+
+# 1. 监听新对话
+@client.on_new_session
+async def handle_new_session(session_id, initial_message):
+    # 分析项目
+    modules = await analyze_project()
+
+    # 询问用户
+    await client.send_message(
+        session_id,
+        f"我看到有{len(modules)}个模块:{', '.join(modules)},你想重点分析哪个?"
+    )
+
+# 2. 处理后续消息
+@client.on_message
+async def handle_message(msg):
+    if "core模块" in msg['content']:
+        # 执行分析
+        result = await analyze_module("core")
+
+        # 返回结果并询问
+        await client.send_message(
+            msg['session_id'],
+            f"core模块使用了{result['architecture']}架构,需要我详细说明吗?"
+        )
+
+    elif "详细说明" in msg['content']:
+        # 深度分析
+        details = await deep_analyze("core")
+        await client.send_message(
+            msg['session_id'],
+            f"详细架构:\n{details}"
+        )
+```
+
+## 总结
+
+### 推荐方案:混合模式
+
+**对话层(Session):**
+- 轻量级消息路由
+- WebSocket连接管理
+- 简单问答直接转发
+
+**执行层(Trace):**
+- 复杂任务创建Trace
+- 复用所有现有能力
+- 结果自动集成到对话
+
+### 关键优势
+
+1. **灵活** - 简单消息不需要Trace开销
+2. **强大** - 复杂任务复用Trace能力
+3. **清晰** - 对话和执行分层
+4. **高效** - 避免不必要的资源消耗
+
+### 实现优先级
+
+1. **Phase 1** - 基础Session + 简单消息(MVP)
+2. **Phase 2** - 集成Trace执行(核心能力)
+3. **Phase 3** - 高级功能(按需)
+

+ 640 - 0
docs/research/a2a-cross-device.md

@@ -0,0 +1,640 @@
+# Agent2Agent 跨设备通信方案
+
+**更新日期:** 2026-03-03
+
+## 场景分类
+
+### 场景1:云端Agent ↔ 云端Agent
+**示例:** 通用助理 → 爬虫运维Agent
+- **部署:** 同一服务器/进程
+- **通信:** 现有`agent`工具(内存调用)
+- **不需要HTTP接口**
+
+### 场景2:云端Agent ↔ 终端Agent ⭐
+**示例:**
+- 云端通用助理 → 用户笔记本上的代码分析Agent
+- 用户终端Agent → 云端知识库Agent
+
+**需求:**
+- 云端Agent需要调用终端Agent的能力
+  - 访问用户本地文件
+  - 执行本地命令
+  - 使用本地工具(IDE、浏览器等)
+- 终端Agent需要调用云端Agent
+  - 访问组织知识库
+  - 查询共享资源
+  - 协作任务
+
+**挑战:**
+- 网络连接(终端可能在NAT后)
+- 认证和授权
+- 数据安全
+
+**需要HTTP接口!**
+
+### 场景3:终端Agent ↔ 终端Agent
+**示例:** 团队成员的Agent互相协作
+- **可能性:** 较小,但可能存在
+- **通信:** 通过云端中转或P2P
+
+## 架构方案
+
+### 方案A:基于现有API封装(推荐)
+
+#### 架构图
+```
+云端Agent                    终端Agent
+    |                            |
+    | HTTP POST /api/a2a/call    |
+    |--------------------------->|
+    |                            |
+    |    创建Trace + 执行任务     |
+    |                            |
+    | WebSocket /api/a2a/watch   |
+    |<---------------------------|
+    |    实时进度推送              |
+    |                            |
+    | HTTP GET /api/a2a/result   |
+    |--------------------------->|
+    |    返回最终结果              |
+    |<---------------------------|
+```
+
+#### 核心设计
+
+**1. 简化的A2A端点**
+
+```python
+# agent/api/a2a.py
+
+@app.post("/api/a2a/call")
+async def a2a_call(request: A2ACallRequest):
+    """
+    简化的Agent调用接口
+
+    请求:
+    {
+        "task": "分析这个项目的架构",
+        "agent_type": "explore",  # 可选
+        "context": {              # 可选
+            "files": [...],
+            "previous_results": {...}
+        },
+        "callback_url": "https://..."  # 可选,完成后回调
+    }
+
+    响应:
+    {
+        "call_id": "a2a-xxx",
+        "status": "started",
+        "watch_url": "ws://host/api/a2a/watch/a2a-xxx"
+    }
+    """
+    # 1. 认证和授权检查
+    # 2. 转换为内部格式
+    messages = [{"role": "user", "content": request.task}]
+    if request.context:
+        messages[0]["content"] += f"\n\n上下文:{json.dumps(request.context)}"
+
+    # 3. 调用现有runner(复用所有逻辑)
+    config = RunConfig(
+        agent_type=request.agent_type or "default",
+        trace_id=None  # 新建
+    )
+
+    # 4. 后台执行
+    task_id = await start_background_task(runner.run(messages, config))
+
+    # 5. 返回call_id(映射到trace_id)
+    return {
+        "call_id": f"a2a-{task_id}",
+        "status": "started",
+        "watch_url": f"ws://{host}/api/a2a/watch/{task_id}"
+    }
+
+
+@app.websocket("/api/a2a/watch/{call_id}")
+async def a2a_watch(websocket: WebSocket, call_id: str):
+    """
+    实时监听执行进度(复用现有WebSocket)
+
+    推送消息:
+    {
+        "type": "progress",
+        "data": {
+            "goal": "正在分析文件结构",
+            "progress": 0.3
+        }
+    }
+
+    {
+        "type": "completed",
+        "data": {
+            "result": "...",
+            "stats": {...}
+        }
+    }
+    """
+    # 复用现有的 /api/traces/{id}/watch 逻辑
+    trace_id = call_id.replace("a2a-", "")
+    await watch_trace(websocket, trace_id)
+
+
+@app.get("/api/a2a/result/{call_id}")
+async def a2a_result(call_id: str):
+    """
+    获取执行结果
+
+    响应:
+    {
+        "status": "completed",
+        "result": {
+            "summary": "...",
+            "details": {...}
+        },
+        "stats": {
+            "duration_ms": 5000,
+            "tokens": 1500,
+            "cost": 0.05
+        }
+    }
+    """
+    trace_id = call_id.replace("a2a-", "")
+    trace = await store.get_trace(trace_id)
+    messages = await store.get_main_path_messages(trace_id, trace.head_sequence)
+
+    # 提取最后的assistant消息作为结果
+    result = extract_final_result(messages)
+
+    return {
+        "status": trace.status,
+        "result": result,
+        "stats": {
+            "duration_ms": trace.total_duration_ms,
+            "tokens": trace.total_tokens,
+            "cost": trace.total_cost
+        }
+    }
+```
+
+**2. 客户端SDK(终端Agent使用)**
+
+```python
+# agent/client/a2a_client.py
+
+class A2AClient:
+    """A2A客户端,用于调用远程Agent"""
+
+    def __init__(self, base_url: str, api_key: str):
+        self.base_url = base_url
+        self.api_key = api_key
+
+    async def call(
+        self,
+        task: str,
+        agent_type: Optional[str] = None,
+        context: Optional[dict] = None,
+        wait: bool = True
+    ) -> Dict[str, Any]:
+        """
+        调用远程Agent
+
+        Args:
+            task: 任务描述
+            agent_type: Agent类型
+            context: 上下文信息
+            wait: 是否等待完成(False则立即返回call_id)
+        """
+        # 1. 发起调用
+        response = await self._post("/api/a2a/call", {
+            "task": task,
+            "agent_type": agent_type,
+            "context": context
+        })
+
+        call_id = response["call_id"]
+
+        if not wait:
+            return {"call_id": call_id, "status": "started"}
+
+        # 2. 等待完成(通过WebSocket或轮询)
+        result = await self._wait_for_completion(call_id)
+        return result
+
+    async def _wait_for_completion(self, call_id: str):
+        """通过WebSocket监听完成"""
+        async with websockets.connect(
+            f"{self.ws_url}/api/a2a/watch/{call_id}",
+            extra_headers={"Authorization": f"Bearer {self.api_key}"}
+        ) as ws:
+            async for message in ws:
+                data = json.loads(message)
+                if data["type"] == "completed":
+                    return data["data"]
+                elif data["type"] == "failed":
+                    raise A2AError(data["data"]["error"])
+
+    async def get_result(self, call_id: str) -> Dict[str, Any]:
+        """获取执行结果(轮询方式)"""
+        return await self._get(f"/api/a2a/result/{call_id}")
+```
+
+**3. 作为工具集成到Agent**
+
+```python
+# agent/tools/builtin/remote_agent.py
+
+@tool(description="调用远程Agent执行任务")
+async def remote_agent(
+    task: str,
+    agent_url: str,
+    agent_type: Optional[str] = None,
+    context: Optional[dict] = None,
+    ctx: ToolContext = None
+) -> ToolResult:
+    """
+    调用远程Agent(云端或其他终端)
+
+    Args:
+        task: 任务描述
+        agent_url: 远程Agent的URL
+        agent_type: Agent类型
+        context: 上下文信息
+    """
+    # 1. 创建客户端
+    client = A2AClient(
+        base_url=agent_url,
+        api_key=ctx.config.get("remote_agent_api_key")
+    )
+
+    # 2. 调用远程Agent
+    result = await client.call(
+        task=task,
+        agent_type=agent_type,
+        context=context,
+        wait=True
+    )
+
+    # 3. 返回结果
+    return ToolResult(
+        title=f"远程Agent完成: {task[:50]}",
+        output=result["result"]["summary"],
+        long_term_memory=f"调用远程Agent完成任务,耗时{result['stats']['duration_ms']}ms"
+    )
+```
+
+#### 优势
+
+1. **复用现有逻辑** - 所有Trace、Message、Goal管理都复用
+2. **简单易用** - 外部只需要提供task,不需要理解Trace概念
+3. **完整功能** - 继承所有现有能力(压缩、续跑、回溯等)
+4. **渐进式** - 可以先实现基础版本,逐步增强
+
+### 方案B:实现标准A2A协议
+
+#### 架构
+
+```python
+# agent/api/a2a_standard.py
+
+@app.post("/api/a2a/v1/tasks")
+async def create_task(request: A2ATaskRequest):
+    """
+    符合Google A2A协议的端点
+
+    请求格式(A2A标准):
+    {
+        "header": {
+            "message_id": "msg_001",
+            "timestamp": "2026-03-03T10:30:00Z"
+        },
+        "task": {
+            "description": "分析项目架构",
+            "capabilities_required": ["file_read", "code_analysis"]
+        },
+        "context": {...}
+    }
+    """
+    # 转换A2A格式到内部格式
+    # 调用runner
+    # 转换结果为A2A格式
+```
+
+#### 优势
+
+1. **标准化** - 符合行业标准
+2. **互操作性** - 可以与其他A2A兼容的Agent通信
+3. **未来兼容** - 跟随行业发展
+
+#### 劣势
+
+1. **复杂度高** - 需要实现完整的A2A协议
+2. **过度设计** - MVP阶段可能不需要
+3. **标准未稳定** - A2A协议还在演进中
+
+## 网络拓扑
+
+### 拓扑1:云端中心化
+
+```
+        云端Gateway
+            |
+    +-------+-------+
+    |       |       |
+  通用    爬虫    成本
+  助理    运维    统计
+    |
+    +-- 调用终端Agent(HTTP)
+            |
+        用户终端Agent
+```
+
+**特点:**
+- 云端Agent作为中心
+- 终端Agent需要暴露HTTP端点
+- 需要处理NAT穿透
+
+### 拓扑2:终端主动连接
+
+```
+用户终端Agent
+    |
+    | WebSocket长连接
+    |
+云端Gateway
+    |
+    +-- 通过连接推送任务
+```
+
+**特点:**
+- 终端Agent主动连接云端
+- 云端通过WebSocket推送任务
+- 无需NAT穿透
+- 类似飞书Bot的模式
+
+### 拓扑3:混合模式(推荐)
+
+```
+云端Agent <--HTTP--> 云端Agent(内存调用)
+    |
+    | WebSocket双向
+    |
+终端Agent <--HTTP--> 终端Agent(如果需要)
+```
+
+**特点:**
+- 云端Agent间用内存调用
+- 云端↔终端用WebSocket
+- 终端间可选HTTP(通过云端中转)
+
+## 认证和授权
+
+### 1. API Key认证
+
+```python
+# 终端Agent启动时注册
+POST /api/a2a/register
+{
+    "agent_id": "user123-laptop",
+    "capabilities": ["file_read", "bash", "browser"],
+    "device_info": {...}
+}
+
+# 返回API Key
+{
+    "api_key": "ak_xxx",
+    "agent_id": "user123-laptop"
+}
+
+# 后续调用携带API Key
+Authorization: Bearer ak_xxx
+```
+
+### 2. 权限控制
+
+```yaml
+# config/a2a_permissions.yaml
+agents:
+  user123-laptop:
+    can_access:
+      - conversations/user123/*
+      - resources/public/*
+    cannot_access:
+      - conversations/other_users/*
+      - agents/*/memory/*
+```
+
+### 3. 数据隔离
+
+- 终端Agent只能访问自己用户的数据
+- 云端Agent可以访问组织共享数据
+- 通过Gateway强制执行
+
+## 实现路线图
+
+### Phase 1:基础A2A接口(MVP)
+
+**目标:** 云端Agent ↔ 终端Agent基础通信
+
+**实现:**
+1. `/api/a2a/call` - 简化调用接口
+2. `/api/a2a/watch` - WebSocket监听
+3. `/api/a2a/result` - 获取结果
+4. `A2AClient` - 客户端SDK
+5. `remote_agent` - 工具集成
+
+**时间:** 1-2周
+
+### Phase 2:增强功能
+
+**目标:** 完善A2A能力
+
+**实现:**
+1. 认证和授权
+2. 数据隔离
+3. 成本控制
+4. 审计日志
+5. 错误处理和重试
+
+**时间:** 2-3周
+
+### Phase 3:标准化(可选)
+
+**目标:** 兼容A2A标准协议
+
+**实现:**
+1. 实现Google A2A协议
+2. 能力协商机制
+3. 与其他A2A Agent互操作
+
+**时间:** 3-4周
+
+## 示例场景
+
+### 场景:云端助理调用终端Agent分析代码
+
+**1. 用户在飞书问:** "帮我分析一下我笔记本上的项目架构"
+
+**2. 云端通用助理:**
+```python
+# 识别需要访问用户终端
+result = await remote_agent(
+    task="分析 /Users/sunlit/Code/Agent 的项目架构",
+    agent_url="https://user123-laptop.agent.local",
+    agent_type="explore"
+)
+```
+
+**3. 终端Agent:**
+- 接收任务
+- 创建本地Trace
+- 使用本地工具(read, glob, grep)
+- 分析代码结构
+- 返回结果
+
+**4. 云端助理:**
+- 接收终端Agent结果
+- 整合到回复中
+- 通过飞书返回给用户
+
+### 场景:终端Agent查询云端知识库
+
+**1. 用户在终端运行:**
+```bash
+agent-cli ask "公司的爬虫部署规范是什么?"
+```
+
+**2. 终端Agent:**
+```python
+# 识别需要查询组织知识库
+result = await remote_agent(
+    task="查询爬虫部署规范",
+    agent_url="https://org.agent.cloud",
+    agent_type="knowledge_query"
+)
+```
+
+**3. 云端知识库Agent:**
+- 查询resources/docs/
+- 查询experiences数据库
+- 返回相关文档
+
+**4. 终端Agent:**
+- 接收结果
+- 展示给用户
+
+## 技术细节
+
+### 1. NAT穿透方案
+
+**方案A:终端主动连接(推荐)**
+```python
+# 终端Agent启动时建立WebSocket长连接
+ws = await websockets.connect("wss://org.agent.cloud/api/a2a/connect")
+
+# 云端通过连接推送任务
+await ws.send(json.dumps({
+    "type": "task",
+    "task_id": "xxx",
+    "data": {...}
+}))
+
+# 终端执行并返回结果
+result = await execute_task(task)
+await ws.send(json.dumps({
+    "type": "result",
+    "task_id": "xxx",
+    "data": result
+}))
+```
+
+**方案B:使用ngrok等隧道服务**
+- 终端Agent启动时创建隧道
+- 注册公网URL到云端
+- 云端通过公网URL调用
+
+### 2. 消息序列化
+
+```python
+# 简化格式(内部使用)
+{
+    "task": "string",
+    "context": {...}
+}
+
+# 标准A2A格式(外部互操作)
+{
+    "header": {...},
+    "task": {...},
+    "capabilities": [...]
+}
+
+# 自动转换
+def to_a2a_format(internal_msg):
+    return {
+        "header": generate_header(),
+        "task": {"description": internal_msg["task"]},
+        "context": internal_msg.get("context", {})
+    }
+```
+
+### 3. 流式响应
+
+```python
+# 支持流式返回中间结果
+@app.websocket("/api/a2a/stream/{call_id}")
+async def a2a_stream(websocket: WebSocket, call_id: str):
+    async for event in runner.run(...):
+        if isinstance(event, Message):
+            await websocket.send_json({
+                "type": "message",
+                "data": event.to_dict()
+            })
+```
+
+## 安全考虑
+
+1. **认证:** API Key + JWT
+2. **授权:** 基于角色的访问控制
+3. **加密:** HTTPS/WSS强制
+4. **限流:** 防止滥用
+5. **审计:** 所有A2A调用记录
+6. **隔离:** 数据访问严格隔离
+
+## 成本控制
+
+```python
+# 每次A2A调用记录成本
+{
+    "call_id": "a2a-xxx",
+    "caller": "user123-laptop",
+    "callee": "org-cloud-agent",
+    "tokens": 1500,
+    "cost": 0.05,
+    "duration_ms": 5000
+}
+
+# 限额检查
+if user_cost_today > user_limit:
+    raise CostLimitExceeded()
+```
+
+## 总结
+
+### 推荐方案
+
+**Phase 1(MVP):** 方案A - 基于现有API封装
+- 简单、快速
+- 复用所有现有逻辑
+- 满足跨设备通信需求
+
+**Phase 3+:** 可选实现标准A2A协议
+- 如果需要与外部系统互操作
+- 跟随行业标准发展
+
+### 关键优势
+
+1. **复用现有能力** - Trace、Message、Goal、压缩等
+2. **渐进式实现** - 先简单后复杂
+3. **灵活扩展** - 可以逐步增强功能
+4. **标准兼容** - 未来可以支持A2A标准

+ 504 - 0
docs/research/a2a-mamp-protocol.md

@@ -0,0 +1,504 @@
+# MAMP:Minimal Agent Message Protocol
+
+**更新日期:** 2026-03-04
+
+## 设计目标
+
+实现与**其他 Agent 系统**(非本系统)的通用交互接口,保持最简化原则。
+
+**与现有方案的关系**:
+- [A2A 跨设备通信](./a2a-cross-device.md):内部 Agent 间通信(基于 Trace API)
+- **MAMP 协议**(本文档):与外部 Agent 系统的通用交互
+
+---
+
+## 核心设计原则
+
+1. **最小化协议**:只定义消息信封,不管内容格式
+2. **适配器模式**:通过适配器层与内部系统集成
+3. **松耦合**:各家 Agent 保持独立实现
+4. **渐进式**:先实现基础功能,需要时再扩展
+
+---
+
+## 消息格式
+
+### 基础消息结构
+
+```json
+{
+  "protocol": "mamp/1.0",
+  "message_id": "msg-uuid-123",
+  "conversation_id": "conv-uuid-456",
+  "from": "agent://your-domain.com/agent-123",
+  "to": "agent://other-domain.com/agent-456",
+  "content": [...],
+  "metadata": {
+    "timestamp": "2026-03-04T10:00:00Z"
+  }
+}
+```
+
+### 字段说明
+
+| 字段 | 类型 | 必需 | 说明 |
+|------|------|------|------|
+| `protocol` | string | 是 | 协议版本标识 |
+| `message_id` | string | 是 | 消息唯一标识 |
+| `conversation_id` | string | 否 | 对话标识(不提供则新建对话) |
+| `from` | string | 是 | 发送方 Agent URI |
+| `to` | string | 是 | 接收方 Agent URI |
+| `content` | string/array | 是 | 消息内容(支持多模态) |
+| `metadata` | object | 是 | 元数据(时间戳等) |
+
+### 新建 vs 续跑
+
+**规则**:通过 `conversation_id` 字段判断
+
+- **无 `conversation_id`**(null 或不存在)→ 新建对话,接收方生成并返回 conversation_id
+- **有 `conversation_id`** → 续跑对话,接收方查找对应的内部 trace_id
+
+**conversation_id 与 trace_id 的关系**:
+- `conversation_id`:跨 Agent 的对话标识符,双方共享
+- `trace_id`:每个 Agent 内部的执行记录,各自独立
+- 每个 Agent 维护 `conversation_id → trace_id` 映射
+
+---
+
+## 多模态内容格式
+
+### Content 结构
+
+参考 Anthropic SDK 和现有多模态实现(`agent/docs/multimodal.md`):
+
+```json
+{
+  "content": [
+    {
+      "type": "text",
+      "text": "这是文本内容"
+    },
+    {
+      "type": "image",
+      "source": {
+        "type": "url",
+        "url": "https://...",
+        "media_type": "image/png"
+      }
+    },
+    {
+      "type": "image",
+      "source": {
+        "type": "base64",
+        "media_type": "image/jpeg",
+        "data": "base64..."
+      }
+    },
+    {
+      "type": "code",
+      "language": "python",
+      "code": "def hello(): pass"
+    },
+    {
+      "type": "file",
+      "name": "report.pdf",
+      "mime_type": "application/pdf",
+      "source": {
+        "type": "url",
+        "url": "https://..."
+      }
+    }
+  ]
+}
+```
+
+### 纯文本简写
+
+```json
+{
+  "content": "纯文本消息"
+}
+```
+
+等价于:
+
+```json
+{
+  "content": [{"type": "text", "text": "纯文本消息"}]
+}
+```
+
+---
+
+## Agent Card(身份与能力)
+
+每个 Agent 提供静态的 card 端点,用于身份识别和能力发现。
+
+### 端点
+
+```
+GET https://your-agent.com/mamp/v1/card
+```
+
+### 响应格式
+
+```json
+{
+  "protocol": "mamp/1.0",
+  "agent_id": "agent://your-domain.com/agent-123",
+  "name": "Code Analyst",
+  "description": "专注于代码分析的 Agent",
+
+  "owner": {
+    "user_id": "user-789",
+    "user_name": "张三",
+    "organization": "YourCompany"
+  },
+
+  "device": {
+    "device_id": "device-mac-001",
+    "device_name": "MacBook Pro",
+    "location": "Beijing Office",
+    "platform": "darwin"
+  },
+
+  "capabilities": {
+    "content_types": ["text", "image", "code"],
+    "max_message_size": 10485760,
+    "streaming": true,
+    "async": true,
+    "tools": ["code_analysis", "file_read", "web_search"]
+  },
+
+  "access": {
+    "public": false,
+    "allowed_agents": ["agent://trusted.com/*"],
+    "require_auth": true
+  }
+}
+```
+
+---
+
+## 传输层
+
+### HTTP REST(最简实现)
+
+**发送消息**:
+
+```http
+POST https://other-agent.com/mamp/v1/messages
+Content-Type: application/json
+Authorization: Bearer {api_key}
+
+{MAMP 消息体}
+```
+
+**响应**:
+
+```json
+{
+  "conversation_id": "conv-abc-123",
+  "message_id": "msg-xyz-456",
+  "status": "received"
+}
+```
+
+**错误响应**:
+
+```json
+{
+  "error": "conversation_not_found",
+  "message": "Conversation conv-xxx not found",
+  "status_code": 404
+}
+```
+
+### 可选扩展
+
+- **WebSocket**:实时双向通信
+- **Server-Sent Events**:流式响应
+- **Message Queue**:异步消息(NATS/Redis)
+
+---
+
+## 寻址方案
+
+使用 URI 格式:`agent://domain/agent-id`
+
+**示例**:
+- `agent://your-domain.com/trace-123` - 你的 Agent
+- `agent://claude.ai/session-456` - Claude
+- `agent://openai.com/assistant-789` - OpenAI Assistant
+
+每个 Agent 系统自己决定如何解析 `agent-id` 部分。
+
+---
+
+## 系统集成
+
+### 三层架构
+
+```
+┌─────────────────────────────────────────────────┐
+│ Layer 3: 内部 Agent 逻辑                         │
+│ (Trace, Goal, Messages...)                      │
+└─────────────────────────────────────────────────┘
+                    ↕
+┌─────────────────────────────────────────────────┐
+│ Layer 2: MAMP 适配器                             │
+│ - 内部格式 ↔ MAMP 格式转换                        │
+│ - conversation_id ↔ trace_id 映射                │
+└─────────────────────────────────────────────────┘
+                    ↕
+┌─────────────────────────────────────────────────┐
+│ Layer 1: 传输层(HTTP/WebSocket/MQ)             │
+└─────────────────────────────────────────────────┘
+```
+
+### 接收端实现
+
+**实现位置**:`agent/trace/mamp_api.py`
+
+```python
+@app.post("/mamp/v1/messages")
+async def receive_mamp_message(msg: dict):
+    """接收外部 Agent 的 MAMP 消息"""
+
+    conv_id = msg.get("conversation_id")
+
+    if not conv_id:
+        # 新建对话
+        conv_id = f"conv-{generate_uuid()}"
+
+        # 创建新 Trace
+        async for item in runner.run(
+            messages=[{"role": "user", "content": msg["content"]}],
+            config=RunConfig(
+                context={
+                    "mamp_conversation_id": conv_id,
+                    "mamp_from": msg["from"]
+                }
+            )
+        ):
+            if isinstance(item, Trace):
+                await store_conversation_mapping(conv_id, item.trace_id)
+
+        return {
+            "conversation_id": conv_id,
+            "message_id": msg["message_id"],
+            "status": "received"
+        }
+
+    else:
+        # 续跑对话
+        trace_id = await get_trace_by_conversation_id(conv_id)
+        if not trace_id:
+            raise HTTPException(404, f"Conversation {conv_id} not found")
+
+        await runner.run(
+            messages=[{"role": "user", "content": msg["content"]}],
+            config=RunConfig(trace_id=trace_id)
+        )
+
+        return {
+            "conversation_id": conv_id,
+            "message_id": msg["message_id"],
+            "status": "received"
+        }
+```
+
+### 发送端实现
+
+**实现位置**:`agent/tools/builtin/mamp_adapter.py`
+
+```python
+@tool(description="与外部 Agent 通信")
+async def send_to_agent(
+    target_agent: str,
+    message: str,
+    conversation_id: Optional[str] = None,
+    ctx: ToolContext = None
+) -> ToolResult:
+    """
+    发送消息到外部 Agent
+
+    Args:
+        target_agent: 目标 Agent URI (agent://domain/id)
+        message: 消息内容
+        conversation_id: 对话 ID(可选,不提供则新建)
+    """
+
+    # 构建 MAMP 消息
+    mamp_msg = {
+        "protocol": "mamp/1.0",
+        "message_id": generate_uuid(),
+        "from": f"agent://{config.domain}/{ctx.trace_id}",
+        "to": target_agent,
+        "content": message,
+        "metadata": {"timestamp": datetime.now().isoformat()}
+    }
+
+    if conversation_id:
+        mamp_msg["conversation_id"] = conversation_id
+
+    # 发送
+    agent_url = parse_agent_url(target_agent)
+    response = await http_post(f"{agent_url}/mamp/v1/messages", mamp_msg)
+
+    # 新建时存储映射
+    if not conversation_id:
+        await store_conversation_mapping(
+            response["conversation_id"],
+            ctx.trace_id
+        )
+
+    return ToolResult(
+        title=f"已发送到 {target_agent}",
+        output=f"Conversation ID: {response['conversation_id']}",
+        long_term_memory=f"与 {target_agent} 的对话 {response['conversation_id']}"
+    )
+```
+
+### conversation_id 映射存储
+
+**实现位置**:`agent/trace/conversation_store.py`
+
+```python
+class ConversationStore:
+    """管理 MAMP conversation_id 和内部 trace_id 的映射"""
+
+    def __init__(self, base_dir: str = ".trace"):
+        self.mapping_file = Path(base_dir) / "mamp_conversations.json"
+
+    async def store_mapping(self, conversation_id: str, trace_id: str):
+        """存储映射关系"""
+        mappings = await self._load_mappings()
+        mappings[conversation_id] = {
+            "trace_id": trace_id,
+            "created_at": datetime.now().isoformat(),
+            "last_message_at": datetime.now().isoformat()
+        }
+        await self._save_mappings(mappings)
+
+    async def get_trace_id(self, conversation_id: str) -> Optional[str]:
+        """根据 conversation_id 查找 trace_id"""
+        mappings = await self._load_mappings()
+        mapping = mappings.get(conversation_id)
+        return mapping["trace_id"] if mapping else None
+```
+
+---
+
+## 使用示例
+
+### 新建对话
+
+```python
+# 调用外部 Agent
+result = await send_to_agent(
+    target_agent="agent://other.com/code-analyst",
+    message="帮我分析这段代码的性能"
+)
+# 返回: {"conversation_id": "conv-abc-123", ...}
+```
+
+### 续跑对话
+
+```python
+# 继续之前的对话
+result = await send_to_agent(
+    target_agent="agent://other.com/code-analyst",
+    message="那如果用异步方案呢?",
+    conversation_id="conv-abc-123"
+)
+```
+
+### 多模态消息
+
+```python
+# 发送图片
+result = await send_to_agent(
+    target_agent="agent://other.com/image-analyst",
+    message={
+        "content": [
+            {"type": "text", "text": "分析这张图片"},
+            {
+                "type": "image",
+                "source": {
+                    "type": "base64",
+                    "media_type": "image/png",
+                    "data": encode_image_base64("screenshot.png")
+                }
+            }
+        ]
+    }
+)
+```
+
+---
+
+## 与现有标准的关系
+
+MAMP 可以作为其他标准的"翻译层":
+
+- **MCP (Model Context Protocol)** → 写 MCP ↔ MAMP 适配器
+- **OpenAI Assistant API** → 写 OpenAI ↔ MAMP 适配器
+- **自定义协议** → 写对应的适配器
+
+**核心思想**:不要试图统一所有 Agent 的内部实现,而是提供一个最薄的互操作层。
+
+---
+
+## 可选扩展
+
+如果需要更丰富的功能,可以逐步添加:
+
+- **认证**:在 metadata 中加 `auth_token`
+- **流式传输**:使用 Server-Sent Events 或 WebSocket
+- **异步回调**:加 `callback_url` 字段
+- **能力协商**:通过 `/mamp/v1/card` 端点
+- **错误处理**:标准化错误码
+
+---
+
+## 实现路线图
+
+### Phase 1:基础协议(1-2 周)
+
+**目标**:实现最简 MAMP 协议
+
+**任务**:
+1. 实现 `/mamp/v1/messages` 端点(接收消息)
+2. 实现 `/mamp/v1/card` 端点(Agent 身份)
+3. 实现 `send_to_agent` 工具(发送消息)
+4. 实现 `ConversationStore`(映射管理)
+5. 支持纯文本消息
+
+### Phase 2:多模态支持(1 周)
+
+**目标**:支持图片、代码等多模态内容
+
+**任务**:
+1. 扩展 content 格式处理
+2. 集成现有多模态实现(`agent/llm/prompts/wrapper.py`)
+3. 支持 base64 和 URL 两种图片传输方式
+
+### Phase 3:增强功能(可选)
+
+**目标**:认证、流式、异步等高级功能
+
+**任务**:
+1. API Key 认证
+2. WebSocket 流式传输
+3. 异步回调机制
+4. 错误处理和重试
+
+---
+
+## 相关文档
+
+- [A2A 跨设备通信](./a2a-cross-device.md):内部 Agent 间通信方案
+- [多模态支持](../../agent/docs/multimodal.md):图片、PDF 处理
+- [工具系统](../../agent/docs/tools.md):工具定义、注册
+- [Agent 框架](../README.md):核心 Agent 能力

+ 114 - 0
docs/research/a2a-protocols.md

@@ -0,0 +1,114 @@
+# Agent2Agent (A2A) 通信协议调研
+
+**调研日期:** 2026-03-03
+
+## 一、行业标准协议
+
+### 1. Google A2A Protocol (2025.04)
+- **定位:** Agent间任务协调和协作
+- **特性:** 标准化消息格式、能力协商、异步通信
+- **适用:** 企业级跨平台Agent协作
+
+### 2. Anthropic MCP (2024.11)
+- **定位:** AI助手与工具/数据系统连接
+- **特性:** JSON-RPC 2.0、即插即用工具
+- **适用:** Agent与工具交互(非Agent间通信)
+- **采用:** OpenAI (2025.03)、Google DeepMind
+
+### 3. IBM ACP (2025初)
+- **定位:** 基于HTTP的Agent消息传递
+- **特性:** 消息代理(Kafka/RabbitMQ)、会话跟踪
+- **适用:** 生产级系统的模块化和可追溯性
+
+### 4. Huawei A2A-T (2026.03开源)
+- **定位:** A2A协议的扩展实现
+- **状态:** 刚开源,推动标准应用
+
+## 二、主流框架实现
+
+### AutoGen (Microsoft)
+- **通信模式:** 对话式多Agent协作
+- **核心:** ConversableAgent + GroupChat
+- **消息管理:** 每个Agent维护对话历史,GroupChat维护全局记录
+- **特点:** 自然语言驱动、支持人机协作
+
+### LangGraph (LangChain)
+- **通信模式:** 基于状态图的消息传递
+- **核心:** State Graph + Persistent State + Message Bus
+- **消息管理:** 状态图管理 + 检查点机制
+- **特点:** 生产级、可追溯、原生支持A2A协议
+
+### CrewAI
+- **通信模式:** 基于角色的任务委派
+- **核心:** Role-Based Agents + Task Delegation + Crew Coordination
+- **消息管理:** Crew级任务历史 + 委派记录
+- **特点:** 类似人类团队、层次化任务分配
+
+## 三、通信模式对比
+
+| 模式 | 优点 | 缺点 | 适用场景 |
+|------|------|------|----------|
+| **直接调用** | 简单、低延迟 | 紧耦合、难扩展 | 小规模简单协作 |
+| **消息队列** | 解耦、异步、可靠 | 复杂、需基础设施 | 企业级大规模系统 |
+| **共享状态** | 知识全局可见、紧密协调 | 并发控制、状态冲突 | 高度协同团队 |
+| **混合模式** | 灵活、可优化 | 架构复杂 | 复杂生产系统 |
+
+## 四、消息历史管理策略
+
+1. **滑动窗口:** 保留最近N条消息
+2. **智能截断:** 基于重要性评分删除
+3. **自动总结:** 接近限制时总结历史(Claude Code使用)
+4. **分层存储:** 短期完整 + 长期总结
+5. **溢出修剪:** 从最旧消息开始修剪
+
+## 五、关键挑战
+
+### 1. 消息历史维护
+- 上下文窗口限制
+- 需要智能压缩策略
+- 跨Agent的上下文共享
+
+### 2. 异步通信
+- 事件驱动架构
+- 回调机制
+- 状态更新和轮询
+
+### 3. 多Agent协作复杂性
+- 协调模式(集中式 vs 去中心化)
+- 冲突解决
+- 死锁预防
+- 可观测性
+
+## 六、标准化趋势
+
+**当前状态(2024-2026):**
+- 协议层分化:MCP(工具层)、A2A(协作层)、ACP(传输层)
+- 行业共识形成中:Google、OpenAI、Anthropic、IBM、Huawei推动
+- 互操作性是关键
+
+**未来展望:**
+- 2026-2027:协议标准逐步成熟
+- 2028-2030:可能出现统一标准
+- 长期:Agent网络成为基础设施
+
+## 七、实践建议
+
+### 架构设计
+1. **分层设计:** 工具层(MCP)+ 协作层(A2A)+ 传输层(ACP)
+2. **消息管理:** 自动总结 + 分层存储
+3. **异步处理:** 事件驱动 + 超时重试
+4. **可观测性:** 结构化日志 + 分布式追踪
+
+### 选择建议
+- **小规模:** AutoGen、CrewAI
+- **大规模:** LangGraph + ACP/A2A
+- **工具集成:** 优先MCP
+- **Agent协作:** 优先A2A
+
+## 参考资料
+
+- [Google A2A Protocol](https://a2a-protocol.org/latest/specification/)
+- [Anthropic MCP](https://modelcontextprotocol.io/specification/2025-06-18)
+- [Huawei A2A-T](https://www.huawei.com/en/news/2026/2/mwc-a2at-opensource)
+- [Agent Interoperability Survey](https://arxiv.org/html/2505.02279v1)
+- [Framework Comparison 2026](https://markaicode.com/crewai-vs-autogen-vs-langgraph-2026/)

+ 484 - 0
docs/research/a2a-trace-storage.md

@@ -0,0 +1,484 @@
+# A2A跨设备Trace存储方案
+
+**问题:** 现有Trace存储在本地文件系统,跨设备时如何访问?
+
+## 场景分析
+
+### 场景1:云端Agent调用终端Agent
+
+```
+云端Agent (云端存储)
+    ↓ 调用
+终端Agent (终端存储)
+    ↓ 返回 sub_trace_id
+云端Agent 想 continue_from sub_trace_id
+    ↓ 问题:sub_trace在终端,云端访问不到!
+```
+
+### 场景2:终端Agent调用云端Agent
+
+```
+终端Agent (终端存储)
+    ↓ 调用
+云端Agent (云端存储)
+    ↓ 返回 sub_trace_id
+终端Agent 想 continue_from sub_trace_id
+    ↓ 问题:sub_trace在云端,终端访问不到!
+```
+
+## 方案对比
+
+### 方案1:远程Trace访问(推荐)
+
+#### 核心思想
+
+**Trace ID包含位置信息,通过HTTP API访问远程Trace**
+
+#### Trace ID格式
+
+```
+本地Trace: abc-123
+远程Trace: agent://terminal-agent-456/abc-123
+          ^^^^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^
+          协议      Agent地址          本地ID
+```
+
+#### 实现
+
+```python
+# agent/trace/remote_store.py
+
+class RemoteTraceStore:
+    """远程Trace存储代理"""
+
+    def __init__(self, agent_url: str, api_key: str):
+        self.agent_url = agent_url
+        self.api_key = api_key
+
+    async def get_trace(self, trace_id: str) -> Optional[Trace]:
+        """通过HTTP API获取远程Trace"""
+        response = await self._get(f"/api/traces/{trace_id}")
+        if response:
+            return Trace(**response)
+        return None
+
+    async def get_main_path_messages(
+        self, trace_id: str, head_sequence: int
+    ) -> List[Message]:
+        """获取远程Trace的消息"""
+        response = await self._get(
+            f"/api/traces/{trace_id}/messages",
+            params={"mode": "main_path", "head_sequence": head_sequence}
+        )
+        return [Message(**m) for m in response["messages"]]
+
+    async def add_message(self, message: Message) -> str:
+        """向远程Trace追加消息(续跑)"""
+        response = await self._post(
+            f"/api/traces/{message.trace_id}/messages",
+            data=message.to_dict()
+        )
+        return response["message_id"]
+
+
+# agent/trace/hybrid_store.py
+
+class HybridTraceStore:
+    """混合存储:本地 + 远程"""
+
+    def __init__(self, local_store: FileSystemTraceStore):
+        self.local_store = local_store
+        self.remote_stores = {}  # agent_url -> RemoteTraceStore
+
+    def _parse_trace_id(self, trace_id: str) -> tuple[str, str]:
+        """
+        解析Trace ID
+
+        返回: (location, local_id)
+        - location: "local" 或 agent_url
+        - local_id: 本地Trace ID
+        """
+        if trace_id.startswith("agent://"):
+            # agent://terminal-agent-456/abc-123
+            parts = trace_id[8:].split("/", 1)
+            return parts[0], parts[1]
+        else:
+            return "local", trace_id
+
+    async def get_trace(self, trace_id: str) -> Optional[Trace]:
+        """获取Trace(自动路由到本地或远程)"""
+        location, local_id = self._parse_trace_id(trace_id)
+
+        if location == "local":
+            return await self.local_store.get_trace(local_id)
+        else:
+            # 远程Trace
+            if location not in self.remote_stores:
+                # 创建远程存储代理
+                self.remote_stores[location] = RemoteTraceStore(
+                    agent_url=f"https://{location}",
+                    api_key=self._get_api_key(location)
+                )
+            return await self.remote_stores[location].get_trace(local_id)
+
+    async def add_message(self, message: Message) -> str:
+        """添加消息(自动路由)"""
+        location, local_id = self._parse_trace_id(message.trace_id)
+
+        if location == "local":
+            return await self.local_store.add_message(message)
+        else:
+            # 向远程Trace追加消息
+            return await self.remote_stores[location].add_message(message)
+```
+
+#### agent工具修改
+
+```python
+@tool(description="创建 Agent 执行任务")
+async def agent(
+    task: Union[str, List[str]],
+    continue_from: Optional[str] = None,  # 支持远程Trace ID
+    agent_url: Optional[str] = None,      # 新增:远程Agent地址
+    context: Optional[dict] = None,
+) -> Dict[str, Any]:
+    """
+    创建 Agent 执行任务
+
+    Args:
+        task: 任务描述
+        continue_from: 继续已有trace(支持远程Trace ID)
+        agent_url: 远程Agent地址(如果调用远程Agent)
+        context: 框架注入的上下文
+    """
+    store = context.get("store")  # HybridTraceStore
+
+    if agent_url:
+        # 调用远程Agent
+        result = await _call_remote_agent(agent_url, task, continue_from)
+        # 返回远程Trace ID
+        remote_trace_id = f"agent://{agent_url}/{result['sub_trace_id']}"
+        return {
+            **result,
+            "sub_trace_id": remote_trace_id,
+            "remote": True
+        }
+    else:
+        # 本地执行(现有逻辑)
+        if continue_from:
+            # 可能是远程Trace,HybridStore会自动处理
+            existing = await store.get_trace(continue_from)
+            if not existing:
+                return {"status": "failed", "error": "Trace not found"}
+
+        # ... 现有逻辑
+```
+
+#### 使用示例
+
+```python
+# 云端Agent调用终端Agent
+result1 = await agent(
+    task="分析本地项目",
+    agent_url="terminal-agent-456.local"
+)
+# 返回: {"sub_trace_id": "agent://terminal-agent-456.local/abc-123"}
+
+# 继续对话(自动访问远程Trace)
+result2 = await agent(
+    task="重点分析core模块",
+    continue_from=result1["sub_trace_id"],  # 远程Trace ID
+    agent_url="terminal-agent-456.local"
+)
+# HybridStore自动通过HTTP API访问远程Trace
+```
+
+#### 优势
+
+1. **透明** - Agent不需要关心Trace在哪里
+2. **灵活** - 支持本地和远程Trace
+3. **简单** - 只需要在Trace ID中编码位置信息
+4. **兼容** - 现有本地Trace不受影响
+
+#### 劣势
+
+1. **网络延迟** - 访问远程Trace需要HTTP请求
+2. **依赖网络** - 远程Agent必须在线
+
+---
+
+### 方案2:中心化Trace存储
+
+#### 核心思想
+
+**所有Agent共享同一个Trace存储(数据库)**
+
+#### 架构
+
+```
+云端Agent ──┐
+            ├──> 中心化Trace存储(PostgreSQL/MongoDB)
+终端Agent ──┘
+```
+
+#### 实现
+
+```python
+# agent/trace/db_store.py
+
+class DatabaseTraceStore:
+    """数据库Trace存储"""
+
+    def __init__(self, db_url: str):
+        self.db = connect(db_url)
+
+    async def create_trace(self, trace: Trace) -> str:
+        await self.db.traces.insert_one(trace.to_dict())
+        return trace.trace_id
+
+    async def get_trace(self, trace_id: str) -> Optional[Trace]:
+        doc = await self.db.traces.find_one({"trace_id": trace_id})
+        if doc:
+            return Trace(**doc)
+        return None
+
+    # ... 其他方法
+```
+
+#### 配置
+
+```yaml
+# config/storage.yaml
+trace_store:
+  type: database
+  url: postgresql://user:pass@db.cloud/traces
+  # 或
+  url: mongodb://db.cloud/traces
+```
+
+#### 优势
+
+1. **简单** - 所有Agent访问同一个存储,无需特殊处理
+2. **一致性** - 数据强一致性
+3. **查询能力** - 可以跨Trace查询和分析
+
+#### 劣势
+
+1. **依赖中心** - 需要中心化数据库
+2. **网络依赖** - 终端Agent必须能访问数据库
+3. **隐私问题** - 终端数据存储在云端
+4. **迁移成本** - 需要从文件系统迁移到数据库
+
+---
+
+### 方案3:Trace同步/缓存
+
+#### 核心思想
+
+**按需同步远程Trace到本地**
+
+#### 实现
+
+```python
+class CachedRemoteStore:
+    """带缓存的远程存储"""
+
+    def __init__(self, local_store, remote_url):
+        self.local_store = local_store
+        self.remote_url = remote_url
+        self.cache = {}  # trace_id -> Trace
+
+    async def get_trace(self, trace_id: str) -> Optional[Trace]:
+        # 1. 检查本地
+        trace = await self.local_store.get_trace(trace_id)
+        if trace:
+            return trace
+
+        # 2. 检查缓存
+        if trace_id in self.cache:
+            return self.cache[trace_id]
+
+        # 3. 从远程获取并缓存
+        trace = await self._fetch_remote(trace_id)
+        if trace:
+            self.cache[trace_id] = trace
+            # 可选:持久化到本地
+            await self.local_store.create_trace(trace)
+        return trace
+
+    async def add_message(self, message: Message) -> str:
+        # 同时写入本地和远程
+        local_id = await self.local_store.add_message(message)
+        await self._sync_to_remote(message)
+        return local_id
+```
+
+#### 优势
+
+1. **性能** - 本地缓存减少网络请求
+2. **离线能力** - 缓存后可以离线访问
+
+#### 劣势
+
+1. **一致性** - 缓存可能过期
+2. **复杂度** - 需要处理同步和冲突
+3. **存储开销** - 需要额外的本地存储
+
+---
+
+### 方案4:混合模式(推荐)
+
+#### 核心思想
+
+**结合方案1和方案2的优点**
+
+- **组织内部**:使用中心化存储(数据库)
+- **跨组织**:使用远程访问(HTTP API)
+
+#### 架构
+
+```
+组织内部:
+云端Agent ──┐
+            ├──> 组织数据库
+职能Agent ──┘
+
+跨组织:
+组织A Agent <──HTTP API──> 组织B Agent
+```
+
+#### 配置
+
+```yaml
+# config/storage.yaml
+trace_store:
+  # 组织内部使用数据库
+  internal:
+    type: database
+    url: postgresql://org-db/traces
+
+  # 跨组织使用远程访问
+  external:
+    type: remote
+    agents:
+      - url: https://partner-org.com
+        api_key: xxx
+```
+
+#### 优势
+
+1. **灵活** - 根据场景选择最优方案
+2. **性能** - 组织内部低延迟
+3. **隐私** - 跨组织数据不共享存储
+
+---
+
+## 推荐方案
+
+### MVP阶段(Phase 1-2):方案1 - 远程Trace访问
+
+**理由:**
+1. **最小改动** - 只需要添加RemoteTraceStore和HybridStore
+2. **灵活** - 支持任意拓扑
+3. **隐私友好** - 数据不离开设备
+4. **渐进式** - 可以逐步迁移到方案4
+
+**实现步骤:**
+1. 实现RemoteTraceStore(通过HTTP API访问)
+2. 实现HybridTraceStore(路由到本地或远程)
+3. 修改agent工具支持agent_url参数
+4. 在Trace ID中编码位置信息
+
+### 长期(Phase 3+):方案4 - 混合模式
+
+**理由:**
+1. **组织内部高效** - 数据库存储,低延迟
+2. **跨组织灵活** - HTTP API,保护隐私
+3. **可扩展** - 支持复杂场景
+
+## 实现细节
+
+### 1. Trace ID格式
+
+```python
+# 本地Trace
+"abc-123"
+
+# 远程Trace(完整格式)
+"agent://terminal-agent-456.local:8000/abc-123"
+
+# 远程Trace(简化格式,使用注册的agent_id)
+"@terminal-agent-456/abc-123"
+```
+
+### 2. Agent注册表
+
+```yaml
+# config/agents.yaml
+agents:
+  terminal-agent-456:
+    url: https://terminal-agent-456.local:8000
+    api_key: ak_xxx
+    type: terminal
+```
+
+### 3. API端点
+
+```python
+# 必需的API端点(用于远程访问)
+GET  /api/traces/{trace_id}                    # 获取Trace
+GET  /api/traces/{trace_id}/messages           # 获取消息
+POST /api/traces/{trace_id}/run                # 续跑
+POST /api/traces/{trace_id}/messages           # 追加消息
+```
+
+### 4. 认证和授权
+
+```python
+# 每个Agent有自己的API Key
+headers = {
+    "Authorization": f"Bearer {api_key}"
+}
+
+# 权限检查
+if trace.uid != request.user_id:
+    raise Forbidden("Cannot access other user's trace")
+```
+
+### 5. 性能优化
+
+```python
+# 批量获取消息
+GET /api/traces/{trace_id}/messages?limit=100&offset=0
+
+# 增量同步
+GET /api/traces/{trace_id}/messages?since_sequence=50
+
+# 压缩传输
+headers = {"Accept-Encoding": "gzip"}
+```
+
+## 总结
+
+**推荐路线:**
+
+1. **Phase 1(MVP)** - 实现方案1(远程Trace访问)
+   - 最小改动
+   - 快速验证跨设备A2A
+   - 2-3周
+
+2. **Phase 2** - 优化和增强
+   - 添加缓存
+   - 批量API
+   - 性能优化
+   - 2-3周
+
+3. **Phase 3(可选)** - 迁移到方案4(混合模式)
+   - 组织内部使用数据库
+   - 跨组织使用远程访问
+   - 4-6周
+
+**关键优势:**
+- 渐进式实现
+- 最小化风险
+- 保持灵活性

+ 0 - 0
examples/analyze_story/README.md → examples/archive/analyze_story/README.md


+ 0 - 0
examples/analyze_story/analysis_results.json → examples/archive/analyze_story/analysis_results.json


+ 0 - 0
examples/analyze_story/analyze_samples.py → examples/archive/analyze_story/analyze_samples.py


+ 0 - 0
examples/analyze_story/generate_report.py → examples/archive/analyze_story/generate_report.py


+ 0 - 0
examples/analyze_story/input/中国合伙人.pdf → examples/archive/analyze_story/input/中国合伙人.pdf


+ 0 - 0
examples/analyze_story/input/大奉打更人.txt → examples/archive/analyze_story/input/大奉打更人.txt


+ 0 - 0
examples/analyze_story/input/搜神记.txt → examples/archive/analyze_story/input/搜神记.txt


+ 0 - 0
examples/analyze_story/input/无双.docx → examples/archive/analyze_story/input/无双.docx


+ 0 - 0
examples/analyze_story/input/雪中悍刀行.txt → examples/archive/analyze_story/input/雪中悍刀行.txt


+ 0 - 0
examples/analyze_story/input/魔道祖师.txt → examples/archive/analyze_story/input/魔道祖师.txt


+ 0 - 0
examples/analyze_story/knowledge/01_Scene_Sequel_Structure.md → examples/archive/analyze_story/knowledge/01_Scene_Sequel_Structure.md


+ 0 - 0
examples/analyze_story/knowledge/01_save_the_cat_beat_sheet.md → examples/archive/analyze_story/knowledge/01_save_the_cat_beat_sheet.md


+ 0 - 0
examples/analyze_story/knowledge/01_scene_sequel_theory.md → examples/archive/analyze_story/knowledge/01_scene_sequel_theory.md


+ 0 - 0
examples/analyze_story/knowledge/01_叙事理论综述.md → examples/archive/analyze_story/knowledge/01_叙事理论综述.md


+ 0 - 0
examples/analyze_story/knowledge/02_MICE_Quotient.md → examples/archive/analyze_story/knowledge/02_MICE_Quotient.md


+ 0 - 0
examples/analyze_story/knowledge/02_MICE_quotient_theory.md → examples/archive/analyze_story/knowledge/02_MICE_quotient_theory.md


+ 0 - 0
examples/analyze_story/knowledge/03_Save_the_Cat_Beats.md → examples/archive/analyze_story/knowledge/03_Save_the_Cat_Beats.md


+ 0 - 0
examples/analyze_story/knowledge/03_web_novel_techniques.md → examples/archive/analyze_story/knowledge/03_web_novel_techniques.md


+ 0 - 0
examples/analyze_story/knowledge/04_Web_Novel_Theory.md → examples/archive/analyze_story/knowledge/04_Web_Novel_Theory.md


+ 0 - 0
examples/analyze_story/knowledge/04_save_the_cat_beats.md → examples/archive/analyze_story/knowledge/04_save_the_cat_beats.md


+ 0 - 0
examples/analyze_story/knowledge/05_AI_narrative_generation.md → examples/archive/analyze_story/knowledge/05_AI_narrative_generation.md


+ 0 - 0
examples/analyze_story/knowledge/05_Integrated_Methodology.md → examples/archive/analyze_story/knowledge/05_Integrated_Methodology.md


+ 0 - 0
examples/analyze_story/knowledge/07_Methodology_Analysis.md → examples/archive/analyze_story/knowledge/07_Methodology_Analysis.md


+ 0 - 0
examples/analyze_story/knowledge/1_scene_sequel_theory.md → examples/archive/analyze_story/knowledge/1_scene_sequel_theory.md


+ 0 - 0
examples/analyze_story/knowledge/samples_overview.md → examples/archive/analyze_story/knowledge/samples_overview.md


+ 0 - 0
examples/analyze_story/methodology/README.md → examples/archive/analyze_story/methodology/README.md


+ 0 - 0
examples/analyze_story/methodology/v2_improved_methodology.md → examples/archive/analyze_story/methodology/v2_improved_methodology.md


+ 0 - 0
examples/analyze_story/read_all_files.py → examples/archive/analyze_story/read_all_files.py


+ 0 - 0
examples/analyze_story/read_samples.py → examples/archive/analyze_story/read_samples.py


+ 0 - 0
examples/analyze_story/read_txt_files.py → examples/archive/analyze_story/read_txt_files.py


+ 1 - 1
examples/analyze_story/run.py → examples/archive/analyze_story/run.py

@@ -153,7 +153,7 @@ async def show_interactive_menu(
 
 
             # 2. 结构化解析与保存 (ACE Curator 逻辑)
             # 2. 结构化解析与保存 (ACE Curator 逻辑)
             if reflection_text:
             if reflection_text:
-                experiences_path = runner.experiences_path or "./.cache/experiences.md"
+                # experiences_path = runner.experiences_path  # 已废弃,使用知识系统 or "./.cache/experiences.md"
                 os.makedirs(os.path.dirname(experiences_path), exist_ok=True)
                 os.makedirs(os.path.dirname(experiences_path), exist_ok=True)
                 
                 
                 # 正则匹配:- [intent:..., state:...] 内容
                 # 正则匹配:- [intent:..., state:...] 内容

+ 0 - 0
examples/analyze_story/runs/搜神记/analysis/w0.error.txt → examples/archive/analyze_story/runs/搜神记/analysis/w0.error.txt


+ 0 - 0
examples/analyze_story/runs/搜神记/analysis/w0.json → examples/archive/analyze_story/runs/搜神记/analysis/w0.json


+ 0 - 0
examples/analyze_story/runs/搜神记/sft_raw/w0_demo/stats.json → examples/archive/analyze_story/runs/搜神记/sft_raw/w0_demo/stats.json


Некоторые файлы не были показаны из-за большого количества измененных файлов