# Mao 改进建议文档 > Note: The `bash` tool has been removed. `shell` is now the only command execution tool. See `2026-06-03-shell-unification-design.md`. 基于 `docs/best-agent-spec/` 中 12 篇最佳实践与当前项目代码的逐项对照分析。 --- ## 架构背景:项目的双层工具体系 ``` LLM 视角(ToolDefinition / function calling) ↑ 统一转换 PromptEngine ↑ / ↑ Skill(内置) MCP Tool(外部) - Skill 接口 - MCP 协议发现 - Spring Bean 注册 - MCP Server 配置 - bash, http 等 - 用户扩展的领域工具 ``` - **Skill**:平台内置的基础工具,实现 `Skill` 接口,通过 Spring Bean 自动注册到 `SkillRegistry`。每个 Agent 通过 `agent_skill` 关联表选择启用哪些 Skill。**bash、文件读写等是 Agent 的基础能力,必须内置**——best-agent-spec s01 明确指出 "One loop and Bash is all you need",bash 是 Agent 循环的另一半。 - **MCP Tool**:用户按需接入的外部工具扩展,通过 MCP 协议动态发现。用于接入数据库、特定 API、领域专用能力等。 - **ToolDefinition**:OpenAI function calling 格式,LLM 看到的统一接口。`PromptEngine` 将 Skill 和 MCP Tool 统一转换。 - **SkillDispatcher**:执行路由,优先查 SkillRegistry,再查 McpToolRegistry。 当前项目的核心问题之一:**内置 Skill 只有一个 `HttpRequestSkill`,缺少 bash、文件读写等基础工具,导致 Agent 缺乏最基本的执行能力。** --- ## 总览:对标矩阵 | 最佳实践章节 | 核心能力 | 当前项目状态 | 优先级 | |---|---|---|---| | s01 Agent Loop | while 循环 + stop_reason 驱动 | ✅ 已实现,基本正确 | — | | s02 Tool Use | bash + 文件工具 + 路径沙箱 | ⚠️ 派发框架有,基础工具缺失,沙箱缺失 | P1 | | s03 TodoWrite | 结构化计划 + nag reminder | ❌ 完全缺失 | P2 | | s05 Skill Loading | 两层按需加载 | ⚠️ 框架有,动态加载未实现 | P1 | | s06 Context Compact | 三层压缩策略 | ❌ 压缩代码存在但从未调用 | **P0** | | s07 Task System | 磁盘持久化 DAG | ❌ 完全缺失 | P3 | | s08 Background Tasks | 非阻塞并行执行 | ❌ 完全缺失 | P2 | | s09 Agent Teams | 持久化队友 + JSONL 信箱 | ❌ 完全缺失 | P3 | | s10 Team Protocols | 请求-响应 FSM | ❌ 完全缺失 | P3 | | s11 Autonomous Agents | 自组织任务认领 | ❌ 完全缺失 | P3 | | s12 Worktree Isolation | 按任务隔离工作目录 | ❌ 完全缺失 | P3 | --- ## P0 — 必须立即修复 ### 1. 上下文压缩是死代码 **现状:** `ContextManager` 已实现 `needsCompression()` 和 `compress()` 方法,但 `AgentLoop.execute()` 中从未调用。对话会无限增长直到超出 LLM 上下文窗口,API 直接报错。 **最佳实践(s06):** 三层压缩策略——micro_compact(每轮静默清理旧 tool_result)、auto_compact(超阈值自动摘要)、manual compact(模型主动触发)。 **改进建议:** ```java // AgentLoop.execute() 中,每次构建 prompt 前加入压缩检查 while (round < maxRounds) { // ← 新增:在构建 prompt 前检查并压缩上下文 if (contextManager.needsCompression(context, context.getModelConfig().getMaxContextTokens())) { List compressed = contextManager.compress(context, 0); context.setMessages(compressed); listener.onContextCompressed(compressed.size()); } ChatRequest request = promptEngine.buildRequest(context); // ... rest of loop } ``` 进一步演进方向: - 实现 micro_compact:每轮结束时,将超过 N 轮前的 tool_result 替换为占位符 `[Previous: used {tool_name}]` - 实现 auto_compact:调用 LLM 生成摘要而非简单截断 - 压缩前将完整对话转录保存到磁盘(`.transcripts/`),防止信息永久丢失 - 为 `AgentEventListener` 增加 `onContextCompressed` 事件,让前端感知压缩发生 --- ## P1 — 高优先级改进 ### 2. 内置基础工具集缺失 + 安全防护不足 **现状:** 内置 Skill 只有 `HttpRequestSkill`,缺少 bash、文件读写等 Agent 基础工具。best-agent-spec s02 明确要求四个基础工具:`bash`、`read_file`、`write_file`、`edit_file`,当前项目一个都没有。同时 `SkillDispatcher` 只做工具名路由,无任何安全检查。 **最佳实践(s02):** bash 是 Agent 的基础能力;`safe_path()` 防止目录逃逸;专用文件工具比裸 bash 更安全可控。 **改进建议:** - **补齐基础内置 Skill**(最高优先级): - `BashSkill`:执行 shell 命令,设置超时(30 秒默认),输出截断(50K 字符) - `ReadFileSkill`:读取文件内容,支持行范围,输出截断 - `WriteFileSkill`:写入文件,路径沙箱检查 - `EditFileSkill`:精确字符串替换编辑,路径沙箱检查 - **路径沙箱**:所有涉及文件操作的内置 Skill 共享一个 `PathSandbox`: ```java public class PathSandbox { private final Path workspaceRoot; public Path resolve(String userPath) { Path resolved = workspaceRoot.resolve(userPath).normalize(); if (!resolved.startsWith(workspaceRoot)) { throw new SecurityException("Path escape attempt: " + userPath); } return resolved; } } ``` - **HttpRequestSkill 安全加固**:增加域名白名单或禁止访问内网地址(SSRF 防护) - **MCP Tool 安全策略**:管理员为每个 MCP Server 配置允许的工具列表和参数约束 ### 3. Skill 动态加载未实现 **现状:** `skill` 数据库表有 `implClass` 字段,但 `SkillRegistry` 只通过 Spring Bean 扫描注册。用户通过管理后台创建的自定义 Skill 实际上无法执行。 **最佳实践(s05):** 两层加载——系统提示词中放轻量元数据,模型需要时通过 tool 加载完整内容。 **改进建议:** - 短期:实现基于 `implClass` 的动态类加载(使用 Spring `ApplicationContext` 或自定义 ClassLoader) - 中期:引入 Skill 脚本引擎,支持 Groovy/JavaScript 脚本形式的 Skill,而非必须写 Java 类 - 长期:实现 s05 的两层 Skill 发现机制——系统提示词中只放 Skill 名称和描述列表,模型通过 `load_skill` 工具按需加载完整指令 ### 4. McpClient 线程安全问题 **现状:** `McpClient` 的 `rpcId` 是普通 `int` 实例字段,并发调用会产生重复 JSON-RPC ID。 **改进建议:** ```java // 将 rpcId 改为 AtomicInteger private final AtomicInteger rpcId = new AtomicInteger(0); // 在每次请求时 int id = rpcId.incrementAndGet(); ``` 同时,工具发现(`discoverAndRegister`)在每次会话执行时重复调用,应增加 TTL 缓存机制。 --- ## P2 — 中优先级改进 ### 5. 缺少 Todo/计划系统 **现状:** 模型在多步任务中没有任何结构化计划机制,容易丢失上下文、重复操作或遗漏步骤。 **最佳实践(s03):** TodoWrite 工具 + 单任务 in_progress 约束 + nag reminder。 **改进建议:** - 新增 `TodoSkill` 实现,内部维护 `List`,每个 item 有 `status`(pending/in_progress/completed) - 强制同一时间只有一个 in_progress 任务 - 在 `AgentLoop` 中加入回合计数器,若连续 3 轮未调用 `todo` 工具,自动注入提醒: ```java if (roundsSinceTodoUpdate >= 3) { context.addToolResult("system-reminder", "请更新你的任务计划。"); } ``` - 这是最低成本高收益的改进,实现简单但显著提升多步任务完成率 ### 6. 缺少后台任务执行 **现状:** 所有工具调用都是同步阻塞的。长时间运行的命令(如 `npm install`、`docker build`)会阻塞整个 SSE 流,前端只能等待。 **最佳实践(s08):** 后台线程执行慢操作,结果通过通知队列注入。 **改进建议:** - 新增 `BackgroundTaskManager`,管理后台线程池 - `BashSkill` 支持 `async: true` 参数:提交到后台线程立即返回 taskId,结果通过通知队列异步注入 - 在 `AgentLoop` 每轮开始前,检查后台任务完成队列,将结果作为 `` 注入 - 设置 300 秒超时和 500 字符结果截断 ### 7. 内存泄漏:EVENT_CONTENT_STORE **现状:** `HarnessService` 中的 `EVENT_CONTENT_STORE`(ConcurrentHashMap)无 TTL、无驱逐。客户端发消息但不开 SSE 连接时,内容永远留在内存中。 **改进建议:** - 使用 `Caffeine` 或 `Guava Cache` 替换,设置 10 分钟过期: ```java private final Cache eventContentStore = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(1000) .build(); ``` - 或改用数据库表配合定期清理,便于多实例共享 --- ## P3 — 长期架构演进 ### 8. 缺少任务系统(Task System) **最佳实践(s07):** 磁盘持久化的任务 DAG,支持依赖关系、状态流转、Owner 认领。是多 Agent 协调的基础设施。 **改进建议:** - 新增 `TaskManager`,任务以 JSON 文件存储在 `.tasks/` 目录 - 任务字段:`id`, `subject`, `status`, `blockedBy`, `owner` - 工具:`task_create`, `task_update`, `task_list`, `task_get` - 任务完成时自动解锁依赖它的其他任务 - 与 TodoWrite 互补:TodoWrite 用于会话内快速检查列表,Task System 用于跨会话持久化工作 ### 9. 缺少多 Agent 团队能力 **最佳实践(s09-s11):** 持久化队友 + JSONL 信箱通信 + 自组织任务认领。 **改进建议(分阶段):** **阶段一 — 基础团队(s09):** - `TeammateManager` 维护团队花名册(`.team/config.json`) - `MessageBus` 基于 JSONL 文件的信箱(`.team/inbox/`) - 每个队友运行独立的 AgentLoop,有自己的上下文和消息历史 - 工具:`spawn`, `send`, `read_inbox` **阶段二 — 团队协议(s10):** - 统一的请求-响应 FSM 模式 - 优雅关闭协议(shutdown_request/response) - 计划审批协议(plan_approval_response) **阶段三 — 自主 Agent(s11):** - 两阶段循环:WORK(LLM 驱动)+ IDLE(轮询新任务) - 自动扫描任务看板,认领未分配且未被阻塞的任务 - 身份重注入:上下文压缩后重新插入 agent 身份信息 - 60 秒空闲超时自动关闭 ### 10. 缺少 Worktree 隔离 **最佳实践(s12):** 每个任务绑定独立的 git worktree,多 Agent 可同时操作同一仓库而不冲突。 **改进建议:** - `WorktreeManager`:`create(name, task_id)` 创建 worktree 并绑定任务 - `.worktrees/index.json` 注册表 + `.worktrees/events.jsonl` 事件流 - 所有命令在 worktree 目录下执行 - 单步清理:`worktree_remove(name, complete_task=true)` 同时处理目录清理、任务完成、事件记录 --- ## 其他工程质量问题 ### 11. 线程池硬编码 **现状:** `SessionController` 中 `Executors.newFixedThreadPool(20)` 硬编码,未使用 `application.yml` 中已定义的 `app.harness.agent-thread-pool-size` 配置。 **改进:** 通过 `@Value` 注入配置值,或使用 `ThreadPoolTaskExecutor` 替换。 ### 12. 硬编码密钥 **现状:** `application.yml` 包含明文数据库密码和默认 JWT secret。`LlmModelConfig.apiKey` 明文存储在数据库。 **改进:** - 数据库密码和 JWT secret 迁移到环境变量或 Vault - LLM API Key 加密存储,运行时解密 ### 13. 无重试和熔断 **现状:** LLM 调用失败直接报错给客户端,无重试。 **改进:** - 引入 Resilience4j 或 Spring Retry,对 LLM 调用增加指数退避重试(最多 3 次) - 对熔断状态降级为排队等待 ### 14. 工具调用串行执行 **现状:** `AgentLoop` 中多个 tool call 顺序执行(第 107-125 行的 for 循环)。当模型一次返回多个独立工具调用时,浪费时间。 **改进:** 使用 `CompletableFuture.allOf()` 并行执行无依赖的工具调用,结果按原始顺序收集。 ### 15. 无测试覆盖 **现状:** `src/test` 目录为空。 **改进优先级:** 1. `AgentLoop` 的集成测试(mock LLM,验证循环终止条件) 2. `ContextManager` 的单元测试(压缩逻辑、token 估算) 3. `SkillDispatcher` 的单元测试(路由逻辑、错误处理) 4. SSE 流的端到端测试 --- ## 建议实施路线图 ``` Phase 1 (1-2 周) — 修复致命问题 ├── [P0] 激活上下文压缩,接入 AgentLoop ├── [P1] McpClient 线程安全修复 ├── [P1] EVENT_CONTENT_STORE 内存泄漏修复 └── [P1] 线程池配置修复 Phase 2 (2-4 周) — 核心能力补齐 ├── [P1] 内置基础工具:BashSkill + ReadFileSkill + WriteFileSkill + EditFileSkill ├── [P1] 路径沙箱 + SSRF 防护 ├── [P1] Skill 动态加载实现 ├── [P2] TodoWrite 计划工具 └── [P2] 后台任务执行 Phase 3 (4-8 周) — 架构升级 ├── [P3] 磁盘持久化 Task System ├── [P3] 多 Agent 团队基础能力 └── 工程质量:重试熔断、测试覆盖 Phase 4 (8+ 周) — 高级特性 ├── [P3] 自主 Agent + 任务认领 ├── [P3] Worktree 隔离 └── [P3] 团队协议 (FSM) ``` --- ## 附录:关键文件索引 | 文件 | 作用 | 主要问题 | |---|---|---| | [AgentLoop.java](backend/src/main/java/cn/etarch/mao/harness/core/AgentLoop.java) | Agent 核心循环 | 缺少压缩调用、工具并行执行 | | [ContextManager.java](backend/src/main/java/cn/etarch/mao/harness/core/ContextManager.java) | 上下文管理 | 从未被调用,摘要策略过于简单 | | [SkillDispatcher.java](backend/src/main/java/cn/etarch/mao/harness/skill/SkillDispatcher.java) | Skill/MCP 双层工具派发 | 缺少基础工具(bash/file),无安全拦截器 | | [SkillRegistry.java](backend/src/main/java/cn/etarch/mao/harness/skill/SkillRegistry.java) | 内置 Skill 注册(Spring Bean) | 只支持 Spring Bean 扫描,`implClass` 动态加载未实现 | | [McpClient.java](backend/src/main/java/cn/etarch/mao/harness/mcp/McpClient.java) | MCP 协议客户端 | rpcId 线程不安全 | | [PromptEngine.java](backend/src/main/java/cn/etarch/mao/harness/core/PromptEngine.java) | Prompt 构建 | 无模板、无版本管理 | | [HarnessService.java](backend/src/main/java/cn/etarch/mao/harness/service/HarnessService.java) | Harness 服务编排 | EVENT_CONTENT_STORE 内存泄漏 | | [SessionController.java](backend/src/main/java/cn/etarch/mao/session/controller/SessionController.java) | 会话 API | 线程池硬编码 |