Talegorithm 1 месяц назад
Родитель
Сommit
3ea53f3c71
53 измененных файлов с 908 добавлено и 1081 удалено
  1. 1 0
      .gitignore
  2. 31 16
      agent/__init__.py
  3. 51 0
      agent/core/README.md
  4. 17 0
      agent/core/__init__.py
  5. 26 0
      agent/core/config.py
  6. 64 92
      agent/core/runner.py
  7. 0 9
      agent/debug/__init__.py
  8. 0 45
      agent/events.py
  9. 71 0
      agent/execution/README.md
  10. 18 10
      agent/execution/__init__.py
  11. 1 1
      agent/execution/api.py
  12. 6 5
      agent/execution/models.py
  13. 1 1
      agent/execution/protocols.py
  14. 1 1
      agent/execution/store.py
  15. 1 1
      agent/execution/tree_dump.py
  16. 1 1
      agent/execution/websocket.py
  17. 5 6
      agent/llm/__init__.py
  18. 0 0
      agent/llm/gemini.py
  19. 0 0
      agent/llm/openrouter.py
  20. 0 0
      agent/llm/prompts/__init__.py
  21. 0 0
      agent/llm/prompts/loader.py
  22. 1 1
      agent/llm/prompts/wrapper.py
  23. 0 10
      agent/llm/providers/__init__.py
  24. 91 0
      agent/memory/README.md
  25. 37 0
      agent/memory/__init__.py
  26. 0 0
      agent/memory/models.py
  27. 2 2
      agent/memory/protocols.py
  28. 27 6
      agent/memory/skill_loader.py
  29. 2 2
      agent/memory/stores.py
  30. 0 9
      agent/models/__init__.py
  31. 0 15
      agent/skills/browser_use/__init__.py
  32. 0 218
      agent/skills/browser_use/browser-use.md
  33. 0 174
      agent/skills/browser_use/setup.py
  34. 16 25
      agent/skills/core.md
  35. 0 15
      agent/storage/__init__.py
  36. 94 0
      agent/tools/README.md
  37. 3 0
      agent/tools/builtin/__init__.py
  38. 1 2
      agent/tools/builtin/search.py
  39. 3 3
      agent/tools/builtin/skill.py
  40. 108 50
      docs/README.md
  41. 2 2
      docs/multimodal.md
  42. 59 19
      docs/project-structure.md
  43. 101 20
      docs/skills.md
  44. 6 6
      docs/step-tree.md
  45. 4 4
      docs/sub-agents.md
  46. 3 2
      docs/testing.md
  47. 4 4
      docs/trace-api.md
  48. 1 1
      examples/feature_extract/feature_extract.prompt
  49. 44 40
      examples/feature_extract/run.py
  50. 3 7
      examples/feature_extract/test.prompt
  51. 1 1
      examples/subagent_example.py
  52. 0 0
      tests/__init__.py
  53. 0 255
      tests/test_runner.py

+ 1 - 0
.gitignore

@@ -36,6 +36,7 @@ env/
 *.swo
 *~
 CLAUDE.md
+.claude/
 
 # Testing
 .pytest_cache/

+ 31 - 16
agent/__init__.py

@@ -1,43 +1,58 @@
 """
-Reson Agent - 可扩展、可学习的 Agent 框架
+Reson Agent - 模块化、可扩展的 Agent 框架
 
 核心导出:
 - AgentRunner: Agent 执行引擎
 - AgentConfig: Agent 配置
-- AgentEvent: Agent 事件
 - Trace, Step: 执行追踪
 - Experience, Skill: 记忆模型
 - tool: 工具装饰器
 - TraceStore, MemoryStore: 存储接口
 """
 
-from agent.runner import AgentRunner, AgentConfig
-from agent.events import AgentEvent
-from agent.trace import Trace, Step, StepType, TraceStore
-from agent.models.memory import Experience, Skill
+# 核心引擎
+from agent.core.runner import AgentRunner
+from agent.core.config import AgentConfig, CallResult
+
+# 执行追踪
+from agent.execution.models import Trace, Step, StepType, Status
+from agent.execution.protocols import TraceStore
+from agent.execution.store import MemoryTraceStore
+
+# 记忆系统
+from agent.memory.models import Experience, Skill
+from agent.memory.protocols import MemoryStore, StateStore
+from agent.memory.stores import MemoryMemoryStore, MemoryStateStore
+
+# 工具系统
 from agent.tools import tool, ToolRegistry, get_tool_registry
-from agent.storage.protocols import MemoryStore, StateStore
+from agent.tools.models import ToolResult, ToolContext
 
-__version__ = "0.1.0"
+__version__ = "0.2.0"
 
 __all__ = [
-    # Runner
+    # Core
     "AgentRunner",
     "AgentConfig",
-    # Events
-    "AgentEvent",
-    # Models
+    "CallResult",
+    # Execution
     "Trace",
     "Step",
     "StepType",
+    "Status",
+    "TraceStore",
+    "MemoryTraceStore",
+    # Memory
     "Experience",
     "Skill",
+    "MemoryStore",
+    "StateStore",
+    "MemoryMemoryStore",
+    "MemoryStateStore",
     # Tools
     "tool",
     "ToolRegistry",
     "get_tool_registry",
-    # Storage
-    "TraceStore",
-    "MemoryStore",
-    "StateStore",
+    "ToolResult",
+    "ToolContext",
 ]

+ 51 - 0
agent/core/README.md

@@ -0,0 +1,51 @@
+# Agent Core - 核心引擎模块
+
+## 职责
+
+核心引擎是框架的"心脏",负责:
+
+1. **Agent 主循环逻辑** (`runner.py`)
+   - 单次调用模式:`call()` - 简单的 LLM 调用
+   - Agent 模式:`run()` - 循环执行 + 记忆 + 工具调用
+
+2. **配置数据类** (`config.py`)
+   - `AgentConfig` - Agent 配置参数
+   - `CallResult` - 单次调用返回结果
+
+3. **事件定义** (`events.py`)
+   - `AgentEvent` - Agent 事件数据结构
+   - `AgentEventType` - 事件类型枚举
+
+## 特点
+
+- **最小依赖**:核心模块只依赖 execution, memory, tools
+- **最稳定**:核心 API 变更频率最低
+- **可扩展**:通过插件化的 tools, execution, memory 扩展功能
+
+## 使用示例
+
+```python
+from agent.core import AgentRunner, AgentConfig
+
+# 创建 Runner
+runner = AgentRunner(
+    llm_call=my_llm_function,
+    config=AgentConfig(max_iterations=10)
+)
+
+# 单次调用
+result = await runner.call(
+    messages=[{"role": "user", "content": "Hello"}]
+)
+
+# Agent 模式
+async for event in runner.run(task="Complete this task"):
+    print(event)
+```
+
+## 文件说明
+
+- `runner.py` - AgentRunner 类,核心执行逻辑
+- `config.py` - 配置类定义
+- `events.py` - 事件系统
+- `__init__.py` - 模块导出

+ 17 - 0
agent/core/__init__.py

@@ -0,0 +1,17 @@
+"""
+Agent Core - 核心引擎模块
+
+职责:
+1. Agent 主循环逻辑(call() 和 run())
+2. 配置数据类(AgentConfig, CallResult)
+"""
+
+from agent.core.runner import AgentRunner, BUILTIN_TOOLS
+from agent.core.config import AgentConfig, CallResult
+
+__all__ = [
+    "AgentRunner",
+    "BUILTIN_TOOLS",
+    "AgentConfig",
+    "CallResult",
+]

+ 26 - 0
agent/core/config.py

@@ -0,0 +1,26 @@
+"""
+Agent 配置类
+"""
+
+from dataclasses import dataclass
+from typing import Optional, List, Dict
+
+
+@dataclass
+class AgentConfig:
+    """Agent 配置"""
+    agent_type: str = "default"
+    max_iterations: int = 10
+    enable_memory: bool = True
+    auto_execute_tools: bool = True
+
+
+@dataclass
+class CallResult:
+    """单次调用结果"""
+    reply: str
+    tool_calls: Optional[List[Dict]] = None
+    trace_id: Optional[str] = None
+    step_id: Optional[str] = None
+    tokens: Optional[Dict[str, int]] = None
+    cost: float = 0.0

+ 64 - 92
agent/runner.py → agent/core/runner.py

@@ -9,17 +9,17 @@ Agent Runner - Agent 执行引擎
 """
 
 import logging
-from dataclasses import dataclass, field
+from dataclasses import field
 from datetime import datetime
-from typing import AsyncIterator, Optional, Dict, Any, List, Callable, Literal
+from typing import AsyncIterator, Optional, Dict, Any, List, Callable, Literal, Union
 
-from agent.events import AgentEvent
-from agent.trace import Trace, Step, TraceStore
-from agent.models.memory import Experience, Skill
-from agent.storage.protocols import MemoryStore, StateStore
-from agent.storage.skill_loader import load_skills_from_dir
+from agent.core.config import AgentConfig, CallResult
+from agent.execution import Trace, Step, TraceStore
+from agent.memory.models import Experience, Skill
+from agent.memory.protocols import MemoryStore, StateStore
+from agent.memory.skill_loader import load_skills_from_dir
 from agent.tools import ToolRegistry, get_tool_registry
-from agent.debug import dump_tree, dump_markdown
+from agent.execution import dump_tree, dump_markdown
 
 logger = logging.getLogger(__name__)
 
@@ -37,26 +37,6 @@ BUILTIN_TOOLS = [
 ]
 
 
-@dataclass
-class AgentConfig:
-    """Agent 配置"""
-    agent_type: str = "default"
-    max_iterations: int = 10
-    enable_memory: bool = True
-    auto_execute_tools: bool = True
-
-
-@dataclass
-class CallResult:
-    """单次调用结果"""
-    reply: str
-    tool_calls: Optional[List[Dict]] = None
-    trace_id: Optional[str] = None
-    step_id: Optional[str] = None
-    tokens: Optional[Dict[str, int]] = None
-    cost: float = 0.0
-
-
 class AgentRunner:
     """
     Agent 执行引擎
@@ -233,7 +213,7 @@ class AgentRunner:
         enable_memory: Optional[bool] = None,
         auto_execute_tools: Optional[bool] = None,
         **kwargs
-    ) -> AsyncIterator[AgentEvent]:
+    ) -> AsyncIterator[Union[Trace, Step]]:
         """
         Agent 模式执行
 
@@ -251,7 +231,7 @@ class AgentRunner:
             **kwargs: 其他参数
 
         Yields:
-            AgentEvent
+            Union[Trace, Step]: Trace 对象(状态变化)或 Step 对象(执行过程)
         """
         if not self.llm_call:
             raise ValueError("llm_call function not provided")
@@ -264,6 +244,7 @@ class AgentRunner:
 
         # 创建 Trace
         trace_id = self._generate_id()
+        trace_obj = None
         if self.trace_store:
             trace_obj = Trace(
                 trace_id=trace_id,
@@ -274,12 +255,8 @@ class AgentRunner:
                 context={"model": model, **kwargs}
             )
             await self.trace_store.create_trace(trace_obj)
-
-        yield AgentEvent("trace_started", {
-            "trace_id": trace_id,
-            "task": task,
-            "agent_type": agent_type
-        })
+            # 返回 Trace(表示开始)
+            yield trace_obj
 
         try:
             # 加载记忆(Experience 和 Skill)
@@ -306,17 +283,19 @@ class AgentRunner:
                     )
                     await self.trace_store.add_step(mem_step)
                     await self._dump_debug(trace_id)
-
-                yield AgentEvent("memory_loaded", {
-                    "experiences_count": len(experiences)
-                })
-
-            # 加载 Skills(如果提供了 skills_dir)
-            if self.skills_dir:
-                skills = load_skills_from_dir(self.skills_dir)
-                if skills:
-                    skills_text = self._format_skills(skills)
-                    logger.info(f"加载 {len(skills)} 个 skills 从 {self.skills_dir}")
+                    # 返回 Step(表示记忆加载完成)
+                    yield mem_step
+
+            # 加载 Skills(内置 + 用户自定义)
+            # load_skills_from_dir() 会自动加载 agent/skills/ 中的内置 skills
+            # 如果提供了 skills_dir,会额外加载用户自定义的 skills
+            skills = load_skills_from_dir(self.skills_dir)
+            if skills:
+                skills_text = self._format_skills(skills)
+                if self.skills_dir:
+                    logger.info(f"加载 {len(skills)} 个 skills (内置 + 自定义: {self.skills_dir})")
+                else:
+                    logger.info(f"加载 {len(skills)} 个内置 skills")
 
             # 构建初始消息
             if messages is None:
@@ -353,11 +332,6 @@ class AgentRunner:
             total_cost = 0.0
 
             for iteration in range(max_iterations):
-                yield AgentEvent("step_started", {
-                    "iteration": iteration,
-                    "step_type": "thought"
-                })
-
                 # 调用 LLM
                 result = await self.llm_call(
                     messages=messages,
@@ -376,6 +350,7 @@ class AgentRunner:
 
                 # 记录 LLM 调用 Step
                 llm_step_id = self._generate_id()
+                llm_step = None
                 if self.trace_store:
                     # 推断 step_type
                     step_type = "thought"
@@ -404,26 +379,33 @@ class AgentRunner:
                     )
                     await self.trace_store.add_step(llm_step)
                     await self._dump_debug(trace_id)
+                    # 返回 Step(LLM 思考完成)
+                    yield llm_step
 
                 sequence += 1
 
-                yield AgentEvent("llm_call_completed", {
-                    "step_id": llm_step_id,
-                    "content": response_content,
-                    "tool_calls": tool_calls,
-                    "tokens": step_tokens,
-                    "cost": step_cost
-                })
-
                 # 处理工具调用
                 if tool_calls and auto_execute_tools:
                     # 检查是否需要用户确认
                     if self.tools.check_confirmation_required(tool_calls):
-                        yield AgentEvent("awaiting_user_action", {
-                            "tool_calls": tool_calls,
-                            "confirmation_flags": self.tools.get_confirmation_flags(tool_calls),
-                            "editable_params": self.tools.get_editable_params_map(tool_calls)
-                        })
+                        # 创建等待确认的 Step
+                        await_step = Step.create(
+                            trace_id=trace_id,
+                            step_type="action",
+                            status="awaiting_approval",
+                            sequence=sequence,
+                            parent_id=llm_step_id,
+                            description="等待用户确认工具调用",
+                            data={
+                                "tool_calls": tool_calls,
+                                "confirmation_flags": self.tools.get_confirmation_flags(tool_calls),
+                                "editable_params": self.tools.get_editable_params_map(tool_calls)
+                            }
+                        )
+                        if self.trace_store:
+                            await self.trace_store.add_step(await_step)
+                            await self._dump_debug(trace_id)
+                        yield await_step
                         # TODO: 等待用户确认
                         break
 
@@ -437,11 +419,6 @@ class AgentRunner:
                             import json
                             tool_args = json.loads(tool_args)
 
-                        yield AgentEvent("tool_executing", {
-                            "tool_name": tool_name,
-                            "arguments": tool_args
-                        })
-
                         # 执行工具
                         tool_result = await self.tools.execute(
                             tool_name,
@@ -451,6 +428,7 @@ class AgentRunner:
 
                         # 记录 action Step
                         action_step_id = self._generate_id()
+                        action_step = None
                         if self.trace_store:
                             action_step = Step(
                                 step_id=action_step_id,
@@ -467,11 +445,14 @@ class AgentRunner:
                             )
                             await self.trace_store.add_step(action_step)
                             await self._dump_debug(trace_id)
+                            # 返回 Step(工具调用)
+                            yield action_step
 
                         sequence += 1
 
                         # 记录 result Step
                         result_step_id = self._generate_id()
+                        result_step = None
                         if self.trace_store:
                             result_step = Step(
                                 step_id=result_step_id,
@@ -488,15 +469,11 @@ class AgentRunner:
                             )
                             await self.trace_store.add_step(result_step)
                             await self._dump_debug(trace_id)
+                            # 返回 Step(工具结果)
+                            yield result_step
 
                         sequence += 1
 
-                        yield AgentEvent("tool_result", {
-                            "step_id": result_step_id,
-                            "tool_name": tool_name,
-                            "result": tool_result
-                        })
-
                         # 添加到消息(Gemini 需要 name 字段!)
                         messages.append({
                             "role": "tool",
@@ -510,6 +487,7 @@ class AgentRunner:
                 # 无工具调用,任务完成
                 # 记录 response Step
                 response_step_id = self._generate_id()
+                response_step = None
                 if self.trace_store:
                     response_step = Step(
                         step_id=response_step_id,
@@ -526,12 +504,9 @@ class AgentRunner:
                     )
                     await self.trace_store.add_step(response_step)
                     await self._dump_debug(trace_id)
+                    # 返回 Step(最终回复)
+                    yield response_step
 
-                yield AgentEvent("conclusion", {
-                    "step_id": response_step_id,
-                    "content": response_content,
-                    "is_final": True
-                })
                 break
 
             # 完成 Trace
@@ -543,12 +518,10 @@ class AgentRunner:
                     total_tokens=total_tokens,
                     total_cost=total_cost
                 )
-
-            yield AgentEvent("trace_completed", {
-                "trace_id": trace_id,
-                "total_tokens": total_tokens,
-                "total_cost": total_cost
-            })
+                # 重新获取更新后的 Trace 并返回
+                trace_obj = await self.trace_store.get_trace(trace_id)
+                if trace_obj:
+                    yield trace_obj
 
         except Exception as e:
             logger.error(f"Agent run failed: {e}")
@@ -559,11 +532,10 @@ class AgentRunner:
                     status="failed",
                     completed_at=datetime.now()
                 )
-
-            yield AgentEvent("trace_failed", {
-                "trace_id": trace_id,
-                "error": str(e)
-            })
+                # 重新获取更新后的 Trace 并返回
+                trace_obj = await self.trace_store.get_trace(trace_id)
+                if trace_obj:
+                    yield trace_obj
             raise
 
     # ===== 反馈 =====

+ 0 - 9
agent/debug/__init__.py

@@ -1,9 +0,0 @@
-"""
-Debug 工具模块
-
-提供 Step 树的实时查看功能,用于开发调试。
-"""
-
-from .tree_dump import StepTreeDumper, dump_tree, dump_markdown, dump_json
-
-__all__ = ["StepTreeDumper", "dump_tree", "dump_markdown", "dump_json"]

+ 0 - 45
agent/events.py

@@ -1,45 +0,0 @@
-"""
-Agent 事件定义
-"""
-
-from dataclasses import dataclass
-from typing import Dict, Any, Literal
-
-
-AgentEventType = Literal[
-    # Trace 生命周期
-    "trace_started",        # Trace 开始
-    "trace_completed",      # Trace 完成
-    "trace_failed",         # Trace 失败
-
-    # 记忆
-    "memory_loaded",        # 记忆加载完成(skills, experiences)
-    "experience_extracted", # 提取了经验
-
-    # 步骤
-    "step_started",         # 步骤开始
-    "llm_delta",            # LLM 输出增量
-    "llm_call_completed",   # LLM 调用完成
-    "tool_executing",       # 工具执行中
-    "tool_result",          # 工具结果
-    "conclusion",           # 结论(中间或最终)
-
-    # 反馈
-    "feedback_received",    # 收到人工反馈
-
-    # 等待用户
-    "awaiting_user_action", # 等待用户确认(工具调用)
-]
-
-
-@dataclass
-class AgentEvent:
-    """Agent 事件"""
-    type: AgentEventType
-    data: Dict[str, Any]
-
-    def to_dict(self) -> Dict[str, Any]:
-        return {"type": self.type, "data": self.data}
-
-    def __repr__(self) -> str:
-        return f"AgentEvent(type={self.type!r}, data={self.data!r})"

+ 71 - 0
agent/execution/README.md

@@ -0,0 +1,71 @@
+# Agent Execution - 执行追踪系统
+
+## 职责
+
+执行追踪系统负责记录和可视化 Agent 的执行过程:
+
+1. **数据模型** (`models.py`)
+   - `Trace` - 一次完整的执行轨迹
+   - `Step` - 执行过程中的一个原子操作(形成树结构)
+   - `StepType` - 步骤类型(思考、动作、结果等)
+   - `Status` - 步骤状态(计划中、执行中、完成、失败)
+
+2. **存储接口** (`protocols.py`, `store.py`)
+   - `TraceStore` - Trace 存储接口协议
+   - `MemoryTraceStore` - 内存实现(用于测试)
+
+3. **可视化工具** (`tree_dump.py`)
+   - `dump_tree()` - 输出文本格式的 step tree
+   - `dump_markdown()` - 输出 markdown 格式(可折叠)
+   - `dump_json()` - 输出 JSON 格式
+
+4. **API 和实时推送** (`api.py`, `websocket.py`)
+   - RESTful API - 查询 Trace 和 Step
+   - WebSocket - 实时推送执行更新
+
+## 模块边界
+
+- **只依赖**:core(事件系统)
+- **被依赖**:core.runner(记录 trace)
+- **独立开发**:可视化、API、WebSocket 可独立迭代
+
+## 使用示例
+
+```python
+from agent.execution import MemoryTraceStore, Trace, Step, dump_tree
+
+# 创建存储
+store = MemoryTraceStore()
+
+# 创建 Trace
+trace = Trace.create(mode="agent", task="Complete task")
+trace_id = await store.create_trace(trace)
+
+# 添加 Step
+step = Step.create(
+    trace_id=trace_id,
+    step_type="thought",
+    description="Analyzing task"
+)
+await store.add_step(step)
+
+# 可视化
+trace = await store.get_trace(trace_id)
+steps = await store.get_trace_steps(trace_id)
+dump_tree(trace, steps)
+```
+
+## 文件说明
+
+- `models.py` - Trace 和 Step 数据模型
+- `protocols.py` - TraceStore 接口定义
+- `store.py` - MemoryTraceStore 实现
+- `tree_dump.py` - Step tree 可视化工具
+- `api.py` - RESTful API(可选,需要 FastAPI)
+- `websocket.py` - WebSocket 推送(可选,需要 FastAPI)
+- `__init__.py` - 模块导出
+
+## 适合分工
+
+- **算法同事**:负责 `tree_dump.py` 可视化优化
+- **后端同事**:负责 `api.py` 和 `websocket.py` 开发

+ 18 - 10
agent/trace/__init__.py → agent/execution/__init__.py

@@ -1,40 +1,44 @@
 """
-Trace 模块 - Context 管理 + 可视化
+Execution - 执行追踪系统
 
 核心职责:
 1. Trace/Step 模型定义
 2. 存储接口和实现(内存/数据库)
-3. RESTful API(可视化查询)
-4. WebSocket 推送(实时更新)
+3. Step 树可视化(文本/markdown/JSON)
+4. RESTful API(可视化查询)
+5. WebSocket 推送(实时更新)
 """
 
 # 模型(核心,无依赖)
-from agent.trace.models import Trace, Step, StepType, Status
+from agent.execution.models import Trace, Step, StepType, Status
 
 # 存储接口(核心,无依赖)
-from agent.trace.protocols import TraceStore
+from agent.execution.protocols import TraceStore
 
 # 内存存储实现(核心,无依赖)
-from agent.trace.memory_store import MemoryTraceStore
+from agent.execution.store import MemoryTraceStore
+
+# Debug 工具(可视化)
+from agent.execution.tree_dump import StepTreeDumper, dump_tree, dump_markdown, dump_json
 
 
 # API 路由(可选,需要 FastAPI)
 def _get_api_router():
     """延迟导入 API Router(避免强制依赖 FastAPI)"""
-    from agent.trace.api import router
+    from agent.execution.api import router
     return router
 
 
 def _get_ws_router():
     """延迟导入 WebSocket Router(避免强制依赖 FastAPI)"""
-    from agent.trace.websocket import router
+    from agent.execution.websocket import router
     return router
 
 
 # WebSocket 广播函数(可选,需要 FastAPI)
 def _get_broadcast_functions():
     """延迟导入 WebSocket 广播函数"""
-    from agent.trace.websocket import (
+    from agent.execution.websocket import (
         broadcast_step_added,
         broadcast_step_updated,
         broadcast_trace_completed,
@@ -62,5 +66,9 @@ __all__ = [
     # 存储
     "TraceStore",
     "MemoryTraceStore",
+    # Debug/可视化
+    "StepTreeDumper",
+    "dump_tree",
+    "dump_markdown",
+    "dump_json",
 ]
-

+ 1 - 1
agent/trace/api.py → agent/execution/api.py

@@ -8,7 +8,7 @@ from typing import List, Optional, Dict, Any
 from fastapi import APIRouter, HTTPException, Query
 from pydantic import BaseModel
 
-from agent.trace.protocols import TraceStore
+from agent.execution.protocols import TraceStore
 
 
 router = APIRouter(prefix="/api/traces", tags=["traces"])

+ 6 - 5
agent/trace/models.py → agent/execution/models.py

@@ -34,11 +34,12 @@ StepType = Literal[
 
 # Step 状态
 Status = Literal[
-    "planned",      # 计划中(未执行)
-    "in_progress",  # 执行中
-    "completed",    # 已完成
-    "failed",       # 失败
-    "skipped",      # 跳过
+    "planned",           # 计划中(未执行)
+    "in_progress",       # 执行中
+    "awaiting_approval", # 等待用户确认
+    "completed",         # 已完成
+    "failed",            # 失败
+    "skipped",           # 跳过
 ]
 
 

+ 1 - 1
agent/trace/protocols.py → agent/execution/protocols.py

@@ -6,7 +6,7 @@ Trace Storage Protocol - Trace 存储接口定义
 
 from typing import Protocol, List, Optional, runtime_checkable
 
-from agent.trace.models import Trace, Step
+from agent.execution.models import Trace, Step
 
 
 @runtime_checkable

+ 1 - 1
agent/trace/memory_store.py → agent/execution/store.py

@@ -6,7 +6,7 @@ Memory Trace Store - 内存存储实现
 
 from typing import Dict, List, Optional
 
-from agent.trace.models import Trace, Step
+from agent.execution.models import Trace, Step
 
 
 class MemoryTraceStore:

+ 1 - 1
agent/debug/tree_dump.py → agent/execution/tree_dump.py

@@ -11,7 +11,7 @@ Step 树 Debug 输出
        code .trace/tree.txt
 
     3. 代码中使用:
-       from agent.debug import dump_tree
+       from agent.execution import dump_tree
        dump_tree(trace, steps)
 """
 

+ 1 - 1
agent/trace/websocket.py → agent/execution/websocket.py

@@ -7,7 +7,7 @@ Step 树 WebSocket 推送
 from typing import Dict, Set
 from fastapi import APIRouter, WebSocket, WebSocketDisconnect
 
-from agent.trace.protocols import TraceStore
+from agent.execution.protocols import TraceStore
 
 
 router = APIRouter(prefix="/api/traces", tags=["websocket"])

+ 5 - 6
agent/llm/__init__.py

@@ -1,11 +1,10 @@
 """
-LLM Provider 封装
+LLM Providers
 
-提供统一的接口支持多种 LLM provider
+各个 LLM 提供商的适配器
 """
 
-from agent.llm.providers.gemini import create_gemini_llm_call
+from .gemini import create_gemini_llm_call
+from .openrouter import create_openrouter_llm_call
 
-__all__ = [
-    "create_gemini_llm_call",
-]
+__all__ = ["create_gemini_llm_call", "create_openrouter_llm_call"]

+ 0 - 0
agent/llm/providers/gemini.py → agent/llm/gemini.py


+ 0 - 0
agent/llm/providers/openrouter.py → agent/llm/openrouter.py


+ 0 - 0
agent/prompts/__init__.py → agent/llm/prompts/__init__.py


+ 0 - 0
agent/prompts/loader.py → agent/llm/prompts/loader.py


+ 1 - 1
agent/prompts/wrapper.py → agent/llm/prompts/wrapper.py

@@ -7,7 +7,7 @@ Prompt Wrapper - 为 .prompt 文件提供 Prompt 实现
 import base64
 from pathlib import Path
 from typing import List, Dict, Any, Union, Optional
-from agent.prompts.loader import load_prompt, get_message
+from agent.llm.prompts.loader import load_prompt, get_message
 
 
 class SimplePrompt:

+ 0 - 10
agent/llm/providers/__init__.py

@@ -1,10 +0,0 @@
-"""
-LLM Providers
-
-各个 LLM 提供商的适配器
-"""
-
-from .gemini import create_gemini_llm_call
-from .openrouter import create_openrouter_llm_call
-
-__all__ = ["create_gemini_llm_call", "create_openrouter_llm_call"]

+ 91 - 0
agent/memory/README.md

@@ -0,0 +1,91 @@
+# Agent Memory - 记忆系统
+
+## 职责
+
+记忆系统管理 Agent 的长期记忆和技能:
+
+1. **数据模型** (`models.py`)
+   - `Experience` - 经验记录(从执行中提取的知识)
+   - `Skill` - 技能定义(领域知识和最佳实践)
+
+2. **存储接口** (`protocols.py`, `stores.py`)
+   - `MemoryStore` - 经验和技能存储接口
+   - `StateStore` - 状态存储接口
+   - `MemoryMemoryStore` - 内存实现
+   - `MemoryStateStore` - 内存实现
+
+3. **技能加载** (`skill_loader.py`)
+   - 从 Markdown 文件加载技能
+   - 支持 YAML frontmatter 和行内元数据
+
+## 模块边界
+
+- **只依赖**:无(纯数据模型和接口)
+- **被依赖**:core.runner(加载和注入记忆)
+- **独立开发**:记忆检索、经验提取算法可独立迭代
+
+## 使用示例
+
+```python
+from agent.memory import (
+    MemoryMemoryStore,
+    Experience,
+    Skill,
+    load_skills_from_dir
+)
+
+# 创建存储
+memory_store = MemoryMemoryStore()
+
+# 添加经验
+exp = Experience.create(
+    scope="coding",
+    context="Python type hints",
+    pattern="Use Optional[T] for nullable types",
+    outcome="success"
+)
+await memory_store.add_experience(exp)
+
+# 加载技能
+skills = load_skills_from_dir("./config/skills")
+for skill in skills:
+    await memory_store.add_skill(skill)
+
+# 检索记忆
+experiences = await memory_store.search_experiences(
+    scope="coding",
+    context="type hints",
+    limit=5
+)
+```
+
+## 技能文件格式
+
+技能文件使用 Markdown + YAML frontmatter:
+
+```markdown
+---
+name: browser-automation
+description: Browser automation best practices
+category: web-automation
+scope: agent:*
+---
+
+## When to use
+
+- Scraping dynamic web pages
+- Testing web applications
+
+## Guidelines
+
+- Always check if element exists before clicking
+- Use explicit waits instead of sleep()
+```
+
+## 文件说明
+
+- `models.py` - Experience 和 Skill 数据模型
+- `protocols.py` - MemoryStore 和 StateStore 接口
+- `stores.py` - 内存存储实现
+- `skill_loader.py` - 技能加载器
+- `__init__.py` - 模块导出

+ 37 - 0
agent/memory/__init__.py

@@ -0,0 +1,37 @@
+"""
+Memory - 记忆系统
+
+核心职责:
+1. Experience 和 Skill 数据模型
+2. MemoryStore 和 StateStore 接口定义
+3. 内存存储实现(MemoryMemoryStore, MemoryStateStore)
+4. Skill 加载器(从 markdown 加载技能)
+"""
+
+# 数据模型
+from agent.memory.models import Experience, Skill
+
+# 存储接口
+from agent.memory.protocols import MemoryStore, StateStore
+
+# 内存存储实现
+from agent.memory.stores import MemoryMemoryStore, MemoryStateStore
+
+# Skill 加载器
+from agent.memory.skill_loader import SkillLoader, load_skills_from_dir
+
+
+__all__ = [
+    # 模型
+    "Experience",
+    "Skill",
+    # 存储接口
+    "MemoryStore",
+    "StateStore",
+    # 存储实现
+    "MemoryMemoryStore",
+    "MemoryStateStore",
+    # Skill 加载
+    "SkillLoader",
+    "load_skills_from_dir",
+]

+ 0 - 0
agent/models/memory.py → agent/memory/models.py


+ 2 - 2
agent/storage/protocols.py → agent/memory/protocols.py

@@ -3,12 +3,12 @@ Storage Protocols - 存储接口定义
 
 使用 Protocol 定义接口,允许不同的存储实现(内存、PostgreSQL、Neo4j 等)
 
-TraceStore 已移动到 agent.trace.protocols
+TraceStore 已移动到 agent.execution.protocols
 """
 
 from typing import Protocol, List, Optional, Dict, Any, runtime_checkable
 
-from agent.models.memory import Experience, Skill
+from agent.memory.models import Experience, Skill
 
 
 @runtime_checkable

+ 27 - 6
agent/storage/skill_loader.py → agent/memory/skill_loader.py

@@ -39,7 +39,7 @@ from pathlib import Path
 from typing import List, Dict, Optional
 import logging
 
-from agent.models.memory import Skill
+from agent.memory.models import Skill
 
 logger = logging.getLogger(__name__)
 
@@ -367,15 +367,36 @@ class SkillLoader:
 
 
 # 便捷函数
-def load_skills_from_dir(skills_dir: str) -> List[Skill]:
+def load_skills_from_dir(skills_dir: Optional[str] = None) -> List[Skill]:
     """
     从目录加载所有 Skills
 
+    加载优先级:
+    1. 始终加载内置 skills(agent/skills/)
+    2. 如果指定了 skills_dir,额外加载该目录的 skills
+
     Args:
-        skills_dir: skills 目录路径
+        skills_dir: 用户自定义 skills 目录路径(可选)
 
     Returns:
-        Skill 列表
+        Skill 列表(内置 + 自定义)
     """
-    loader = SkillLoader(skills_dir)
-    return loader.load_all()
+    all_skills = []
+
+    # 1. 加载内置 skills(agent/skills/)
+    builtin_skills_dir = Path(__file__).parent.parent / "skills"
+    if builtin_skills_dir.exists():
+        loader = SkillLoader(str(builtin_skills_dir))
+        builtin_skills = loader.load_all()
+        all_skills.extend(builtin_skills)
+        logger.info(f"加载了 {len(builtin_skills)} 个内置 skills")
+
+    # 2. 加载用户自定义 skills(如果提供)
+    if skills_dir:
+        loader = SkillLoader(skills_dir)
+        custom_skills = loader.load_all()
+        all_skills.extend(custom_skills)
+        logger.info(f"加载了 {len(custom_skills)} 个自定义 skills")
+
+    return all_skills
+

+ 2 - 2
agent/storage/memory_impl.py → agent/memory/stores.py

@@ -3,13 +3,13 @@ Memory Implementation - 内存存储实现
 
 用于测试和简单场景,数据不持久化
 
-MemoryTraceStore 已移动到 agent.trace.memory_store
+MemoryTraceStore 已移动到 agent.execution.store
 """
 
 from typing import Dict, List, Optional, Any
 from datetime import datetime
 
-from agent.models.memory import Experience, Skill
+from agent.memory.models import Experience, Skill
 
 
 class MemoryMemoryStore:

+ 0 - 9
agent/models/__init__.py

@@ -1,9 +0,0 @@
-"""
-Models 包 - 记忆相关模型
-
-Trace/Step 模型已移动到 agent.trace 模块
-"""
-
-from agent.models.memory import Experience, Skill
-
-__all__ = ["Experience", "Skill"]

+ 0 - 15
agent/skills/browser_use/__init__.py

@@ -1,15 +0,0 @@
-"""
-Browser-Use Skill
-
-包含 browser-use 的使用文档和环境配置工具
-"""
-
-from agent.skills.browser_use.setup import (
-    check_browser_use,
-    install_browser_use_chromium
-)
-
-__all__ = [
-    "check_browser_use",
-    "install_browser_use_chromium",
-]

+ 0 - 218
agent/skills/browser_use/browser-use.md

@@ -1,218 +0,0 @@
----
-name: browser-use
-description: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, or extract information from web pages.
-allowed-tools: Bash(browser-use:*)
----
-
-# Browser Automation with browser-use CLI
-
-The `browser-use` command provides fast, persistent browser automation. It maintains browser sessions across commands, enabling complex multi-step workflows.
-
-## Quick Start
-
-```bash
-browser-use open https://example.com           # Navigate to URL
-browser-use state                              # Get page elements with indices
-browser-use click 5                            # Click element by index
-browser-use type "Hello World"                 # Type text
-browser-use screenshot                         # Take screenshot
-browser-use close                              # Close browser
-```
-
-## Core Workflow
-
-1. **Navigate**: `browser-use open <url>` - Opens URL (starts browser if needed)
-2. **Inspect**: `browser-use state` - Returns clickable elements with indices
-3. **Interact**: Use indices from state to interact (`browser-use click 5`, `browser-use input 3 "text"`)
-4. **Verify**: `browser-use state` or `browser-use screenshot` to confirm actions
-5. **Repeat**: Browser stays open between commands
-
-## Browser Modes
-
-```bash
-browser-use --browser chromium open <url>      # Default: headless Chromium
-browser-use --browser chromium --headed open <url>  # Visible Chromium window
-browser-use --browser real open <url>          # User's Chrome with login sessions
-browser-use --browser remote open <url>        # Cloud browser (requires API key)
-```
-
-- **chromium**: Fast, isolated, headless by default
-- **real**: Uses your Chrome with cookies, extensions, logged-in sessions
-- **remote**: Cloud-hosted browser with proxy support (requires BROWSER_USE_API_KEY)
-
-## Commands
-
-### Navigation
-```bash
-browser-use open <url>                    # Navigate to URL
-browser-use back                          # Go back in history
-browser-use scroll down                   # Scroll down
-browser-use scroll up                     # Scroll up
-```
-
-### Page State
-```bash
-browser-use state                         # Get URL, title, and clickable elements
-browser-use screenshot                    # Take screenshot (outputs base64)
-browser-use screenshot path.png           # Save screenshot to file
-browser-use screenshot --full path.png    # Full page screenshot
-```
-
-### Interactions (use indices from `browser-use state`)
-```bash
-browser-use click <index>                 # Click element
-browser-use type "text"                   # Type text into focused element
-browser-use input <index> "text"          # Click element, then type text
-browser-use keys "Enter"                  # Send keyboard keys
-browser-use keys "Control+a"              # Send key combination
-browser-use select <index> "option"       # Select dropdown option
-```
-
-### Tab Management
-```bash
-browser-use switch <tab>                  # Switch to tab by index
-browser-use close-tab                     # Close current tab
-browser-use close-tab <tab>               # Close specific tab
-```
-
-### JavaScript & Data
-```bash
-browser-use eval "document.title"         # Execute JavaScript, return result
-browser-use extract "all product prices"  # Extract data using LLM (requires API key)
-```
-
-### Python Execution (Persistent Session)
-```bash
-browser-use python "x = 42"               # Set variable
-browser-use python "print(x)"             # Access variable (outputs: 42)
-browser-use python "print(browser.url)"   # Access browser object
-browser-use python --vars                 # Show defined variables
-browser-use python --reset                # Clear Python namespace
-browser-use python --file script.py       # Execute Python file
-```
-
-The Python session maintains state across commands. The `browser` object provides:
-- `browser.url` - Current page URL
-- `browser.title` - Page title
-- `browser.goto(url)` - Navigate
-- `browser.click(index)` - Click element
-- `browser.type(text)` - Type text
-- `browser.screenshot(path)` - Take screenshot
-- `browser.scroll()` - Scroll page
-- `browser.html` - Get page HTML
-
-### Agent Tasks (Requires API Key)
-```bash
-browser-use run "Fill the contact form with test data"    # Run AI agent
-browser-use run "Extract all product prices" --max-steps 50
-```
-
-Agent tasks use an LLM to autonomously complete complex browser tasks. Requires `BROWSER_USE_API_KEY` or configured LLM API key (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc).
-
-### Session Management
-```bash
-browser-use sessions                      # List active sessions
-browser-use close                         # Close current session
-browser-use close --all                   # Close all sessions
-```
-
-### Server Control
-```bash
-browser-use server status                 # Check if server is running
-browser-use server stop                   # Stop server
-browser-use server logs                   # View server logs
-```
-
-## Global Options
-
-| Option | Description |
-|--------|-------------|
-| `--session NAME` | Use named session (default: "default") |
-| `--browser MODE` | Browser mode: chromium, real, remote |
-| `--headed` | Show browser window (chromium mode) |
-| `--profile NAME` | Chrome profile (real mode only) |
-| `--json` | Output as JSON |
-| `--api-key KEY` | Override API key |
-
-**Session behavior**: All commands without `--session` use the same "default" session. The browser stays open and is reused across commands. Use `--session NAME` to run multiple browsers in parallel.
-
-## Examples
-
-### Form Submission
-```bash
-browser-use open https://example.com/contact
-browser-use state
-# Shows: [0] input "Name", [1] input "Email", [2] textarea "Message", [3] button "Submit"
-browser-use input 0 "John Doe"
-browser-use input 1 "john@example.com"
-browser-use input 2 "Hello, this is a test message."
-browser-use click 3
-browser-use state  # Verify success
-```
-
-### Multi-Session Workflows
-```bash
-browser-use --session work open https://work.example.com
-browser-use --session personal open https://personal.example.com
-browser-use --session work state    # Check work session
-browser-use --session personal state  # Check personal session
-browser-use close --all             # Close both sessions
-```
-
-### Data Extraction with Python
-```bash
-browser-use open https://example.com/products
-browser-use python "
-products = []
-for i in range(20):
-    browser.scroll('down')
-browser.screenshot('products.png')
-"
-browser-use python "print(f'Captured {len(products)} products')"
-```
-
-### Using Real Browser (Logged-In Sessions)
-```bash
-browser-use --browser real open https://gmail.com
-# Uses your actual Chrome with existing login sessions
-browser-use state  # Already logged in!
-```
-
-## Tips
-
-1. **Always run `browser-use state` first** to see available elements and their indices
-2. **Use `--headed` for debugging** to see what the browser is doing
-3. **Sessions persist** - the browser stays open between commands
-4. **Use `--json` for parsing** output programmatically
-5. **Python variables persist** across `browser-use python` commands within a session
-6. **Real browser mode** preserves your login sessions and extensions
-
-## Troubleshooting
-
-**Browser won't start?**
-```bash
-browser-use server stop               # Stop any stuck server
-browser-use --headed open <url>       # Try with visible window
-```
-
-**Element not found?**
-```bash
-browser-use state                     # Check current elements
-browser-use scroll down               # Element might be below fold
-browser-use state                     # Check again
-```
-
-**Session issues?**
-```bash
-browser-use sessions                  # Check active sessions
-browser-use close --all               # Clean slate
-browser-use open <url>                # Fresh start
-```
-
-## Cleanup
-
-**Always close the browser when done.** Run this after completing browser automation:
-
-```bash
-browser-use close
-```

+ 0 - 174
agent/skills/browser_use/setup.py

@@ -1,174 +0,0 @@
-"""
-Browser-Use 设置工具
-
-检查并安装 browser-use 的依赖(CLI 和 Chromium)
-"""
-
-import subprocess
-from pathlib import Path
-from typing import Optional
-
-from agent.tools import tool, ToolResult
-
-
-def _check_browser_use_cli() -> bool:
-    """检查 browser-use CLI 是否已安装"""
-    try:
-        result = subprocess.run(
-            ["browser-use", "--help"],
-            capture_output=True,
-            timeout=5
-        )
-        return result.returncode == 0
-    except (FileNotFoundError, subprocess.TimeoutExpired):
-        return False
-
-
-def _check_chromium_installed() -> bool:
-    """检查 Chromium 是否已安装(Playwright 缓存)"""
-    # macOS
-    playwright_cache = Path.home() / "Library" / "Caches" / "ms-playwright"
-    if not playwright_cache.exists():
-        # Linux
-        playwright_cache = Path.home() / ".cache" / "ms-playwright"
-
-    if playwright_cache.exists():
-        chromium_dirs = list(playwright_cache.glob("chromium-*"))
-        return len(chromium_dirs) > 0
-
-    return False
-
-
-@tool(description="检查 browser-use 依赖并提供安装指导")
-async def check_browser_use(
-    uid: str = ""
-) -> ToolResult:
-    """
-    检查 browser-use 的依赖是否已安装
-
-    检查项:
-    1. browser-use CLI 是否可用
-    2. Chromium 浏览器是否已安装
-
-    Returns:
-        ToolResult: 检查结果和安装指导
-    """
-    cli_installed = _check_browser_use_cli()
-    chromium_installed = _check_chromium_installed()
-
-    output = "# Browser-Use Dependency Check\n\n"
-
-    # CLI 状态
-    output += "## 1. Browser-Use CLI\n\n"
-    if cli_installed:
-        output += "✅ **Installed** - `browser-use` command is available\n\n"
-    else:
-        output += "❌ **Not Installed**\n\n"
-        output += "Install with:\n```bash\npip install browser-use\n# or\nuv add browser-use && uv sync\n```\n\n"
-
-    # Chromium 状态
-    output += "## 2. Chromium Browser\n\n"
-    if chromium_installed:
-        output += "✅ **Installed** - Chromium is available in Playwright cache\n\n"
-    else:
-        output += "❌ **Not Installed**\n\n"
-        output += "Install with:\n```bash\nuvx browser-use install\n```\n\n"
-        output += "Installation location:\n"
-        output += "- macOS: `~/Library/Caches/ms-playwright/`\n"
-        output += "- Linux: `~/.cache/ms-playwright/`\n\n"
-
-    # 总结
-    if cli_installed and chromium_installed:
-        output += "## ✅ Ready to Use\n\n"
-        output += "All dependencies are installed. You can now use browser-use commands.\n\n"
-        output += "Test with: `browser-use --help`\n"
-        status = "ready"
-    else:
-        output += "## ⚠️ Setup Required\n\n"
-        output += "Please complete the installation steps above before using browser-use.\n"
-        status = "incomplete"
-
-    return ToolResult(
-        title="Browser-Use Dependency Check",
-        output=output,
-        metadata={
-            "cli_installed": cli_installed,
-            "chromium_installed": chromium_installed,
-            "status": status
-        }
-    )
-
-
-@tool(description="安装 browser-use 的 Chromium 浏览器")
-async def install_browser_use_chromium(
-    uid: str = ""
-) -> ToolResult:
-    """
-    安装 Chromium 浏览器(通过 uvx browser-use install)
-
-    注意:这会下载约 200-300MB 的 Chromium 浏览器
-
-    Returns:
-        ToolResult: 安装结果
-    """
-    # 先检查 CLI 是否可用
-    if not _check_browser_use_cli():
-        return ToolResult(
-            title="Browser-Use CLI Not Found",
-            output="Please install browser-use first:\n```bash\npip install browser-use\n```",
-            error="browser-use CLI not found"
-        )
-
-    # 检查是否已安装
-    if _check_chromium_installed():
-        return ToolResult(
-            title="Chromium Already Installed",
-            output="Chromium is already installed in the Playwright cache.\n\n"
-                   "Location: `~/Library/Caches/ms-playwright/` (macOS) or `~/.cache/ms-playwright/` (Linux)"
-        )
-
-    # 执行安装
-    output = "Installing Chromium browser...\n\n"
-    output += "This may take a few minutes (downloading ~200-300MB).\n\n"
-
-    try:
-        result = subprocess.run(
-            ["uvx", "browser-use", "install"],
-            capture_output=True,
-            text=True,
-            timeout=300  # 5 分钟超时
-        )
-
-        if result.returncode == 0:
-            output += "✅ Installation successful!\n\n"
-            output += result.stdout
-
-            return ToolResult(
-                title="Chromium Installed",
-                output=output,
-                metadata={"installed": True}
-            )
-        else:
-            output += f"❌ Installation failed\n\n"
-            output += f"Error: {result.stderr}\n"
-
-            return ToolResult(
-                title="Installation Failed",
-                output=output,
-                error=result.stderr
-            )
-
-    except subprocess.TimeoutExpired:
-        return ToolResult(
-            title="Installation Timeout",
-            output="Installation timed out after 5 minutes.\n"
-                   "Please try running manually:\n```bash\nuvx browser-use install\n```",
-            error="Timeout"
-        )
-    except Exception as e:
-        return ToolResult(
-            title="Installation Error",
-            output=f"Unexpected error: {str(e)}\n\n"
-                   "Please try running manually:\n```bash\nuvx browser-use install\n```",
-            error=str(e)
-        )

+ 16 - 25
agent/skills/core.md

@@ -1,7 +1,7 @@
 ---
 name: core
 type: core
-description: 核心系统能,自动加载到 System Prompt
+description: 核心系统能,自动加载到 System Prompt
 ---
 
 # Core Skills
@@ -10,29 +10,24 @@ description: 核心系统功能,自动加载到 System Prompt
 
 ---
 
-## Step 管理
+## 计划与执行
 
-你可以使用 `step` 工具来管理执行计划和进度。
+对于复杂任务,你要先分析需求,并使用 `step` 工具来管理执行计划和进度。这一工具会形成一棵思维树。
 
-### 何时使用
-
-- **复杂任务**(3 个以上步骤):先制定计划再执行
-- **简单任务**:直接执行,无需计划
-
-### 创建计划
-
-当任务复杂时,先制定计划:
+### 创建计划:拆分任务步骤,创建TODO
 
 ```
-step(plan=["探索代码库", "修改配置", "运行测试"])
+step(plan=["调研并确定方案", "执行方案", "评估结果"])
 ```
 
+这将在当前节点下增加3个plan节点。你可以在执行过程中设置进一步的sub plan。
+
 ### 开始执行
 
 聚焦到某个目标开始执行:
 
 ```
-step(focus="探索代码库")
+step(focus="调研并确定方案")
 ```
 
 ### 完成并切换
@@ -40,15 +35,7 @@ step(focus="探索代码库")
 完成当前目标,提供总结,切换到下一个:
 
 ```
-step(complete=True, summary="主配置在 /src/config.yaml,包含数据库连接配置", focus="修改配置")
-```
-
-### 调整计划
-
-执行中发现需要增加步骤:
-
-```
-step(plan=["备份原配置"])  # 追加新目标
+step(complete=True, summary="人物姿势的最佳提取工具是openpose", focus="执行方案")
 ```
 
 ### 查看进度
@@ -59,11 +46,15 @@ step(plan=["备份原配置"])  # 追加新目标
 read_progress()
 ```
 
----
-
-## 使用规范
+### 使用规范
 
 1. **同时只有一个目标处于执行中**:完成当前目标后再切换
 2. **summary 应简洁**:记录关键结论和发现,不要冗长
 3. **计划可调整**:根据执行情况追加或跳过目标
 4. **简单任务不需要计划**:单步操作直接执行即可
+
+## 信息调研
+
+你可以通过联网搜索工具`search_posts`获取来自Github、小红书、微信公众号、知乎等渠道的信息。
+
+调研过程可能需要多次搜索,比如基于搜索结果中获得的启发或信息启动新的搜索,直到得到令人满意的答案。你可以使用`step`工具管理搜索的过程,或者使用文档记录搜索的中间或最终结果。

+ 0 - 15
agent/storage/__init__.py

@@ -1,15 +0,0 @@
-"""
-Storage 包 - 存储接口和实现
-
-TraceStore 和 MemoryTraceStore 已移动到 agent.trace 模块
-"""
-
-from agent.storage.protocols import MemoryStore, StateStore
-from agent.storage.memory_impl import MemoryMemoryStore, MemoryStateStore
-
-__all__ = [
-    "MemoryStore",
-    "StateStore",
-    "MemoryMemoryStore",
-    "MemoryStateStore",
-]

+ 94 - 0
agent/tools/README.md

@@ -0,0 +1,94 @@
+# Agent Tools - 工具系统
+
+## 职责
+
+工具系统提供 Agent 可调用的各种能力:
+
+1. **工具注册** (`registry.py`)
+   - 工具装饰器 `@tool`
+   - 全局工具注册表
+   - 工具发现和调用
+
+2. **Schema 生成** (`schema.py`)
+   - 将 Python 函数转换为 OpenAI function calling schema
+   - 支持类型注解和文档字符串
+
+3. **工具模型** (`models.py`)
+   - `ToolResult` - 工具执行结果
+   - `ToolContext` - 工具执行上下文
+
+4. **内置工具** (`builtin/`)
+   - `read_file` - 读取文件
+   - `edit_file` - 编辑文件
+   - `write_file` - 写入文件
+   - `glob_files` - 文件搜索
+   - `grep_content` - 内容搜索
+   - `bash_command` - 执行 Shell 命令
+   - `skill` - 加载技能文档
+
+5. **高级工具** (`advanced/`)
+   - LSP - Language Server Protocol 集成
+   - WebFetch - 网页抓取
+
+6. **适配器** (`adapters/`)
+   - Browser-use - 浏览器自动化
+
+## 工具开发
+
+创建自定义工具:
+
+```python
+from agent.tools import tool, ToolResult
+
+@tool(description="Calculate sum of two numbers")
+async def add(a: int, b: int, uid: str = "") -> ToolResult:
+    """
+    Add two numbers
+
+    Args:
+        a: First number
+        b: Second number
+        uid: User ID (auto-injected)
+
+    Returns:
+        ToolResult with sum
+    """
+    return ToolResult(
+        success=True,
+        data={"sum": a + b}
+    )
+```
+
+## 文件说明
+
+- `registry.py` - 工具注册表和装饰器
+- `schema.py` - OpenAI schema 生成
+- `models.py` - ToolResult 等数据模型
+- `sensitive.py` - 敏感数据处理
+- `url_matcher.py` - URL 模式匹配
+- `builtin/` - 核心工具
+- `advanced/` - 高级工具
+- `adapters/` - 外部集成
+
+## 目录结构
+
+```
+tools/
+├── __init__.py          # 工具装饰器和注册表
+├── registry.py          # 核心注册逻辑
+├── schema.py            # Schema 生成
+├── models.py            # 数据模型
+├── builtin/             # 核心工具(必需)
+│   ├── read.py
+│   ├── edit.py
+│   ├── write.py
+│   ├── glob.py
+│   ├── grep.py
+│   ├── bash.py
+│   └── skill.py
+├── advanced/            # 高级工具(可选)
+│   ├── lsp.py
+│   └── webfetch.py
+└── adapters/            # 外部集成(可选)
+    └── browser_use/
+```

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

@@ -14,6 +14,7 @@ from agent.tools.builtin.glob import glob_files
 from agent.tools.builtin.grep import grep_content
 from agent.tools.builtin.bash import bash_command
 from agent.tools.builtin.skill import skill, list_skills
+from agent.tools.builtin.search import search_posts, get_search_suggestions
 
 __all__ = [
     "read_file",
@@ -24,4 +25,6 @@ __all__ = [
     "bash_command",
     "skill",
     "list_skills",
+    "search_posts",
+    "get_search_suggestions",
 ]

+ 1 - 2
agent/tools/builtin/search.py

@@ -14,8 +14,7 @@ from typing import Any, Dict
 
 import httpx
 
-from agent import tool
-from agent.tools.models import ToolResult
+from agent.tools import tool, ToolResult
 
 
 # API 基础配置

+ 3 - 3
agent/tools/builtin/skill.py

@@ -10,12 +10,12 @@ from pathlib import Path
 from typing import Optional
 
 from agent.tools import tool, ToolResult
-from agent.storage.skill_loader import SkillLoader
+from agent.memory.skill_loader import SkillLoader
 
 # 默认 skills 目录(优先级:项目 skills > 框架 skills)
 DEFAULT_SKILLS_DIRS = [
     os.getenv("SKILLS_DIR", "./skills"),      # 项目特定 skills(优先)
-    "./agent/skills"                            # 框架内置 skills
+    "./agent/skills"                           # 框架内置 skills
 ]
 
 
@@ -83,7 +83,7 @@ async def skill(
 
     加载顺序:
     1. 如果指定 skills_dir,只在该目录查找
-    2. 否则按优先级查找:./skills/ (项目) -> ./agent/skills/ (框架)
+    2. 否则按优先级查找:./skills/ (项目) -> ./config/skills/ (框架)
     """
     # 确定要搜索的目录列表
     if skills_dir:

+ 108 - 50
docs/README.md

@@ -68,19 +68,25 @@
 ## 核心流程:Agent Loop
 
 ```python
-async def run(task: str, max_steps: int = 50):
+async def run(task: str, max_steps: int = 50) -> AsyncIterator[Union[Trace, Step]]:
     # 1. 创建 Trace
-    trace = Trace(trace_id=gen_id(), task=task, status="running")
-    await trace_store.save(trace)
+    trace = Trace.create(mode="agent", task=task, status="in_progress")
+    await trace_store.create_trace(trace)
+    yield trace  # 返回 Trace(表示开始)
 
-    # 2. 检索 Experiences,构建 system prompt
+    # 2. 加载 Skills(内置 + 自定义)
+    # 内置 skills(agent/skills/core.md)自动加载
+    skills = load_skills_from_dir(skills_dir)  # skills_dir 可选
+    skills_text = format_skills(skills)
+
+    # 3. 检索 Experiences,构建 system prompt
     experiences = await search_experiences(task)
-    system_prompt = build_system_prompt(experiences)
+    system_prompt = build_system_prompt(experiences, skills_text)
 
-    # 3. 初始化消息
+    # 4. 初始化消息
     messages = [{"role": "user", "content": task}]
 
-    # 4. ReAct 循环
+    # 5. ReAct 循环
     for step in range(max_steps):
         # 调用 LLM
         response = await llm.chat(
@@ -89,11 +95,15 @@ async def run(task: str, max_steps: int = 50):
             tools=tool_registry.to_schema()  # 包括 skill、task 等工具
         )
 
-        # 记录 LLM 调用
-        await add_step(trace, "llm_call", {
-            "response": response.content,
-            "tool_calls": response.tool_calls
-        })
+        # 记录 LLM 调用 Step
+        llm_step = Step.create(
+            trace_id=trace.trace_id,
+            step_type="thought",
+            status="completed",
+            data={"content": response.content, "tool_calls": response.tool_calls}
+        )
+        await trace_store.add_step(llm_step)
+        yield llm_step  # 返回 Step
 
         # 没有工具调用,完成
         if not response.tool_calls:
@@ -108,23 +118,44 @@ async def run(task: str, max_steps: int = 50):
             # 执行工具(包括 skill、task 工具)
             result = await execute_tool(tool_call)
 
-            # 记录步骤
-            await add_step(trace, "tool_call", {"tool": tool_call.name, "args": tool_call.args})
-            await add_step(trace, "tool_result", {"output": result})
+            # 记录 action Step
+            action_step = Step.create(
+                trace_id=trace.trace_id,
+                step_type="action",
+                status="completed",
+                parent_id=llm_step.step_id,
+                data={"tool_name": tool_call.name, "arguments": tool_call.args}
+            )
+            await trace_store.add_step(action_step)
+            yield action_step
+
+            # 记录 result Step
+            result_step = Step.create(
+                trace_id=trace.trace_id,
+                step_type="result",
+                status="completed",
+                parent_id=action_step.step_id,
+                data={"output": result}
+            )
+            await trace_store.add_step(result_step)
+            yield result_step
 
             # 添加到消息历史
             messages.append({"role": "assistant", "tool_calls": [tool_call]})
             messages.append({"role": "tool", "content": result})
 
-    # 5. 完成
+    # 6. 完成
     trace.status = "completed"
-    await trace_store.save(trace)
+    await trace_store.update_trace(trace.trace_id, status="completed")
+    yield trace  # 返回更新后的 Trace
 
     return trace
 ```
 
 **关键机制**:
+- **统一返回类型**:`AsyncIterator[Union[Trace, Step]]` - 实时返回执行状态
 - **Doom Loop 检测**:跟踪最近 3 次工具调用,如果都是同一个工具且参数相同,中断循环
+- **Skills 自动加载**:`agent/skills/core.md` 总是自动加载,`skills_dir` 可选额外加载
 - **动态工具加载**:Skill 通过 tool 动态加载,按需消耗 context
 - **Sub-Agent 支持**:通过 task 工具启动专门化的 Sub-Agent 处理子任务
 
@@ -176,7 +207,7 @@ class Trace:
     context: Dict[str, Any] = field(default_factory=dict)
 ```
 
-**实现**:`agent/models/trace.py:Trace`
+**实现**:`agent/execution/models.py:Trace`
 
 ### Step(执行步骤)
 
@@ -193,7 +224,7 @@ class Step:
     summary: Optional[str] = None    # 仅 evaluation 类型需要
 ```
 
-**实现**:`agent/models/trace.py:Step`
+**实现**:`agent/execution/models.py:Step`
 
 **详细设计**:参考 [`docs/step-tree.md`](./step-tree.md)
 
@@ -274,8 +305,8 @@ async def my_tool(arg: str, ctx: ToolContext) -> ToolResult:
 - 图片资源处理
 
 **实现**:
-- `agent/prompts/wrapper.py:SimplePrompt` - Prompt 包装器
-- `agent/llm/providers/gemini.py:_convert_messages_to_gemini` - 格式转换
+- `agent/llm/prompts/wrapper.py:SimplePrompt` - Prompt 包装器
+- `agent/llm/gemini.py:_convert_messages_to_gemini` - 格式转换
 
 **使用示例**:`examples/feature_extract/run.py`
 
@@ -304,9 +335,9 @@ $user$
 - 多模态消息支持(图片等)
 
 **实现**:
-- `agent/prompts/loader.py:load_prompt()` - 文件解析
-- `agent/prompts/loader.py:get_message()` - 参数替换
-- `agent/prompts/wrapper.py:SimplePrompt` - Prompt 包装器
+- `agent/llm/prompts/loader.py:load_prompt()` - 文件解析
+- `agent/llm/prompts/loader.py:get_message()` - 参数替换
+- `agent/llm/prompts/wrapper.py:SimplePrompt` - Prompt 包装器
 
 **使用**:
 ```python
@@ -347,7 +378,8 @@ await tools.execute("skill", {"skill_name": "browser-use"})
 ```
 
 **实现**:
-- `agent/storage/skill_loader.py:SkillLoader` - Markdown 解析器
+- `agent/memory/skill_loader.py:SkillLoader` - Markdown 解析器
+- `agent/memory/skill_loader.py:load_skills_from_dir()` - Skills 自动加载(内置 + 自定义)
 - `agent/tools/builtin/skill.py:skill()` - skill 工具实现
 - `agent/tools/builtin/skill.py:list_skills()` - 列出可用 skills
 
@@ -400,7 +432,7 @@ system_prompt = base_prompt + "\n\n# Learned Experiences\n" + "\n".join([
 ])
 ```
 
-**实现**:`agent/storage/experience_pg.py:ExperienceStore`
+**实现**:`agent/memory/stores.py:ExperienceStore`(待实现 PostgreSQL 版本)
 
 ---
 
@@ -426,7 +458,9 @@ class SkillLoader(Protocol):
         """加载指定 skill 的 Markdown 内容"""
 ```
 
-**实现**:`agent/storage/protocols.py`
+**实现**:
+- Trace/Step 协议:`agent/execution/protocols.py`
+- Memory 协议:`agent/memory/protocols.py`
 
 **实现策略**:
 - Trace/Step: 文件系统(JSON)
@@ -435,27 +469,51 @@ class SkillLoader(Protocol):
 
 ---
 
-## 模块结构
+## 模块结构(v0.2.2)
 
 ```
 agent/
-├── __init__.py
-├── runner.py              # AgentRunner
-├── models/
-│   ├── trace.py           # Trace, Step
-│   └── memory.py          # Experience, Skill
-├── storage/
-│   ├── protocols.py       # TraceStore, ExperienceStore, SkillLoader
-│   ├── trace_fs.py        # 文件系统实现
-│   ├── experience_pg.py   # PostgreSQL 实现
-│   └── skill_fs.py        # 文件系统实现
-├── tools/
+├── __init__.py            # 公开 API
+│
+├── core/                  # 核心引擎
+│   ├── runner.py          # AgentRunner
+│   └── config.py          # AgentConfig, CallResult
+│
+├── execution/             # 执行追踪
+│   ├── models.py          # Trace, Step
+│   ├── protocols.py       # TraceStore
+│   ├── store.py           # MemoryTraceStore
+│   ├── tree_dump.py       # 可视化
+│   ├── api.py             # RESTful API
+│   └── websocket.py       # WebSocket
+│
+├── memory/                # 记忆系统
+│   ├── models.py          # Experience, Skill
+│   ├── protocols.py       # MemoryStore, StateStore
+│   ├── stores.py          # 存储实现
+│   └── skill_loader.py    # Skill 加载器(自动加载内置 skills)
+│
+├── tools/                 # 工具系统
 │   ├── registry.py        # ToolRegistry
 │   ├── models.py          # ToolResult, ToolContext
 │   ├── schema.py          # SchemaGenerator
 │   ├── url_matcher.py     # URL 模式匹配
-│   └── sensitive.py       # 敏感数据处理
-└── llm.py                 # LLMProvider Protocol
+│   ├── sensitive.py       # 敏感数据处理
+│   ├── builtin/           # 核心工具
+│   ├── advanced/          # 高级工具
+│   └── adapters/          # 外部集成
+│
+├── llm/                   # LLM 相关
+│   ├── gemini.py
+│   ├── openrouter.py
+│   └── prompts/
+│       ├── loader.py
+│       └── wrapper.py
+│
+├── skills/                # 内置 Skills(自动加载)
+│   └── core.md            # 核心 skill,每次运行自动加载
+│
+└── subagents/             # Sub-agent
 ```
 
 ---
@@ -502,7 +560,7 @@ dump_tree(trace, steps)
 watch -n 0.5 cat .trace/tree.txt
 ```
 
-**实现**:`agent/debug/tree_dump.py`
+**实现**:`agent/execution/tree_dump.py`
 
 **详细说明**:参考 [`docs/step-tree.md`](./step-tree.md#debug-工具)
 
@@ -535,13 +593,13 @@ GEMINI_API_KEY=xxx pytest tests/e2e/ -v -m e2e
 
 | 概念 | 定义 | 存储 | 实现 |
 |------|------|------|------|
-| **Trace** | 一次任务执行 | 文件系统(JSON) | `models/trace.py` |
-| **Step** | 执行步骤(树结构) | 文件系统(JSON) | `models/trace.py` |
-| **Goal Step** | 计划项/目标 | Step 的一种类型 | `models/trace.py` |
+| **Trace** | 一次任务执行 | 文件系统(JSON) | `execution/models.py` |
+| **Step** | 执行步骤(树结构) | 文件系统(JSON) | `execution/models.py` |
+| **Goal Step** | 计划项/目标 | Step 的一种类型 | `execution/models.py` |
 | **Sub-Agent** | 专门化的子代理 | 独立 Trace | `tools/builtin/task.py` |
-| **AgentDefinition** | Agent 类型定义 | 配置文件/代码 | `models/agent.py` |
-| **Skill** | 能力描述(Markdown) | 文件系统 | `storage/skill_fs.py` |
-| **Experience** | 经验规则(条件+规则) | 数据库 + 向量 | `storage/experience_pg.py` |
+| **AgentDefinition** | Agent 类型定义 | 配置文件/代码 | `subagents/` |
+| **Skill** | 能力描述(Markdown) | 文件系统 | `memory/skill_loader.py` |
+| **Experience** | 经验规则(条件+规则) | 数据库 + 向量 | `memory/stores.py` |
 | **Tool** | 可调用的函数 | 内存(注册表) | `tools/registry.py` |
-| **Agent Loop** | ReAct 循环 | - | `runner.py` |
-| **Doom Loop** | 无限循环检测 | - | `runner.py` |
+| **Agent Loop** | ReAct 循环 | - | `core/runner.py` |
+| **Doom Loop** | 无限循环检测 | - | `core/runner.py` |

+ 2 - 2
docs/multimodal.md

@@ -21,7 +21,7 @@ Prompt 层 (SimplePrompt) → OpenAI 格式消息 → Provider 层适配 → 模
 
 ### 1. Prompt 层多模态支持
 
-**实现位置**:`agent/prompts/wrapper.py:SimplePrompt`
+**实现位置**:`agent/llm/prompts/wrapper.py:SimplePrompt`
 
 **功能**:构建 OpenAI 格式的多模态消息
 
@@ -54,7 +54,7 @@ messages = prompt.build_messages(
 
 ### 2. Gemini Provider 适配
 
-**实现位置**:`agent/llm/providers/gemini.py:_convert_messages_to_gemini`
+**实现位置**:`agent/llm/gemini.py:_convert_messages_to_gemini`
 
 **功能**:将 OpenAI 多模态格式转换为 Gemini 格式
 

+ 59 - 19
docs/project-structure.md

@@ -16,18 +16,41 @@
 
 ```
 agent/
-├── tools/builtin/              # 内置基础工具
-├── tools/advanced/             # 高级工具(第三方适配)
-├── skills/                     # 框架内置 Skills
+├── core/                       # 核心引擎
+│   ├── runner.py               # AgentRunner
+│   └── config.py               # AgentConfig, CallResult
+├── execution/                  # 执行追踪
+│   ├── models.py               # Trace, Step
+│   ├── protocols.py            # TraceStore
+│   ├── store.py                # MemoryTraceStore
+│   └── tree_dump.py            # 可视化
+├── memory/                     # 记忆系统
+│   ├── models.py               # Experience, Skill
+│   ├── protocols.py            # MemoryStore, StateStore
+│   ├── stores.py               # 存储实现
+│   └── skill_loader.py         # Skill 加载器(自动加载内置 skills)
+├── tools/                      # 工具系统
+│   ├── builtin/                # 内置基础工具
+│   ├── advanced/               # 高级工具(第三方适配)
+│   └── adapters/               # 外部集成
+├── llm/                        # LLM 相关
+│   ├── gemini.py
+│   ├── openrouter.py
+│   └── prompts/                # Prompt 工具
+├── skills/                     # 框架内置 Skills(自动加载)
+│   ├── core.md                 # 核心 skill,每次运行自动加载
 │   └── browser_use/            # 浏览器自动化 skill
-├── presets/subagents/          # 内置 Sub-Agent 定义
-└── runner.py                   # Agent 运行器
+└── subagents/                  # 内置 Sub-Agent 定义
 ```
 
 **实现**:
+- 核心引擎:`agent/core/runner.py`
+- 执行追踪:`agent/execution/`
+- 记忆系统:`agent/memory/`(Skills 自动加载)
 - 工具系统:`agent/tools/`
-- Skills:`agent/skills/browser_use/`(含文档和环境配置)
-- Sub-Agent 预设:`agent/subagents/default.json`
+- LLM 集成:`agent/llm/`
+- Skills:`agent/skills/core.md`(自动加载)+ `agent/skills/browser_use/`(按需加载)
+- Sub-Agent 预设:`agent/subagents/`
 
 ---
 
@@ -48,12 +71,15 @@ your-project/
 
 | 资源 | 框架预设 | 项目配置 | 优先级 |
 |------|---------|---------|--------|
-| Skills | `agent/skills/` | `./skills/` | 项目优先 |
-| Sub-Agents | `agent/subagents/default.json` | `./subagents/*.json` | 项目覆盖 |
+| Skills | `agent/skills/` | `./skills/` | 项目优先(`agent/skills/core.md` 总是自动加载) |
+| Sub-Agents | `agent/subagents/` | `./subagents/` | 项目覆盖 |
 | Tools | `agent/tools/builtin/` | `./tools/` | 手动注册 |
 
 **实现**:
-- Skills 多路径查找:`agent/tools/builtin/skill.py` - `DEFAULT_SKILLS_DIRS`
+- Skills 自动加载:`agent/memory/skill_loader.py:load_skills_from_dir()`
+  - 总是自动加载 `agent/skills/` 中的所有 skills(包括 `core.md`)
+  - 可选加载 `skills_dir` 参数指定的额外自定义 skills
+- Skills 工具动态加载:`agent/tools/builtin/skill.py` - 搜索路径 `./skills/` → `./agent/skills/`
 - Sub-Agents 加载:按顺序加载配置,后加载的覆盖先加载的
 
 ---
@@ -63,22 +89,36 @@ your-project/
 ### 加载框架预设
 
 ```python
-from agent.agent_registry import get_agent_registry
-
-registry = get_agent_registry()
-registry.load_from_config("agent/subagents/default.json")
+from agent import AgentRunner
+from agent.llm import create_gemini_llm_call
+
+# Skills 自动加载
+# - agent/skills/core.md 总是自动加载
+# - agent/skills/ 中的其他 skills 也会自动加载
+runner = AgentRunner(
+    llm_call=create_gemini_llm_call(),
+    # skills_dir 可选:加载额外的自定义 skills
+)
 ```
 
 ### 扩展框架(可选)
 
 ```python
-# 项目特定配置覆盖框架预设
-from pathlib import Path
-if Path("./subagents/custom.json").exists():
-    registry.load_from_config("./subagents/custom.json")
+# 加载项目特定的自定义 skills
+runner = AgentRunner(
+    llm_call=create_gemini_llm_call(),
+    skills_dir="./skills",  # 额外加载项目自定义 skills
+)
+
+# 结果:agent/skills/ + ./skills/ 中的所有 skills 都会被加载
 ```
 
-Skills 和工具自动按优先级加载。
+**Skills 加载机制**:
+- **自动加载**:`agent/skills/` 中的所有 skills(包括 `core.md`)总是自动加载
+- **可选加载**:通过 `skills_dir` 参数额外加载项目自定义 skills
+- **动态加载**:通过 `skill` 工具运行时按需加载特定 skill
+
+详见 [`SKILLS_SYSTEM.md`](../SKILLS_SYSTEM.md)
 
 ---
 

+ 101 - 20
docs/skills.md

@@ -9,7 +9,8 @@ Skills 是 Agent 的领域知识库,存储在 Markdown 文件中。
 | 类型 | 加载位置 | 加载时机 | 文件位置 |
 |------|---------|---------|---------|
 | **Core Skill** | System Prompt | Agent 启动时自动加载 | `agent/skills/core.md` |
-| **普通 Skill** | 对话消息 | 模型调用 `skill` 工具时 | `agent/skills/{name}/` |
+| **内置 Skill** | 对话消息 | 模型调用 `skill` 工具时 | `agent/skills/{name}/` |
+| **自定义 Skill** | 对话消息 | 模型调用 `skill` 工具时 | `./skills/{name}.md` |
 
 ### Core Skill
 
@@ -20,11 +21,13 @@ Skills 是 Agent 的领域知识库,存储在 Markdown 文件中。
 
 **位置**:`agent/skills/core.md`
 
-**加载方式**:框架自动注入到 System Prompt
+**加载方式**:
+- 框架自动注入到 System Prompt
+- `load_skills_from_dir()` 总是自动加载 `agent/skills/` 中的所有 skills(包括 `core.md`)
 
-### 普通 Skill
+### 内置 Skill
 
-特定领域能力,按需加载:
+框架提供的特定领域能力,按需加载:
 
 - browser_use(浏览器自动化)
 - 其他领域 skills
@@ -33,6 +36,16 @@ Skills 是 Agent 的领域知识库,存储在 Markdown 文件中。
 
 **加载方式**:模型调用 `skill` 工具
 
+### 自定义 Skill
+
+项目特定的能力,按需加载:
+
+**位置**:`./skills/{name}.md`
+
+**加载方式**:
+1. 通过 `skills_dir` 参数在 Agent 启动时加载到 System Prompt
+2. 通过 `skill` 工具运行时按需加载
+
 ---
 
 ## 普通 Skill 文件格式
@@ -79,37 +92,57 @@ mkdir skills
 
 ### 3. Agent 调用
 
-Agent 在运行时可以调用 skill 工具:
+**方式 1:自动加载到 System Prompt**
 
 ```python
 from agent import AgentRunner
-from agent.llm.providers.gemini import create_gemini_llm_call
+from agent.llm import create_gemini_llm_call
+import os
+
+# 加载自定义 skills 到 System Prompt
+runner = AgentRunner(
+    llm_call=create_gemini_llm_call(os.getenv("GEMINI_API_KEY")),
+    skills_dir="./skills",  # 可选:加载额外的自定义 skills
+)
+
+# 结果:
+# - agent/skills/core.md 自动加载(总是)
+# - agent/skills/ 中的其他 skills 自动加载
+# - ./skills/ 中的 skills 也会自动加载
+```
+
+**方式 2:运行时动态加载**
+
+```python
+from agent import AgentRunner
+from agent.llm import create_gemini_llm_call
 import os
 
 runner = AgentRunner(
     llm_call=create_gemini_llm_call(os.getenv("GEMINI_API_KEY"))
 )
 
-async for event in runner.run(
+async for item in runner.run(
     task="帮我从网站提取数据",
-    tools=["skill", "list_skills", "bash"],
     model="gemini-2.0-flash-exp"
 ):
-    if event.type == "conclusion":
-        print(event.data["content"])
+    # Agent 会自动调用 skill 工具加载需要的 skills
+    pass
 ```
 
 **Agent 工作流**:
-1. Agent 接收任务
-2. Agent 调用 `list_skills()` 查看可用 skills
-3. Agent 调用 `skill(skill_name="browser-use")` 加载需要的 skill
-4. Skill 内容注入到对话历史
-5. Agent 根据 skill 指导完成任务
+1. Agent 启动时自动加载 `agent/skills/` 中的所有 skills(包括 `core.md`)
+2. 如果提供了 `skills_dir`,也会加载自定义 skills 到 System Prompt
+3. Agent 接收任务
+4. Agent 可以调用 `list_skills()` 查看可用 skills
+5. Agent 可以调用 `skill(skill_name="browser-use")` 动态加载特定 skill
+6. Skill 内容注入到对话历史
+7. Agent 根据 skill 指导完成任务
 
 ### 4. 手动测试
 
 ```python
-from tools.skill import list_skills, skill
+from agent.tools.builtin.skill import list_skills, skill
 
 # 列出所有 skills
 result = await list_skills()
@@ -120,7 +153,51 @@ result = await skill(skill_name="browser-use")
 print(result.output)
 ```
 
-## Skill 工具
+## Skill 加载机制
+
+### 自动加载(Agent 启动时)
+
+```python
+# load_skills_from_dir() 自动加载内置 skills
+runner = AgentRunner(
+    llm_call=my_llm,
+    # 不需要指定 skills_dir,内置 skills 会自动加载
+)
+
+# 结果:agent/skills/ 中的所有 skills 都会被加载到 System Prompt
+```
+
+### 可选加载额外的自定义 Skills
+
+```python
+runner = AgentRunner(
+    llm_call=my_llm,
+    skills_dir="./my-custom-skills",  # 可选:加载额外的自定义 skills
+)
+
+# 结果:agent/skills/ + ./my-custom-skills/ 中的所有 skills 都会被加载
+```
+
+### 动态加载(运行时)
+
+Agent 可以通过 `skill` 工具运行时动态加载特定的 skill:
+
+```python
+# Agent 调用
+skill(skill_name="browser-use")
+
+# 搜索路径(优先级):
+# 1. ./skills/browser-use.md         ← 项目自定义
+# 2. ./agent/skills/browser-use/     ← 框架内置
+```
+
+**实现位置**:
+- `agent/memory/skill_loader.py:load_skills_from_dir()` - 自动加载机制
+- `agent/tools/builtin/skill.py` - skill 工具(动态加载)
+
+详见 [`SKILLS_SYSTEM.md`](../SKILLS_SYSTEM.md)
+
+## Skill 工具 API
 
 ### `skill` 工具
 
@@ -137,6 +214,8 @@ print(result.output)
 
 **返回**:Skills 列表,包含名称、ID 和简短描述
 
+**实现位置**:`agent/tools/builtin/skill.py`
+
 ## 环境变量
 
 可以设置默认 skills 目录:
@@ -148,6 +227,8 @@ SKILLS_DIR=./skills
 
 ## 参考
 
-- 示例: `examples/skills_example.py`
-- Skill 文件: `skills/` 目录
-- 工具实现: `tools/skill.py`
+- 完整文档:[`SKILLS_SYSTEM.md`](../SKILLS_SYSTEM.md)
+- 示例:`examples/feature_extract/run.py`
+- Skill 文件:`agent/skills/` 目录
+- 工具实现:`agent/tools/builtin/skill.py`
+- 加载器实现:`agent/memory/skill_loader.py`

+ 6 - 6
docs/step-tree.md

@@ -489,17 +489,17 @@ code .trace/tree.md
     time: 14:30:15
 ```
 
-**实现**:`agent/debug/tree_dump.py`
+**实现**:`agent/execution/tree_dump.py`
 
 ---
 
 ## 实现位置
 
-- Step 模型:`agent/models/trace.py:Step`(已实现)
-- Trace 模型:`agent/models/trace.py:Trace`(已实现)
-- 存储接口:`agent/storage/protocols.py:TraceStore`(已实现)
-- 内存存储:`agent/storage/memory_impl.py:MemoryTraceStore`(已实现)
-- Debug 工具:`agent/debug/tree_dump.py`(已实现)
+- Step 模型:`agent/execution/models.py:Step`(已实现)
+- Trace 模型:`agent/execution/models.py:Trace`(已实现)
+- 存储接口:`agent/execution/protocols.py:TraceStore`(已实现)
+- 内存存储:`agent/execution/store.py:MemoryTraceStore`(已实现)
+- Debug 工具:`agent/execution/tree_dump.py`(已实现)
 - **Core Skill**:`agent/skills/core.md`(已实现)
 - step 工具:`agent/tools/builtin/step.py`(待实现)
 - read_progress 工具:`agent/tools/builtin/step.py`(待实现)

+ 4 - 4
docs/sub-agents.md

@@ -9,7 +9,7 @@
 ### AgentDefinition
 
 ```python
-# agent/models/agent.py
+# agent/subagents/agent.py
 
 @dataclass
 class AgentDefinition:
@@ -32,12 +32,12 @@ class AgentDefinition:
     can_spawn_subagent: bool = False
 ```
 
-**实现位置**:`agent/models/agent.py:AgentDefinition`(待实现)
+**实现位置**:`agent/subagents/agent.py:AgentDefinition`(待实现)
 
 ### Trace 扩展
 
 ```python
-# agent/models/trace.py (扩展现有模型)
+# agent/execution/models.py (扩展现有模型)
 
 @dataclass
 class Trace:
@@ -49,7 +49,7 @@ class Trace:
     spawned_by_tool: Optional[str] = None    # 启动此 Sub-Agent 的 Step ID
 ```
 
-**实现位置**:`agent/models/trace.py:Trace`
+**实现位置**:`agent/execution/models.py:Trace`
 
 ---
 

+ 3 - 2
docs/testing.md

@@ -50,9 +50,10 @@ pytest tests/e2e/ -v -m e2e
 
 | 模块 | 目标覆盖率 |
 |------|-----------|
-| agent/models/ | 90%+ |
+| agent/core/ | 90%+ |
+| agent/execution/ | 90%+ |
+| agent/memory/ | 80%+ |
 | agent/tools/ | 85%+ |
-| agent/storage/ | 80%+ |
 | agent/llm/ | 70%+ |
 
 ---

+ 4 - 4
docs/trace-api.md

@@ -6,10 +6,10 @@
 
 ## 架构概览
 
-**职责定位**:`agent/trace` 模块负责所有 Trace/Step 相关功能
+**职责定位**:`agent/execution` 模块负责所有 Trace/Step 相关功能
 
 ```
-agent/trace/
+agent/execution/
 ├── models.py          # Trace/Step 数据模型
 ├── protocols.py       # TraceStore 存储接口
 ├── memory_store.py    # 内存存储实现
@@ -377,7 +377,7 @@ from agent.storage.protocols import TraceStore  # ImportError
 
 ## 相关文档
 
-- [agent/trace/models.py](../agent/trace/models.py) - Trace/Step 模型定义
-- [agent/trace/api.py](../agent/trace/api.py) - RESTful API 实现
+- [agent/execution/models.py](../agent/execution/models.py) - Trace/Step 模型定义
+- [agent/execution/api.py](../agent/execution/api.py) - RESTful API 实现
 - [api_server.py](../api_server.py) - FastAPI 应用入口
 - [requirements.txt](../requirements.txt) - FastAPI 依赖

+ 1 - 1
examples/feature_extract/feature_extract.prompt

@@ -1,5 +1,5 @@
 ---
-model: gemini-2.5-flash
+model: sonnet-4.5
 temperature: 0.3
 ---
 

+ 44 - 40
examples/feature_extract/run.py

@@ -15,10 +15,10 @@ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
 from dotenv import load_dotenv
 load_dotenv()
 
-from agent.prompts import SimplePrompt
-from agent.runner import AgentRunner
-from agent.storage import MemoryTraceStore
-from agent.llm.providers.openrouter import create_openrouter_llm_call
+from agent.llm.prompts import SimplePrompt
+from agent.core.runner import AgentRunner
+from agent.execution import MemoryTraceStore, Trace, Step
+from agent.llm import create_openrouter_llm_call
 
 
 async def main():
@@ -31,8 +31,9 @@ async def main():
     output_dir = base_dir / "output_1"
     output_dir.mkdir(exist_ok=True)
 
-    # Skills 目录
-    skills_dir = project_root / "agent" / "skills"
+    # Skills 目录(可选:用户自定义 skills)
+    # 注意:内置 skills(agent/skills/core.md)会自动加载
+    skills_dir = None  # 或者指定自定义 skills 目录,如: project_root / "skills"
 
     print("=" * 60)
     print("特征提取任务 (Agent 模式)")
@@ -81,7 +82,7 @@ async def main():
     runner = AgentRunner(
         trace_store=MemoryTraceStore(),
         llm_call=create_openrouter_llm_call(model="anthropic/claude-sonnet-4.5"),
-        skills_dir=str(skills_dir),  # 恢复加载 skills,测试 Claude 是否能处理
+        skills_dir=skills_dir,  # 可选:加载额外的用户自定义 skills(内置 skills 会自动加载)
         debug=True  # 启用 debug,输出到 .trace/
     )
 
@@ -93,7 +94,7 @@ async def main():
 
     final_response = ""
 
-    async for event in runner.run(
+    async for item in runner.run(
         task="[图片和特征描述已包含在 messages 中]",  # 占位符
         messages=[user_message_with_image],  # 传入包含图片的用户消息
         system_prompt=system_prompt,
@@ -102,38 +103,41 @@ async def main():
         max_iterations=10,
         # tools 参数不传入,测试自动加载内置工具
     ):
-        event_type = event.type
-        event_data = event.data
-
-        if event_type == "trace_started":
-            print(f"[Trace] 开始: {event_data.get('trace_id', '')[:8]}")
-
-        elif event_type == "memory_loaded":
-            exp_count = event_data.get('experiences_count', 0)
-            if exp_count > 0:
-                print(f"[Memory] 加载 {exp_count} 条经验")
-
-        elif event_type == "step_started":
-            step_type = event_data.get('step_type', '')
-            print(f"[Step] {step_type}...")
-
-        elif event_type == "thought":
-            content = event_data.get('content', '')
-            if content:
-                print(f"[Thought] {content[:100]}...")
-
-        elif event_type == "tool_execution":
-            tool_name = event_data.get('tool_name', '')
-            print(f"[Tool] 执行 {tool_name}")
-
-        elif event_type == "conclusion":
-            final_response = event_data.get('content', '')  # 修正:字段名是 content 不是 response
-            print(f"[Conclusion] Agent 完成")
-
-        elif event_type == "trace_completed":
-            print(f"[Trace] 完成")
-            print(f"  - Total tokens: {event_data.get('total_tokens', 0)}")
-            print(f"  - Total cost: ${event_data.get('total_cost', 0.0):.4f}")
+        # 处理 Trace 对象(整体状态变化)
+        if isinstance(item, Trace):
+            if item.status == "in_progress":
+                print(f"[Trace] 开始: {item.trace_id[:8]}")
+            elif item.status == "completed":
+                print(f"[Trace] 完成")
+                print(f"  - Total tokens: {item.total_tokens}")
+                print(f"  - Total cost: ${item.total_cost:.4f}")
+            elif item.status == "failed":
+                print(f"[Trace] 失败")
+
+        # 处理 Step 对象(执行过程)
+        elif isinstance(item, Step):
+            if item.step_type == "memory_read":
+                exp_count = item.data.get('experiences_count', 0)
+                if exp_count > 0:
+                    print(f"[Memory] 加载 {exp_count} 条经验")
+
+            elif item.step_type == "thought":
+                if item.status == "completed":
+                    content = item.data.get('content', '')
+                    if content:
+                        print(f"[Thought] {content[:100]}...")
+
+            elif item.step_type == "action":
+                tool_name = item.data.get('tool_name', '')
+                print(f"[Tool] 执行 {tool_name}")
+
+            elif item.step_type == "result":
+                tool_name = item.data.get('tool_name', '')
+                print(f"[Tool] {tool_name} 完成")
+
+            elif item.step_type == "response":
+                final_response = item.data.get('content', '')
+                print(f"[Response] Agent 完成")
 
     # 6. 输出结果
     print()

+ 3 - 7
examples/feature_extract/test.prompt

@@ -4,17 +4,13 @@ temperature: 0.3
 ---
 
 $system$
-# 角色
-你是一位计算机视觉专家,也是一位才华横溢的社媒博主、内容创作者。
+你是最顶尖的AI助手,可以拆分并调用工具逐步解决复杂问题。
 
+$user$
 # 任务
-分析一个优质内容的指定特征适合什么样的表示(仅仅语言描述是不够的),并完成该特征的提取
+分析一个优质内容的指定特征适合什么样的表示,通过联网搜索给我一个参考的表示方案
 提取的特征将用于在生成类似内容时作为参考内容(所以要保留重要信息),也会和其他内容的同一维度的特征放在一起聚类发现规律(所以特征表示要尽量精简、不要过于具体),或用于模型训练。
 
-# 工具
-- 你可以加载browser-use的skill,并根据skill中的指引操作浏览器,来做调研或检索
-
-$user$
 # 指定的特征:
 %text%
 

+ 1 - 1
examples/subagent_example.py

@@ -7,7 +7,7 @@ Sub-Agent 使用示例
 import asyncio
 import os
 from agent import AgentRunner, AgentConfig
-from agent.llm.providers.gemini import create_gemini_llm_call
+from agent.llm import create_gemini_llm_call
 from agent.agent_registry import get_agent_registry
 from agent.models.agent import AgentDefinition
 

+ 0 - 0
tests/__init__.py


+ 0 - 255
tests/test_runner.py

@@ -1,255 +0,0 @@
-"""
-AgentRunner 测试
-"""
-
-import pytest
-from agent import (
-    AgentRunner,
-    AgentEvent,
-    Trace,
-    Step,
-    Experience,
-    Skill,
-    tool,
-    get_tool_registry,
-)
-from agent.storage import MemoryTraceStore, MemoryMemoryStore
-
-
-# 测试工具
-@tool(
-    editable_params=["query"],
-    display={"zh": {"name": "测试搜索", "params": {"query": "关键词"}}}
-)
-async def search_tool(query: str, limit: int = 10, uid: str = "") -> dict:
-    """测试搜索工具"""
-    return {"results": [f"结果: {query}"], "count": 1}
-
-
-# Mock LLM 调用
-async def mock_llm_call(
-    messages: list,
-    model: str = "gpt-4o",
-    tools: list = None,
-    **kwargs
-) -> dict:
-    """模拟 LLM 调用"""
-    # 简单模拟:如果有工具,第一次调用返回 tool_call,第二次返回结果
-    user_msg = messages[-1]["content"] if messages else ""
-
-    if "搜索" in user_msg and tools:
-        return {
-            "content": "",
-            "tool_calls": [{
-                "id": "call_123",
-                "function": {
-                    "name": "search_tool",
-                    "arguments": '{"query": "测试查询"}'
-                }
-            }],
-            "prompt_tokens": 100,
-            "completion_tokens": 50,
-            "cost": 0.01
-        }
-
-    return {
-        "content": f"回复: {user_msg}",
-        "tool_calls": None,
-        "prompt_tokens": 100,
-        "completion_tokens": 50,
-        "cost": 0.01
-    }
-
-
-class TestTraceAndStep:
-    """测试 Trace 和 Step"""
-
-    def test_trace_create(self):
-        trace = Trace.create(mode="call", uid="user123")
-        assert trace.trace_id is not None
-        assert trace.mode == "call"
-        assert trace.uid == "user123"
-        assert trace.status == "running"
-
-    def test_step_create(self):
-        step = Step.create(
-            trace_id="trace_123",
-            step_type="thought",
-            sequence=0,
-            status="completed",
-            description="测试步骤",
-            data={"content": "hello"}
-        )
-        assert step.step_id is not None
-        assert step.trace_id == "trace_123"
-        assert step.step_type == "thought"
-        assert step.status == "completed"
-        assert step.data["content"] == "hello"
-
-
-class TestMemoryStore:
-    """测试内存存储"""
-
-    @pytest.mark.asyncio
-    async def test_trace_store(self):
-        store = MemoryTraceStore()
-
-        # 创建 Trace
-        trace = Trace.create(mode="agent", task="测试任务")
-        trace_id = await store.create_trace(trace)
-        assert trace_id == trace.trace_id
-
-        # 获取 Trace
-        retrieved = await store.get_trace(trace_id)
-        assert retrieved is not None
-        assert retrieved.task == "测试任务"
-
-        # 添加 Step
-        step = Step.create(
-            trace_id=trace_id,
-            step_type="llm_call",
-            sequence=0
-        )
-        await store.add_step(step)
-
-        # 获取 Steps
-        steps = await store.get_trace_steps(trace_id)
-        assert len(steps) == 1
-
-    @pytest.mark.asyncio
-    async def test_memory_store(self):
-        store = MemoryMemoryStore()
-
-        # 添加 Experience
-        exp = Experience.create(
-            scope="agent:test",
-            condition="测试条件",
-            rule="测试规则"
-        )
-        exp_id = await store.add_experience(exp)
-        assert exp_id == exp.exp_id
-
-        # 搜索
-        results = await store.search_experiences("agent:test", "")
-        assert len(results) == 1
-
-
-class TestToolRegistry:
-    """测试工具注册"""
-
-    def test_tool_registered(self):
-        registry = get_tool_registry()
-        assert registry.is_registered("search_tool")
-
-    def test_get_schemas(self):
-        registry = get_tool_registry()
-        schemas = registry.get_schemas(["search_tool"])
-        assert len(schemas) == 1
-        assert schemas[0]["function"]["name"] == "search_tool"
-
-    @pytest.mark.asyncio
-    async def test_execute_tool(self):
-        registry = get_tool_registry()
-        result = await registry.execute("search_tool", {"query": "hello"}, uid="test")
-        assert "结果" in result
-
-
-class TestAgentRunner:
-    """测试 AgentRunner"""
-
-    @pytest.mark.asyncio
-    async def test_call_simple(self):
-        """测试简单调用"""
-        runner = AgentRunner(
-            trace_store=MemoryTraceStore(),
-            llm_call=mock_llm_call
-        )
-
-        result = await runner.call(
-            messages=[{"role": "user", "content": "你好"}],
-            model="gpt-4o"
-        )
-
-        assert "你好" in result.reply
-        assert result.trace_id is not None
-
-    @pytest.mark.asyncio
-    async def test_run_simple(self):
-        """测试 Agent 运行"""
-        runner = AgentRunner(
-            trace_store=MemoryTraceStore(),
-            memory_store=MemoryMemoryStore(),
-            llm_call=mock_llm_call
-        )
-
-        events = []
-        async for event in runner.run(
-            task="简单任务",
-            agent_type="test"
-        ):
-            events.append(event)
-
-        # 检查事件序列
-        event_types = [e.type for e in events]
-        assert "trace_started" in event_types
-        assert "trace_completed" in event_types
-
-    @pytest.mark.asyncio
-    async def test_run_with_tools(self):
-        """测试带工具的 Agent 运行"""
-        runner = AgentRunner(
-            trace_store=MemoryTraceStore(),
-            llm_call=mock_llm_call
-        )
-
-        events = []
-        async for event in runner.run(
-            task="请搜索相关内容",
-            tools=["search_tool"],
-            agent_type="test"
-        ):
-            events.append(event)
-
-        event_types = [e.type for e in events]
-        assert "tool_executing" in event_types
-        assert "tool_result" in event_types
-
-    @pytest.mark.asyncio
-    async def test_add_feedback(self):
-        """测试添加反馈"""
-        trace_store = MemoryTraceStore()
-        memory_store = MemoryMemoryStore()
-
-        runner = AgentRunner(
-            trace_store=trace_store,
-            memory_store=memory_store,
-            llm_call=mock_llm_call
-        )
-
-        # 先运行一个任务
-        trace_id = None
-        step_id = None
-        async for event in runner.run(task="测试任务", agent_type="test"):
-            if event.type == "trace_started":
-                trace_id = event.data["trace_id"]
-            if event.type == "llm_call_completed":
-                step_id = event.data["step_id"]
-
-        # 添加反馈
-        exp_id = await runner.add_feedback(
-            trace_id=trace_id,
-            target_step_id=step_id,
-            feedback_type="correction",
-            content="应该这样做"
-        )
-
-        assert exp_id is not None
-
-        # 验证经验被存储
-        exp = await memory_store.get_experience(exp_id)
-        assert exp is not None
-        assert exp.rule == "应该这样做"
-
-
-if __name__ == "__main__":
-    pytest.main([__file__, "-v"])