feat: Agent 安全纵深防御、Checkpoint 快照、会话 Rewind/Branch、自进化
Skill、流式执行优化与系统架构全面升级
本次提交对标 Claude Code 与 Hermes-Agent 的工程细节,在安全、可靠性、
会话管理、自我进化四个维度进行了系统性加固,变更总量 48 文件 / +12680 -2292 行。
═══════ 安全纵深防御 ═══════
1. Hardline 硬阻止层 (src/agent/runtime/hardline.rs, +534 行)
- 不可绕过的危险命令拦截(关重启、磁盘擦除、Fork 炸弹、rm -rf /、kill -1)
- 反规避标准化管线: ANSI 序列剥离 → Unicode NFKC → shell 反斜杠还原 → 空字面量清理
- 在 PermissionChecker 之前执行,YOLO/Bypass 模式下同样生效
- 集成到 executor Phase 2,被拒绝工具直接注入错误结果
2. Permission 优先级裁决器 (src/agent/runtime/permission.rs, +200 行)
- 7 层正式优先级规则 (P0 Deny → P7 Allow),带冲突日志
- explain() 方法支持审计追溯
- Hook PermissionRequired 与 Checker 结果的正确叠加逻辑
═══════ Checkpoint 文件快照系统 ═══════
3. git2 原生快照 (src/agent/runtime/checkpoint.rs, +920 行)
- 基于 git2 bare repo,内容寻址自动去重
- 文件变更操作前自动触发 (file_write/file_edit/run_bash)
- 每目录每 turn 最多一次快照,防止同一轮重复
- 支持 list/diff/restore API + pre-rollback 安全快照
- 旧快照自动 prune(保留最近 N 个)+ 按目录隔离 ref
- 排除规则自动过滤 node_modules/target/.git/*.pdf 等
- 集成到 executor: 文件操作前 ckpt.ensure_checkpoint()
═══════ 错误恢复系统大升级 ═══════
4. 21 种 FailoverReason 分类 (src/agent/runtime/error_recovery.rs, +1200 行)
- 参考 Hermes-Agent error_classifier.py
- 8 步分类管线: provider-specific → HTTP status → text pattern → error body → fallback
- is_retryable / should_compress / should_failover / is_permanent 方法
- Context Overflow 自动修复: 从错误消息提取 token 限制,自动下调预算
- RecoveryStep::AdjustMaxTokens 实现 (参考 Claude Code 自动修复)
- 向后兼容 ErrorKind 别名
═══════ 会话 Rewind / Branch / Retry 体系 ═══════
5. 完整 undo 栈 (src/agent/runtime/session.rs, +800 行 + 2 迁移脚本)
- Rewind (软删除): active=0 标记,审计 trail 保留,LLM 不可见
- Restore (撤销回退): 冲突检测——回退后有新消息则拒绝,引导使用 Branch
- Branch: 分叉会话,复制所有 active=1 消息到新会话
- Retry: 硬删除最后一轮对话,返回原消息文本供前端重提交
- 数据库: agent_messages.active 列 + agent_sessions.rewind_count + parent_session_id
- API: 4 个新端点 (/branch, /retry, /rewind, /rewind/restore)
- load_history_for_agent 全面使用 active=1 过滤
═══════ Hooks 系统模块化重构 ═══════
6. 单文件 → 7 模块体系 (src/agent/hooks/)
hooks.rs (994 行) 拆分为:
- mod.rs — 入口 + HookRegistry + SessionHookManager
- types.rs — 类型定义 (Context, TaggedContext, PermissionRequestAction 等)
- traits.rs — AgentHook + AsyncAgentHook + 15 种生命周期事件
- matcher.rs — 工具名/参数匹配 + session 作用域过滤
- dispatch.rs — 并行调度引擎 (run_pre/post_tool_use 等)
- registry.rs — 注册/注销/查询
- builtins.rs — CancellationHook + MetricsHook + AuditLogHook + ContextDeduplicator
关键改进:
- run_pre_tool_use 并行执行所有匹配 hooks,聚合 Block/MutateInput/Continue
- TaggedContext 带完整来源标记的上下文注入 (hook_name + event)
- ContextDeduplicator 单 dispatch cycle 内内容哈希去重
- AsyncAgentHook 支持 fire-and-forget 异步 hooks
═══════ Executor 并发执行升级 ═══════
7. 三阶段管道重写 (src/agent/runtime/executor.rs, +600 行)
- Phase 1: 死循环检测 + 参数解析 (不变)
- Phase 2: Hardline 预检查 (新增) → PermissionChecker (改进)
- Phase 3: ToolPartitioner 分区 → 逐批次执行 (重写)
- 并行批次内 FuturesUnordered 并发
- 串行批次确保非并发安全工具独占执行
- Checkpoint 预触发集成
- Hook 上下文注入: system-reminder 格式 + ContextDeduplicator 去重
- Hook 阻塞错误详细记录
═══════ 流式执行真正的流式调度 ═══════
8. StreamingExecutor 重写 (src/agent/runtime/streaming_executor.rs, ~400 行变更)
- on_tool_use 中对并发安全工具立即 tokio::spawn,不等待 flush
- executing_non_concurrent 标志阻塞后继工具直到独占工具完成
- JoinHandle 管理替代自定义 cancel channel
- completed_queue 按流顺序 yield
- Sibling Abort 通过 broadcast channel + tokio::select! 竞速
- ToolContext 实现 Clone (支持 per-task 上下文复制)
═══════ 自改进 Skill 系统 ═══════
9. PatternDetector + SkillCreator + Curator (src/agent/skills/, +1500 行)
- PatternDetector: 扫描 agent_messages 表,检测跨 session 重复工具调用模式
- SkillCreator: 将高置信度模式自动生成 SKILL.md (YAML frontmatter + 工作流步骤)
- SelfImprovePipeline: 一站式 模式检测 → 创建 → 质量审查
- Curator: 分析 skill 使用统计,标记 stale/deprecated,建议清理
- Skill frontmatter 新增 pinned 字段 (禁止 Curator 自动清理)
═══════ 基础设施优化 ═══════
10. 系统提示词缓存 (src/agent/runtime/system_prompt.rs + mod.rs)
- SystemPromptCache: 首次计算后永久复用,/clear 时失效
- 新增 SAFETY / SYSTEM_CONTEXT / TOOL_USAGE 静态 section
- 环境/tools/skills/memory 动态 section 通过 get_or_compute 缓存
11. ToolRegistry schema 缓存 (src/agent/tools/mod.rs)
- schema_cache + schema_generation 版本号
- 工具变更/过滤器变更时自动失效
- precompute_definitions() 预计算 (AgentRuntime 初始化时调用)
12. 迭代摘要融合 (src/agent/compact.rs, +100 行)
- 参考 Hermes context_compressor.py
- CollapseLog 追踪压缩历史,支持溢出合并
- extract_prior_summary: 提取已有摘要融入新压缩
13. SubAgent 系统提示词模块化 (src/agent/tools/subagent.rs)
- 复用 5 个标准 section + 子代理专有上下文 section
- 独立 ToolRegistry 构建工具列表
═══════ 前端 — CSS 变量主题系统 ═══════
14. 全新主题变量体系 (dashboard/src/index.css + App.tsx + 各面板)
- CSS 自定义属性: --bg-card, --text-main, --text-muted, --border-precision
- 语义化颜色: --accent-blueprint, --accent-star
- 全面替换硬编码 Tailwind 颜色 (slate-xxx → var(--xxx))
- 文献入库提示优化 ("核心知识节点" 替代 "向量块")
- ReaderPanel 样式变量化
This commit is contained in:
@@ -0,0 +1,909 @@
|
||||
# Claude Code / Hermes-Agent 参考分析
|
||||
|
||||
对 Claude Code (`/home/fmq/program/claudecode/src/`) 和 Hermes-Agent (`libs/hermes-agent/`)
|
||||
源码的全面架构分析,记录对 AstroResearch Agent 系统的参考价值与改进方向。
|
||||
|
||||
> 分析日期: 2026-06-22 | 最后更新: 2026-06-22
|
||||
|
||||
## 实施状态
|
||||
|
||||
| 优先级 | 改进项 | 状态 | 涉及文件 |
|
||||
|--------|--------|------|---------|
|
||||
| P0 | Context Overflow 自动修复 | ✅ 已完成 | `error_recovery.rs` (+150 行) |
|
||||
| P0 | StreamingExecutor 真正流式调度 | ✅ 已完成 | `streaming_executor.rs` (重写 ~400 行) |
|
||||
| P0 | Executor 集成分区器(批次串行/并行) | ✅ 已完成 | `executor.rs` (Phase 3 重写 + 2 个提取函数) |
|
||||
| P1 | 工具并发分区 `partition_tool_calls` | ✅ 已完成 | `partitioner.rs` (+2 测试) |
|
||||
| P1 | PermissionRequest / PermissionDenied Hooks | ✅ 已完成 | `hooks/types.rs`, `traits.rs`, `dispatch.rs`, `mod.rs` |
|
||||
| P1 | Auto-mode Classifier | ⏳ 待定 | — |
|
||||
| P2 | Self-improving Skills(模式检测 + 自动创建 + Curator) | ✅ 已完成 | `skills/pattern_detector.rs` + `curator.rs` + `SkillCreator` |
|
||||
| P2 | Coordinator Mode | ⏳ 待定 | — |
|
||||
| P2 | UserPromptSubmit / PreCompact / PostCompact Hook | ⏳ 待定 | — |
|
||||
| P3 | FTS5 跨 session 搜索 | ⏳ 待定 | — |
|
||||
| P3 | Tool `defer_loading` / `classifier_summary` | ⏳ 待定 | — |
|
||||
| P3 | 模型回退策略 | ⏳ 待定 | — |
|
||||
| P3 | Session Memory Compaction | ⏳ 待定 | — |
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [总体评估](#1-总体评估)
|
||||
2. [工具并发执行模型](#2-工具并发执行模型)
|
||||
3. [Permission 系统](#3-permission-系统)
|
||||
4. [Hooks 系统](#4-hooks-系统)
|
||||
5. [Error Recovery / 重试系统](#5-error-recovery--重试系统)
|
||||
6. [Tool 定义系统](#7-tool-定义系统)
|
||||
8. [Memory 持久化](#8-memory-持久化)
|
||||
9. [Coordinator / Multi-Agent](#9-coordinator--multi-agent)
|
||||
10. [Hermes-Agent 的独特贡献](#10-hermes-agent-的独特贡献)
|
||||
11. [优先级排序 —— 建议实施路线](#11-优先级排序--建议实施路线)
|
||||
|
||||
---
|
||||
|
||||
## 1. 总体评估
|
||||
|
||||
### 1.1 参考项目概览
|
||||
|
||||
| 维度 | Claude Code | Hermes-Agent | AstroResearch |
|
||||
|------|-------------|-------------|---------------|
|
||||
| 语言 | TypeScript (Node.js) | Python (3.11+) | Rust (Axum) |
|
||||
| 定位 | 终端 IDE 编程助手 | 通用 AI 个人助手 | 天文科研 Agent |
|
||||
| Agent 循环 | 流式 `query()` generator | 同步 `while` 循环 | 流式 ReAct 循环 |
|
||||
| 工具注册 | 手动 import + `getAllBaseTools()` | 文件系统自动发现 | 手动 `ToolRegistry::new()` |
|
||||
| 工具接口 | `Tool<T>` — ~70 个方法 | `handler(args) -> JSON string` | `AgentTool` trait — ~10 个方法 |
|
||||
| 权限系统 | 6 层优先级 + Classifier + 沙箱 | 无内置 | 3 层规则 + PermissionChecker |
|
||||
| Hooks | 27 种事件,6 种 hook 类型 | PluginManager 生命周期 | 15 种事件,2 种 hook 类型 |
|
||||
| 子代理 | `AgentTool` + Fork + Worktree 隔离 | `delegate_task` + 子 AIAgent | `SubAgentTool` + 独立 ReAct |
|
||||
| 多 Agent | Coordinator 模式 + Swarm/Team | Kanban 工作队列 | Team 系统 (lead/teammate) |
|
||||
| 持久化 | 文件系统 Markdown + cost tracker | SQLite (FTS5) + SessionDB | SQLite + MEMORY.md |
|
||||
| 上下文压缩 | 微压缩 + 自动压缩 + 手动压缩 | ContextCompressor | 4 层压缩 (微/snip/auto/aggro) |
|
||||
|
||||
### 1.2 核心结论
|
||||
|
||||
AstroResearch 的 Agent 系统架构本身就是**对标 Claude Code 设计的**——`StreamingToolExecutor`、
|
||||
`PermissionChecker`、`HookRegistry` 都明确标注了参考来源。当前差距主要是**实现深度**而非**设计方向**。
|
||||
|
||||
Claude Code 的参考价值在**工程细节**:流式调度的时机选择、Overflow 的自动修复、Classifier
|
||||
的并行化设计。Hermes-Agent 的独特价值在**自我进化**(Self-improving Skills)和**多 Profile 隔离**。
|
||||
|
||||
---
|
||||
|
||||
## 2. 工具并发执行模型
|
||||
|
||||
### 2.1 对比
|
||||
|
||||
| 特性 | Claude Code | AstroResearch (当前) |
|
||||
|------|-------------|---------------------|
|
||||
| 流式调度 | tool_use 到达**立即**开始执行 | tool_use 全部收集,`flush()` 批量执行 |
|
||||
| 并发分区 | `partitionToolCalls()` 自动分组连续只读工具并行 | `ToolPartitioner` 存在但基本未使用 |
|
||||
| Sibling Abort | Bash 错误 → 级联取消兄弟工具,有专用 `siblingAbortController` | `AbortReason::SiblingError` + 广播通道存在,取消逻辑不完整 |
|
||||
| Progress 流式 | `pendingProgress` 即时 yield,`progressAvailableResolve` 唤醒等待 | `execute_with_progress` 有通道,`getCompletedResults` 未检查 |
|
||||
| 中断行为 | `interruptBehavior()` 区分 `cancel` vs `block` | `InterruptBehavior` 枚举存在但未在 Executor 中使用 |
|
||||
|
||||
### 2.2 Claude Code 的分区逻辑
|
||||
|
||||
```typescript
|
||||
// src/services/tools/toolOrchestration.ts
|
||||
// 自动将连续只读工具分组并行,写工具独立串行
|
||||
function partitionToolCalls(toolUseMessages, toolUseContext): Batch[] {
|
||||
return toolUseMessages.reduce((acc, toolUse) => {
|
||||
const tool = findToolByName(toolUseContext.options.tools, toolUse.name)
|
||||
const parsedInput = tool?.inputSchema.safeParse(toolUse.input)
|
||||
const isConcurrencySafe = parsedInput?.success
|
||||
? (() => { try { return Boolean(tool.isConcurrencySafe(parsedInput.data)) } catch { return false } })()
|
||||
: false
|
||||
|
||||
if (isConcurrencySafe && acc[acc.length - 1]?.isConcurrencySafe) {
|
||||
acc[acc.length - 1].blocks.push(toolUse) // 合并到当前批次
|
||||
} else {
|
||||
acc.push({ isConcurrencySafe, blocks: [toolUse] }) // 新批次
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
```
|
||||
|
||||
关键点:
|
||||
- **输入感知**的并发安全判断:同一工具可能因参数不同而安全属性不同
|
||||
- 并发安全的工具**连续分组**——不打断写入顺序
|
||||
- 非并发安全的工具**独占执行**——等上一个完成后才启动下一个
|
||||
|
||||
### 2.3 Claude Code 的 StreamingToolExecutor 核心逻辑
|
||||
|
||||
```
|
||||
文件: src/services/tools/StreamingToolExecutor.ts (531 行)
|
||||
|
||||
状态机: Queued → Executing → Completed → Yielded
|
||||
|
||||
生命周期:
|
||||
1. addTool(block) — LLM 流产生 tool_use 时立即调用
|
||||
2. processQueue() — 检查 concurrency 条件,启动可执行工具
|
||||
3. executeTool(tool) — 创建子 AbortController,调用 runToolUse generator
|
||||
4. getCompletedResults() — 按序 yield 结果(非阻塞)
|
||||
5. getRemainingResults() — 等待未完成工具(async generator)
|
||||
|
||||
关键设计:
|
||||
- siblingAbortController: 父 AbortController 的子节点
|
||||
Bash 错误 → siblingAbortController.abort('sibling_error') → 取消所有兄弟
|
||||
但 toolAbortController 的 abort 会向上冒泡到父 AbortController
|
||||
- progressAvailableResolve: Promise resolver 用于唤醒等待 progress 的 getRemainingResults
|
||||
- 中断行为: 'cancel' 工具被用户中断时生成 REJECT_MESSAGE; 'block' 工具不受影响
|
||||
```
|
||||
|
||||
### 2.4 AstroResearch 的现状(2026-06-22 更新)
|
||||
|
||||
```
|
||||
文件: src/agent/runtime/streaming_executor.rs (~400 行,已重写)
|
||||
|
||||
已实现:
|
||||
✅ TrackedTool 状态机 (Queued/Executing/Completed/Yielded)
|
||||
✅ Sibling Abort 广播通道(broadcast::channel + tokio::select! 竞速)
|
||||
✅ on_tool_use / flush / next_result 接口
|
||||
✅ 输出截断
|
||||
✅ 真正的流式调度 — on_tool_use 中对并发安全工具立即 spawn tokio task
|
||||
✅ 非并发安全工具独占执行 — executing_non_concurrent 标志阻塞后续启动
|
||||
✅ 并发取消 — tokio::select! 在工具执行和 Sibling Abort 之间竞速
|
||||
✅ 输入感知的并发安全判断 — 通过 tool_registry.get().is_concurrency_safe(&args)
|
||||
✅ ToolContext 实现 Clone(支持 per-task 复制上下文)
|
||||
|
||||
与 Claude Code 的对齐:
|
||||
- 核心理念一致:addTool → 立即 processQueue
|
||||
- Sibling Abort 机制等效:broadcast::Sender + subscribe
|
||||
- collectCompletedTasks 使用 JoinHandle::is_finished() 做非阻塞检查
|
||||
```
|
||||
|
||||
|
||||
### 2.5 已实施改进(2026-06-22)
|
||||
|
||||
**✅ P0: 真正的流式调度** — 已完成
|
||||
|
||||
`on_tool_use` 中对并发安全工具立即 `tokio::spawn`,非并发安全工具标记 `executing_non_concurrent`
|
||||
并阻塞后续启动,直到独占工具完成。
|
||||
|
||||
**✅ P0: Executor 集成分区器** — 已完成
|
||||
|
||||
`src/agent/runtime/executor.rs` Phase 3 从「所有非拒绝工具单一 `FuturesUnordered` 无差别并发」
|
||||
改为「`ToolPartitioner` 分区 → 逐批次执行」:
|
||||
- 并行批次内 `FuturesUnordered` 并发
|
||||
- 串行批次内逐个执行(非并发安全工具独占)
|
||||
- 提取 `execute_single_tool()` 和 `process_single_result()` 两个辅助函数
|
||||
|
||||
**✅ P1: 并发分区器** — 已完成
|
||||
|
||||
`src/agent/runtime/partitioner.rs` 新增 2 个测试(`run_bash`、`file_write` 打断并发批)。
|
||||
|
||||
### 2.6 原始建议(已过时)
|
||||
|
||||
```rust
|
||||
// 建议在 on_tool_use 中对并发安全的工具立即 spawn
|
||||
pub fn on_tool_use(&mut self, call_id: String, name: String, args: Value) -> bool {
|
||||
let is_concurrency_safe = /* 判断 */
|
||||
let idx = self.tracked.len();
|
||||
self.tracked.push(tool);
|
||||
if is_concurrency_safe && self.can_execute_now() {
|
||||
let handle = tokio::spawn(/* 执行 */);
|
||||
self.tracked[idx].handle = Some(handle);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**P1: 并发分区**
|
||||
|
||||
```rust
|
||||
/// 将 tool_use 列表分区为 (并发安全批次, 非并发安全单例)
|
||||
fn partition_tool_calls(calls: &[PreparedCall], registry: &ToolRegistry) -> Vec<Batch> {
|
||||
calls.iter().fold(Vec::new(), |mut acc, call| {
|
||||
let is_safe = registry.get(&call.tool_name)
|
||||
.map(|t| t.is_concurrency_safe(&call.args))
|
||||
.unwrap_or(false);
|
||||
if is_safe && acc.last().map_or(false, |b: &Batch| b.concurrent) {
|
||||
acc.last_mut().unwrap().calls.push(call.clone());
|
||||
} else {
|
||||
acc.push(Batch { concurrent: is_safe, calls: vec![call.clone()] });
|
||||
}
|
||||
acc
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Permission 系统
|
||||
|
||||
### 3.1 对比
|
||||
|
||||
| 特性 | Claude Code | AstroResearch (当前) |
|
||||
|------|-------------|---------------------|
|
||||
| 规则来源分层 | 6 层优先级:policy > project > user > plugin > flag > command | 单一规则列表 |
|
||||
| 规则行为 | Allow / Deny / Ask | Allow / Deny / Ask ✅ |
|
||||
| Classifier 自动模式 | 两阶段(快速 + 思考),并行于 hooks 启动 | 无 |
|
||||
| 拒绝追踪 | 带时间窗口的限流回退 (DenialTracker) | 简单计数 |
|
||||
| 沙箱集成 | `shouldUseSandbox()` + `sandbox-adapter` | 无沙箱概念 |
|
||||
| 决策溯源 | 每条 PermissionDecisionReason 记录完整来源链 | 只返回 Allow/Deny/Ask |
|
||||
| 权限模式 | 5 种:default, acceptEdits, bypassPermissions, dontAsk, plan | 4 种 ✅ |
|
||||
|
||||
### 3.2 Claude Code 的 Permission 决策管道
|
||||
|
||||
```
|
||||
1. validateInput() — Zod schema 验证
|
||||
2. runPreToolUseHooks() — Session hooks(用户配置的)
|
||||
3. canUseTool — 检查 deny 规则
|
||||
4. resolveHookPermissionDecision() — Allow 规则
|
||||
5. [auto mode] Classifier — 两阶段分类器(Haiku)
|
||||
6. [default mode] 用户弹窗 — 交互式确认
|
||||
7. PermissionDecisionReason — 记录决策来源
|
||||
```
|
||||
|
||||
每个决策都烙印 `PermissionDecisionReason`:
|
||||
```
|
||||
rule | mode | subcommandResults | permissionPromptTool |
|
||||
hook | asyncAgent | sandboxOverride | classifier |
|
||||
workingDir | safetyCheck | other
|
||||
```
|
||||
|
||||
### 3.3 Classifier 系统(最值得借鉴)
|
||||
|
||||
Claude Code 的 auto-mode classifier 是一个**独立的小模型调用**(Haiku),在后台并行运行:
|
||||
|
||||
```
|
||||
Auto Mode 决策流程:
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ 1. startSpeculativeClassifierCheck() │
|
||||
│ └─ 并行于 PreToolUse hooks 启动 │
|
||||
│ 2. 两阶段分类: │
|
||||
│ ├─ Fast: 简单模式匹配(秒级) │
|
||||
│ └─ Thinking: 深度分析(复杂命令时) │
|
||||
│ 3. 结果: Allow / Deny / Ask + confidence │
|
||||
│ 4. DenialTracker: 连续 Deny 后 fallback 用户 │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
AstroResearch 目前没有 auto-mode——所有非白名单工具都需要用户交互确认。
|
||||
|
||||
### 3.4 建议改进
|
||||
|
||||
**P1: Auto-mode Classifier**
|
||||
|
||||
```rust
|
||||
/// Auto-mode 分类器 — 使用廉价模型在后台预分类工具调用
|
||||
pub struct AutoClassifier {
|
||||
llm: LlmClient, // 使用廉价模型(如 Haiku 级别 provider)
|
||||
cache: LruCache<String, ClassificationResult>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClassificationResult {
|
||||
pub decision: PermissionResult,
|
||||
pub confidence: f64,
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
impl AutoClassifier {
|
||||
/// 在工具执行前异步预分类(不阻塞用户)
|
||||
pub async fn preclassify(
|
||||
&self,
|
||||
tool_name: &str,
|
||||
args: &Value,
|
||||
context: &str, // 从 CLAUDE.md 和当前对话提取
|
||||
) -> ClassificationResult {
|
||||
// 构建精简 prompt:
|
||||
// "You are a security classifier. Evaluate this tool call:
|
||||
// Tool: {tool_name}
|
||||
// Args: {args}
|
||||
// Context: {context}
|
||||
// Respond: ALLOW|DENY|ASK <confidence 0-100> <reason>"
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**P1: PermissionRequest / PermissionDenied Hook 事件**
|
||||
|
||||
这两个事件对科研场景的审计至关重要:
|
||||
|
||||
```rust
|
||||
// 在 hooks/types.rs 中添加
|
||||
pub enum HookEvent {
|
||||
// ... 现有事件 ...
|
||||
/// 权限请求前触发(可阻止或修改)
|
||||
PermissionRequest,
|
||||
/// 权限被拒绝后触发(审计日志)
|
||||
PermissionDenied,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Hooks 系统
|
||||
|
||||
### 4.1 对比
|
||||
|
||||
| 特性 | Claude Code | AstroResearch (当前) |
|
||||
|------|-------------|---------------------|
|
||||
| Hook 类型 | 6 种:command, prompt, agent, http, callback, function | 2 种:sync AgentHook + async AsyncAgentHook |
|
||||
| 匹配器 | simple / pipe-separated / regex | glob + 精确匹配 |
|
||||
| if 条件 | `preparePermissionMatcher()` — Bash 上有 tree-sitter | 无 |
|
||||
| 输出协议 | JSON `{continue, decision, reason, suppressOutput, hookSpecificOutput}` | 直接返回值 |
|
||||
| 超时 | 每个 hook 独立超时(默认 10min) | 统一 `DEFAULT_HOOK_TIMEOUT` |
|
||||
| 事件数量 | 27 种 | ~15 种 |
|
||||
| 来源 | config + plugin + SDK + session + function | registry + session |
|
||||
|
||||
### 4.2 Claude Code 的 27 种 Hook 事件
|
||||
|
||||
```
|
||||
生命周期类:
|
||||
SessionStart, Setup, SubagentStart, SubagentStop, SessionEnd, Stop, StopFailure
|
||||
|
||||
用户交互类:
|
||||
UserPromptSubmit, Elicitation, ElicitationResult, PermissionRequest, PermissionDenied
|
||||
|
||||
工具执行类:
|
||||
PreToolUse, PostToolUse, PostToolUseFailure
|
||||
|
||||
上下文类:
|
||||
PreCompact, PostCompact, InstructionsLoaded
|
||||
|
||||
环境监控类:
|
||||
FileChanged, CwdChanged, ConfigChange
|
||||
|
||||
Swarm/Team 类:
|
||||
TeammateIdle, TaskCreated, TaskCompleted
|
||||
|
||||
UI 类:
|
||||
Notification, StatusLine, FileSuggestion
|
||||
```
|
||||
|
||||
### 4.3 AstroResearch 缺失的关键事件
|
||||
|
||||
| 缺失事件 | 用途 | 优先级 | 状态 |
|
||||
|---------|------|--------|------|
|
||||
| `UserPromptSubmit` | 用户提交 prompt 前拦截(自动上下文注入) | P2 | ⏳ |
|
||||
| `Notification` | 长时间操作完成通知 | P1 | ⏳ |
|
||||
| `PermissionRequest` | 权限弹窗前触发 | P1 | ✅ 已实现 |
|
||||
| `PermissionDenied` | 权限被拒绝后记录审计 | P1 | ✅ 已实现 |
|
||||
| `PreCompact` | 上下文压缩前机会(保存重要信息) | P2 | ⏳ |
|
||||
| `PostCompact` | 上下文压缩后通知(更新外部状态) | P2 | ⏳ |
|
||||
|
||||
### 4.4 已实施改进(2026-06-22)
|
||||
|
||||
**✅ P1: PermissionRequest / PermissionDenied 事件** — 已完成
|
||||
|
||||
新增类型(`src/agent/hooks/types.rs`):
|
||||
- `HookEvent::PermissionRequest` / `HookEvent::PermissionDenied`
|
||||
- `PermissionRequestContext` — 携带 `current_decision`、`permission_mode`、`is_subagent`
|
||||
- `PermissionDeniedContext` — 携带 `reason`、`source` (Rule/Classifier/User/Timeout)
|
||||
- `PermissionRequestAction` — Continue / Override / InjectContext
|
||||
- `PermissionDecision` / `PermissionDenialSource` 枚举
|
||||
|
||||
新增 trait 方法(`src/agent/hooks/traits.rs`):
|
||||
- `AgentHook::on_permission_request()` → `PermissionRequestAction`
|
||||
- `AgentHook::on_permission_denied()` → void (审计日志)
|
||||
|
||||
新增调度方法(`src/agent/hooks/dispatch.rs`):
|
||||
- `HookRegistry::run_on_permission_request()` — 并行调用,第一个 Override 生效
|
||||
- `HookRegistry::run_on_permission_denied()` — fire-and-forget 审计
|
||||
|
||||
### 4.5 原始建议(部分已过时)
|
||||
|
||||
原建议的 context 设计已被更完善的实现替代:
|
||||
}
|
||||
```
|
||||
|
||||
**P2: 支持 command 类型 hook**
|
||||
|
||||
```rust
|
||||
/// Command 类型 hook — 执行外部 shell 命令处理事件
|
||||
pub struct CommandHook {
|
||||
command: String, // 如 "python3 audit.py"
|
||||
timeout: Duration, // 默认 10min
|
||||
shell: HookShell, // Bash | PowerShell
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Error Recovery / 重试系统
|
||||
|
||||
### 5.1 对比
|
||||
|
||||
| 特性 | Claude Code | AstroResearch (当前) |
|
||||
|------|-------------|---------------------|
|
||||
| 重试结构 | Generator 模式,yield 系统消息直到不可重试 | ErrorRecovery 枚举 + 简单决策 |
|
||||
| 退避算法 | 指数 + 25% jitter,可配置上限(32s 默认,5min 持久) | 固定退避 |
|
||||
| 错误分类 | `shouldRetry()` 检查 15+ 种错误,每种不同策略 | 8 步分类管线 |
|
||||
| 529 Overloaded | 3 次重试 → 模型回退(Opus→Sonnet)→ 持久化重试 | 无模型回退 |
|
||||
| Context Overflow | 解析 "X + Y > Z",自动调整 max_tokens + 1000 安全缓冲 | 只分类不修复 |
|
||||
| 持久化重试 | 无限重试 + 30s 心跳 | 无 |
|
||||
| Fast Cooldown | 429/529 在 fast mode → retry-after <20s → 10min cooldown | N/A |
|
||||
|
||||
### 5.2 Context Overflow 自动修复(最值得借鉴)
|
||||
|
||||
Claude Code 的做法:
|
||||
|
||||
```typescript
|
||||
// src/services/api/withRetry.ts
|
||||
// 解析 Anthropic API 的错误消息:
|
||||
// "input length and max_tokens exceed context limit: 180000 + 32000 > 200000"
|
||||
// → 计算安全的 max_tokens = 200000 - 180000 - 1000(safety) = 19000
|
||||
|
||||
function parseContextOverflowError(errorMessage: string) {
|
||||
const match = errorMessage.match(
|
||||
/input length and max_tokens exceed context limit: (\d+) \+ (\d+) > (\d+)/
|
||||
);
|
||||
if (match) {
|
||||
const [, inputLen, maxTokens, contextLimit] = match.map(Number);
|
||||
const newMaxTokens = contextLimit - inputLen - SAFETY_MARGIN;
|
||||
if (newMaxTokens > MIN_TOKENS) {
|
||||
return { shouldRetry: true, adjustedMaxTokens: newMaxTokens };
|
||||
}
|
||||
}
|
||||
return { shouldRetry: false };
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 已实施改进(2026-06-22)
|
||||
|
||||
**✅ P0: Context Overflow 自动修复** — 已完成
|
||||
|
||||
新增公共 API(`src/agent/runtime/error_recovery.rs`):
|
||||
- `ContextOverflowInfo` — 从错误消息解析的数值结构体
|
||||
- `parse_context_overflow()` — 支持 Anthropic/OpenAI/通用三种格式
|
||||
- `calculate_safe_max_tokens()` — `context_limit - input_length - SAFETY_MARGIN(1000)`
|
||||
- `RecoveryStep::AdjustMaxTokens { new_max_tokens }` — 恢复管线第 0 步
|
||||
- `extract_three_numbers()` — 正则匹配 "A + B > C" 模式(使用已有 `regex` crate)
|
||||
|
||||
`AgentRuntime` 集成(`src/agent/runtime/mod.rs`):
|
||||
```rust
|
||||
let overflow_info = error_recovery::parse_context_overflow(&e_str);
|
||||
while let Some(recovery_step) = recovery.try_recover(&error_kind, overflow_info.as_ref()) {
|
||||
// AdjustMaxTokens 优先于 AggressiveCompact,仅无空间时才回退到压缩
|
||||
}
|
||||
```
|
||||
|
||||
12 个新增测试覆盖 Anthropic/OpenAI/Generic 格式、边界条件、恢复优先级。
|
||||
|
||||
### 5.4 原始建议(部分已过时)
|
||||
|
||||
**P1: 模型回退策略**
|
||||
|
||||
```rust
|
||||
/// 模型回退链 — 529/Overloaded 时自动降级
|
||||
pub struct ModelFallback {
|
||||
chain: Vec<ModelTier>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ModelTier {
|
||||
Primary(String), // 如 "claude-opus-4-8"
|
||||
Fallback(String), // 如 "claude-sonnet-4-6"
|
||||
Emergency(String), // 如 "claude-haiku-4-5"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Tool 定义系统
|
||||
|
||||
### 7.1 对比
|
||||
|
||||
| 特性 | Claude Code `Tool<T>` | AstroResearch `AgentTool` trait |
|
||||
|------|----------------------|-------------------------------|
|
||||
| 并发安全 | `isConcurrencySafe(input)` — 输入感知 ✅ | `is_concurrency_safe(args)` — ✅ |
|
||||
| 语义标记 | `isReadOnly()` / `isDestructive()` / `isConcurrencySafe()` | `causes_sibling_abort()` / `interrupt_behavior()` |
|
||||
| 权限逻辑 | `checkPermissions()` — 工具自己决定权限 | 集中在 PermissionChecker |
|
||||
| 分类器输入 | `toAutoClassifierInput()` — 精简信息 | 无 |
|
||||
| 延迟加载 | `shouldDefer` / `alwaysLoad` — 减小 prompt | 无 |
|
||||
| 搜索提示 | `searchHint` — 帮助 ToolSearch 匹配 | 无 |
|
||||
| UI 渲染 | `renderToolUseMessage/Result/Progress/Error` (6 种) | 前端独立处理 |
|
||||
| 中断行为 | `interruptBehavior()` — cancel vs block | `interrupt_behavior()` ✅ |
|
||||
| 输出大小 | `maxResultSizeChars` — 超限存磁盘 | env var 全局配置 |
|
||||
| 权限匹配器 | `preparePermissionMatcher()` — 工具级模式匹配 | HookMatcher |
|
||||
|
||||
### 7.2 Claude Code 的 `buildTool()` Factory
|
||||
|
||||
```typescript
|
||||
// 每个工具通过 buildTool 创建,自动填充安全默认值
|
||||
const myTool: Tool = buildTool({
|
||||
name: 'MyTool',
|
||||
inputSchema: z.object({ ... }),
|
||||
async call(input, context, toolUseId) { ... },
|
||||
// 以下都有安全默认值,按需覆盖:
|
||||
// isEnabled: true (可根据 permission mode 禁用)
|
||||
// isConcurrencySafe: false
|
||||
// isReadOnly: false
|
||||
// isDestructive: false
|
||||
// checkPermissions: {behavior: 'allow'} (最宽松)
|
||||
// toAutoClassifierInput: '' (不参与分类)
|
||||
// shouldDefer: false (立即加载)
|
||||
// interruptBehavior: 'block' (不可中断)
|
||||
})
|
||||
```
|
||||
|
||||
### 7.3 建议改进
|
||||
|
||||
**P2: 为 AgentTool trait 添加方法**
|
||||
|
||||
```rust
|
||||
pub trait AgentTool: Send + Sync {
|
||||
// ... 现有方法 ...
|
||||
|
||||
/// 返回用于 auto-mode 分类器的精简摘要
|
||||
fn classifier_summary(&self, args: &Value) -> String {
|
||||
format!("{}", self.name())
|
||||
}
|
||||
|
||||
/// 是否是只读操作(与并发安全不同——glob 是 readonly 但不能和 bash 并发)
|
||||
fn is_readonly(&self) -> bool { false }
|
||||
|
||||
/// 是否需要延迟加载工具描述(大工具可延迟以减小 prompt)
|
||||
fn defer_loading(&self) -> bool { false }
|
||||
}
|
||||
```
|
||||
|
||||
**P3: 工具 allow/deny 列表**
|
||||
|
||||
参考 Claude Code 的做法,AstroResearch 已有 `subagent_allowed_tools`,可扩展:
|
||||
|
||||
```rust
|
||||
// 为子代理/异步代理定义工具过滤策略
|
||||
pub struct ToolFilterPolicy {
|
||||
/// 全局禁止(不计代理类型)
|
||||
pub all_agent_disallowed: Vec<String>,
|
||||
/// 自定义代理禁用(不能 spawn 子代理 + 编辑文件的代理)
|
||||
pub custom_agent_disallowed: Vec<String>,
|
||||
/// 异步代理白名单(只读子集)
|
||||
pub async_agent_allowed: Vec<String>,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Memory 持久化
|
||||
|
||||
### 8.1 对比
|
||||
|
||||
AstroResearch 与 Claude Code 在 Memory 设计上高度相似(都是文件系统 frontmatter markdown +
|
||||
`MEMORY.md` 索引),差距很小。
|
||||
|
||||
Claude Code 多了:
|
||||
- **Team Memory**: 共享给团队的记忆(AstroResearch 有 team 系统但无 team memory)
|
||||
- **Session Memory compaction**: 压缩时自动通过 LLM 提取记忆到文件
|
||||
|
||||
### 8.2 建议改进
|
||||
|
||||
**P3: Session Memory Compaction**
|
||||
|
||||
压缩时自动提取记忆:
|
||||
|
||||
```rust
|
||||
/// 在 compact.rs 的 auto_compact 过程中提取 session memory
|
||||
pub async fn extract_session_memory(
|
||||
llm: &LlmClient,
|
||||
messages: &[ChatMessage],
|
||||
) -> Vec<MemoryExtraction> {
|
||||
// 用专门的小 prompt 让 LLM 从对话中提取可持久化的记忆
|
||||
// 返回候选记忆列表,由 MemoryManager 做 dedup + 衰减
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Coordinator / Multi-Agent
|
||||
|
||||
### 9.1 Claude Code 的 Coordinator Mode
|
||||
|
||||
```
|
||||
Coordinator Mode 架构:
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Coordinator (Coordinator System Prompt) │
|
||||
│ Tools: Agent, SendMessage, TaskStop, │
|
||||
│ SyntheticOutput (仅 4 个) │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Worker 1 │ │ Worker 2 │ │ Worker 3 │ │
|
||||
│ │ (async) │ │ (async) │ │ (async) │ │
|
||||
│ │ standard │ │ standard │ │ standard │ │
|
||||
│ │ tools │ │ tools │ │ tools │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
│ Worker 结果以 <task-notification> 返回 │
|
||||
│ Coordinator 做 Synthesis → 下一轮 Workers │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 9.2 关键设计点
|
||||
|
||||
1. **Coordinator 只看到 4 个工具**——它不能直接读文件或执行 bash,只能编排 Workers
|
||||
2. **Workers 全异步**——Coordinator 不等待,结果以 `<task-notification>` XML 注入
|
||||
3. **Continue-vs-Spawn 决策矩阵**:
|
||||
- 新任务与已有 Worker 上下文重叠 <30% → 创建新 Worker
|
||||
- 新任务是对已有 Worker 的跟进 → `SendMessage` 继续
|
||||
4. **Worker prompt 写法规范**:自包含(self-contained),包含完整 spec,明确交付物
|
||||
|
||||
### 9.3 AstroResearch 的现状
|
||||
|
||||
AstroResearch 有 `SubAgentTool` + `TeamManager`,但没有 Coordinator 的概念。Team
|
||||
是平级的(lead ↔ teammates),不是层级编排。
|
||||
|
||||
### 9.4 建议改进
|
||||
|
||||
**P2: Coordinator Mode 原型**
|
||||
|
||||
```
|
||||
当 Agent 检测到复杂多步骤任务时,自动切换为 Coordinator 模式:
|
||||
|
||||
用户请求
|
||||
→ Coordinator 做任务分解
|
||||
→ 并行子代理执行 (SubAgentTool, async)
|
||||
→ 结果综合
|
||||
→ 减少单 Agent 的步骤数和 token 消耗
|
||||
|
||||
关键实现:
|
||||
1. Coordinator system prompt: 类似 Claude Code coordinatorMode.ts
|
||||
2. 仅暴露 SubAgentTool + 少量管理工具
|
||||
3. 子代理结果以结构化格式注入
|
||||
4. 综合阶段由 Coordinator 处理
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Hermes-Agent 的独特贡献
|
||||
|
||||
### 10.1 文件位置
|
||||
|
||||
`/home/fmq/program/AstroResearch/libs/hermes-agent/`
|
||||
|
||||
### 10.2 Hermes-Agent vs Claude Code 架构对比
|
||||
|
||||
| 关注点 | Hermes-Agent | Claude Code |
|
||||
|--------|-------------|-------------|
|
||||
| 语言 | Python 3.11+ | TypeScript (Node.js 20+) |
|
||||
| Agent 循环 | 同步 `while` 循环 | 异步 `query()` generator |
|
||||
| 工具注册 | 文件系统自动发现 (`tools/*.py`) | 手动 import + `getAllBaseTools()` |
|
||||
| 工具接口 | `handler(args) -> JSON string` | `Tool<T>` — ~70 方法 |
|
||||
| 状态管理 | SQLite (SessionDB + FTS5) | 内存 `AppState` React store |
|
||||
| Plugin 系统 | PluginManager + 生命周期 hooks | Plugin loader + MCP 集成 |
|
||||
| Profile 隔离 | 多 profile + 独立 `HERMES_HOME` | 单 profile |
|
||||
| 子代理 | 子 `AIAgent` 实例 | `LocalAgentTask` + `runAgent()` |
|
||||
| Swarm/Team | Kanban 工作队列 | `InProcessTeammateTask` + coordinator |
|
||||
| 上下文压缩 | `ContextCompressor` | `compact/` 服务 |
|
||||
| MCP 支持 | `mcp_tool.py` + catalog | 完整的 `services/mcp/` |
|
||||
| 定位 | 个人 AI 助手(自我改进、记忆、跨平台) | 编程 AI 助手(终端集成、文件操作) |
|
||||
|
||||
### 10.3 Hermes-Agent 的核心架构
|
||||
|
||||
```
|
||||
hermes-agent/
|
||||
├── run_agent.py # AIAgent 类 (~11k LOC) — 核心入口
|
||||
├── model_tools.py # 工具编排层 (~2.7k LOC)
|
||||
├── toolsets.py # 工具集定义
|
||||
├── hermes_state.py # SessionDB — SQLite + FTS5
|
||||
├── cli.py # HermesCLI 类 (~11k LOC)
|
||||
│
|
||||
├── agent/ # Agent 内部
|
||||
│ ├── conversation_loop.py # 主循环
|
||||
│ ├── turn_context.py # 每轮上下文 dataclass
|
||||
│ ├── system_prompt.py # 三层 System Prompt
|
||||
│ ├── prompt_builder.py # Prompt 片段构建
|
||||
│ ├── memory_manager.py # 记忆编排
|
||||
│ ├── context_compressor.py # 上下文压缩
|
||||
│ ├── tool_executor.py # 工具执行分发
|
||||
│ ├── tool_guardrails.py # 安全 guardrails
|
||||
│ └── curator.py # 后台 Skill 生命周期管理
|
||||
│
|
||||
├── tools/ # 工具实现(自动发现)
|
||||
│ ├── registry.py # ToolRegistry(discover_builtin_tools)
|
||||
│ ├── delegate_tool.py # 子代理 spawn
|
||||
│ ├── skills_tool.py # Skill 管理
|
||||
│ └── cronjob_tools.py # Cron 调度
|
||||
│
|
||||
└── hermes_cli/ # CLI 子系统
|
||||
├── plugins.py # PluginManager + 生命周期 hooks
|
||||
└── profiles.py # 多 Profile 隔离
|
||||
```
|
||||
|
||||
### 10.4 最值得借鉴的 Hermes 特性
|
||||
|
||||
**Self-improving Skills**
|
||||
|
||||
```
|
||||
工作流:
|
||||
1. Agent 在科研中反复使用某流程
|
||||
(如: 搜索某类天体 → 下载论文 → RAG → 总结)
|
||||
2. Agent 自动检测重复模式
|
||||
3. Agent 创建 Skill 保存该流程
|
||||
4. Curator 管理 Skill 生命周期
|
||||
5. 下次相似查询直接加载 Skill,无需重新探索
|
||||
|
||||
AstroResearch 已有 Skills 系统 (skills.rs),缺少:
|
||||
- 自动检测重复模式
|
||||
- Agent 自主创建 Skill
|
||||
- Curator 管理 Skill 质量
|
||||
```
|
||||
|
||||
**Session FTS5 搜索**
|
||||
|
||||
```python
|
||||
# Hermes 的 SessionDB 使用 SQLite FTS5 实现跨 session 搜索
|
||||
class SessionDB:
|
||||
def search_sessions(self, query: str) -> List[Session]:
|
||||
"""全文本搜索所有历史会话"""
|
||||
return self.db.execute(
|
||||
"SELECT * FROM sessions WHERE sessions MATCH ?", (query,)
|
||||
)
|
||||
```
|
||||
|
||||
AstroResearch 的 `agent_sessions` 表有基本的 title/status 字段,但没有全文搜索。
|
||||
|
||||
### 10.5 建议改进
|
||||
|
||||
**P2: Self-improving Skills 原型**
|
||||
|
||||
```
|
||||
1. Pattern Detector (自动检测)
|
||||
- 监控 N 个 session 中的工具调用序列
|
||||
- 使用简单的子序列匹配识别重复模式
|
||||
- 阈值: 3 次相似序列 → 候选 Skill
|
||||
|
||||
2. Skill Creator (Agent 自主创建)
|
||||
- 将候选 Skill 展示给用户确认
|
||||
- 生成 SKILL.md 文件(含 when_to_use, steps)
|
||||
- 注册到 SkillRegistry
|
||||
|
||||
3. Curator (管理生命周期)
|
||||
- 跟踪 Skill 使用频率
|
||||
- 长时间未用的 Skill 标记为 stale
|
||||
- 提示用户审查或删除
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 优先级排序 —— 建议实施路线
|
||||
|
||||
按价值/投入比排序:
|
||||
|
||||
| 优先级 | 改进项 | 来源 | 状态 | 价值 | 预估投入 | 依赖 |
|
||||
|--------|--------|------|------|------|---------|------|
|
||||
| **P0** | Context Overflow 自动修复 | Claude Code | ✅ | 减少 ~50% LLM 调用失败 | ~30 行 | 无 |
|
||||
| **P0** | StreamingExecutor 真正的流式调度 | Claude Code | ✅ | 减少 30-50% 工具执行延迟 | ~200 行 | 无 |
|
||||
| **P0** | Executor 集成分区器(批次串行/并行) | Claude Code | ✅ | 修复非安全工具错误并发 | ~300 行 | ToolPartitioner |
|
||||
| **P1** | 工具并发分区 `partition_tool_calls` | Claude Code | ✅ | 批量只读操作 3-5x 加速 | ~100 行 | 无 |
|
||||
| **P1** | PermissionRequest / PermissionDenied Hook 事件 | Claude Code | ✅ | 安全审计能力 | ~100 行 | 无 |
|
||||
| **P1** | Auto-mode Classifier(廉价模型预分类) | Claude Code | ⏳ | 消除 80%+ 权限弹窗 | ~300 行 | LLM client 支持 |
|
||||
| **P2** | Self-improving Skills(Agent 保存成功流程) | Hermes | ✅ | 科研场景独特价值 | ~500 行 | Skills 系统 |
|
||||
| **P2** | Coordinator Mode(层级多 Agent 编排) | Claude Code | ⏳ | 复杂任务效果提升 | ~800 行 | SubAgent + Team |
|
||||
| **P2** | UserPromptSubmit / PreCompact / PostCompact Hook | Claude Code | ⏳ | Hook 系统完善 | ~200 行 | 无 |
|
||||
| **P3** | FTS5 跨 session 搜索 | Hermes | ⏳ | 历史研究可复用 | 中 | SQLite 迁移 |
|
||||
| **P3** | Tool `defer_loading` / `classifier_summary` | Claude Code | ⏳ | 减小 tool schema prompt | ~50 行 | 无 |
|
||||
| **P3** | 模型回退策略 (Model Fallback) | Claude Code | ⏳ | 提高可用性 | ~200 行 | ErrorRecovery |
|
||||
| **P3** | Session Memory Compaction | Claude Code | ⏳ | 自动化记忆提取 | ~300 行 | MemoryManager + Compact |
|
||||
|
||||
### 实施进度(2026-06-22)
|
||||
|
||||
1. **P0 项 — 全部完成** ✅
|
||||
- Context Overflow 自动修复(`error_recovery.rs`)
|
||||
- StreamingExecutor 真正流式调度(`streaming_executor.rs` 重写)
|
||||
- Executor 集成分区器(`executor.rs` Phase 3 重写)
|
||||
2. **P1 项 — 部分完成**
|
||||
- ✅ 工具并发分区
|
||||
- ✅ PermissionRequest / PermissionDenied Hook 事件
|
||||
- ⏳ Auto-mode Classifier — 需要设计讨论
|
||||
3. **P2 项在下一个大版本规划**:需要设计讨论和更多测试
|
||||
4. **P3 项作为 backlog**:长期优化方向
|
||||
|
||||
---
|
||||
|
||||
## 附录 A: Claude Code 关键源码索引
|
||||
|
||||
| 文件 | 用途 | 与 AstroResearch 对应 |
|
||||
|------|------|----------------------|
|
||||
| `src/Tool.ts` | Tool 类型 + buildTool factory | `src/agent/tools/mod.rs` |
|
||||
| `src/tools.ts` | 工具注册 + assembleToolPool | `ToolRegistry::new()` |
|
||||
| `src/services/tools/toolExecution.ts` | 工具执行管道 | `executor.rs` |
|
||||
| `src/services/tools/toolOrchestration.ts` | 并发分区 + 批量执行 | `partitioner.rs` + `executor.rs` |
|
||||
| `src/services/tools/StreamingToolExecutor.ts` | 流式工具执行 | `streaming_executor.rs` |
|
||||
| `src/services/api/withRetry.ts` | 错误重试 + 退避 | `error_recovery.rs` |
|
||||
| `src/services/api/claude.ts` | API 流式调用 | `streaming.rs` |
|
||||
| `src/constants/prompts.ts` | System Prompt 构建 | `system_prompt.rs` |
|
||||
| `src/utils/hooks.ts` | Hook 执行引擎 (5022 行) | `hooks/dispatch.rs` |
|
||||
| `src/utils/permissions/permissions.ts` | 权限逻辑 | `permission.rs` |
|
||||
| `src/utils/permissions/yoloClassifier.ts` | Auto-mode 分类器 | 无 (建议新增) |
|
||||
| `src/tools/AgentTool/runAgent.ts` | 子代理执行引擎 | `subagent.rs` |
|
||||
| `src/tools/AgentTool/AgentTool.tsx` | 子代理编排 | `tools/subagent.rs` |
|
||||
| `src/coordinator/coordinatorMode.ts` | Coordinator 模式 | 无 (建议参考) |
|
||||
| `src/memdir/memdir.ts` | Memory 文件系统 | `memory/mod.rs` |
|
||||
| `src/skills/loadSkillsDir.ts` | Skill 加载 | `skills.rs` |
|
||||
|
||||
## 附录 B: Hermes-Agent 关键源码索引
|
||||
|
||||
| 文件 | 用途 | 与 AstroResearch 对应 |
|
||||
|------|------|----------------------|
|
||||
| `run_agent.py` | AIAgent 核心类 | `runtime/mod.rs` |
|
||||
| `agent/conversation_loop.py` | Agent 主循环 | `runtime/mod.rs` (ReAct loop) |
|
||||
| `agent/system_prompt.py` | 三层 System Prompt | `system_prompt.rs` |
|
||||
| `agent/context_compressor.py` | 上下文压缩 | `compact.rs` |
|
||||
| `agent/memory_manager.py` | 记忆编排 | `memory/mod.rs` |
|
||||
| `agent/curator.py` | Skill 生命周期管理 | 无 (建议参考) |
|
||||
| `tools/registry.py` | 工具自动发现 | `tools/mod.rs` |
|
||||
| `tools/delegate_tool.py` | 子代理 | `subagent.rs` |
|
||||
| `hermes_state.py` | SessionDB + FTS5 | `api/agent.rs` (sessions) |
|
||||
| `hermes_cli/plugins.py` | PluginManager | 无 |
|
||||
| `hermes_cli/profiles.py` | 多 Profile 隔离 | 无 |
|
||||
| `model_tools.py` | 工具编排 | `executor.rs` |
|
||||
|
||||
## 附录 C: 变更日志
|
||||
|
||||
### 2026-06-22 — 首轮实施
|
||||
|
||||
**P0: Context Overflow 自动修复**
|
||||
- `src/agent/runtime/error_recovery.rs`: +150 行
|
||||
- 新增 `ContextOverflowInfo`、`parse_context_overflow()`、`calculate_safe_max_tokens()`
|
||||
- 新增 `RecoveryStep::AdjustMaxTokens`、`extract_three_numbers()`
|
||||
- 12 个新增测试(Anthropic/OpenAI/Generic 格式 + 边界 + 恢复优先级)
|
||||
- `src/agent/runtime/mod.rs`: AgentRuntime 集成调用点
|
||||
- `Cargo.lock`: 无新增依赖(使用已有 `regex` crate)
|
||||
|
||||
**P0: StreamingExecutor 真正流式调度**
|
||||
- `src/agent/runtime/streaming_executor.rs`: 重写 ~400 行(原 316 行)
|
||||
- `on_tool_use` 中对并发安全工具立即 `tokio::spawn`
|
||||
- `tokio::select!` 在工具执行和 Sibling Abort 之间竞速
|
||||
- `collect_completed_tasks()` 使用 `JoinHandle::is_finished()` 非阻塞检查
|
||||
- `executing_non_concurrent` 标志实现独占执行
|
||||
- `src/agent/tools/mod.rs`: `ToolContext` 添加 `Clone` derive
|
||||
|
||||
**P0: Executor 集成分区器**
|
||||
- `src/agent/runtime/executor.rs`: Phase 3 重写 + 2 个提取函数
|
||||
- Phase 3a: 构建非拒绝工具的 (原索引, PreparedCall) 映射
|
||||
- Phase 3b: `ToolPartitioner::partition()` 分区
|
||||
- Phase 3c: 逐批次执行(并行批次 `FuturesUnordered`,串行批次逐个执行)
|
||||
- 提取 `execute_single_tool()` 和 `process_single_result()` 辅助函数
|
||||
- `src/agent/runtime/partitioner.rs`: +2 测试(`run_bash`、`file_write` 打断并发批)
|
||||
|
||||
**P1: PermissionRequest / PermissionDenied Hook 事件**
|
||||
- `src/agent/hooks/types.rs`: +80 行
|
||||
- 新增 `HookEvent::PermissionRequest`、`HookEvent::PermissionDenied`
|
||||
- 新增 `PermissionRequestContext`、`PermissionDeniedContext`
|
||||
- 新增 `PermissionRequestAction`、`PermissionDecision`、`PermissionDenialSource`
|
||||
- `src/agent/hooks/traits.rs`: +20 行
|
||||
- `AgentHook::on_permission_request()`、`AgentHook::on_permission_denied()`
|
||||
- `src/agent/hooks/dispatch.rs`: +120 行
|
||||
- `run_on_permission_request()` (并行调用,第一个 Override 生效)
|
||||
- `run_on_permission_denied()` (fire-and-forget 审计)
|
||||
- `src/agent/hooks/mod.rs`: 更新 re-exports
|
||||
|
||||
### 2026-06-22 — Self-improving Skills
|
||||
|
||||
**P2: Self-improving Skills(模式检测 + 自动创建 + Curator)**
|
||||
- `src/agent/skills/pattern_detector.rs`: +420 行
|
||||
- `PatternDetector` — 从 `agent_messages` DB 表扫描工具调用序列
|
||||
- `DetectedPattern` — 跨 session 重复模式(携带出现次数、置信度、指纹)
|
||||
- 滑动窗口子序列提取 + Jaccard 相似度去重 + 超序列包含检测
|
||||
- 8 个单元测试(子序列提取、指纹、去重、Jaccard 计算)
|
||||
- `src/agent/skills/curator.rs`: +700 行
|
||||
- `Curator` — Skill 生命周期管理(Active → Inactive → Stale → Deprecated)
|
||||
- `SkillQuality` — 基于调用次数和新鲜度的质量评分(对数 + 指数衰减)
|
||||
- `CuratorReport` — 分析报告 + 清理建议列表
|
||||
- `archive_stale_skills()` — 将过期 skill 移动到归档目录
|
||||
- `CuratorRunner` — 后台空闲触发审查(`should_run_now()` + `record_activity()` 心跳)
|
||||
- 12 个单元测试(5 种生命周期状态 + 质量评分 + 清理候选 + pinned + runner)
|
||||
- `src/agent/skills.rs`: +230 行
|
||||
- `SkillCreator` — 将 `DetectedPattern` 转为 SKILL.md 文件(kebab-case 命名 + YAML frontmatter)
|
||||
- `SelfImprovePipeline` — 一站式管道(检测 → 创建 → 质量审查)
|
||||
- `SelfImproveResult` — 管道结果(patterns_found + skills_created + curator_report)
|
||||
- `SkillFrontmatter` + `SkillMeta` 增加 `pinned` 字段
|
||||
|
||||
### 2026-06-22 — Hermes Curator 特性补齐
|
||||
|
||||
**Pinned Skills(不可清理)**
|
||||
- `src/agent/skills.rs`: `SkillFrontmatter.pinned` + `SkillMeta.pinned` — YAML `pinned: true`
|
||||
- `src/agent/skills/curator.rs`: `evaluate_quality(pinned)` — 强制 Active + min_score 0.8;`analyze` 过滤 pinned 不进入 cleanup_candidates
|
||||
|
||||
**Seed Record(新 Skill 锚定时钟)**
|
||||
- `src/agent/skills/curator.rs`: `evaluate_quality` 对无统计记录 skill 设置 `days_since_last_use=Some(0)`(等效刚创建),`NEW_SKILL_GRACE_PERIOD_DAYS=7` 防止立即 stale
|
||||
|
||||
**CuratorRunner(后台空闲触发审查)**
|
||||
- `src/agent/skills/curator.rs`: `CuratorRunner` 结构体 + `should_run_now()`(paused/idle/interval 三重检查)+ `record_activity()` 心跳 + `run_once()` + `spawn()` tokio 后台任务 + `pause()`/`resume()`
|
||||
- 5 个新增测试(pinned_always_active、pinned_not_in_cleanup、seed_record、runner_paused、runner_idle)
|
||||
+822
-175
File diff suppressed because it is too large
Load Diff
@@ -133,6 +133,29 @@ graph TD
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 多源权限优先级 (Multi-Source Permission Precedence)
|
||||
|
||||
当多个来源(Checker 规则、Hook、工具级规则、会话规则)同时做出权限决策时,
|
||||
`resolve_permission_precedence()` 按以下优先级裁决:
|
||||
|
||||
| Priority | Source | Description |
|
||||
|----------|--------|-------------|
|
||||
| **P0** (highest) | `PermissionChecker::Deny` | 环境变量/配置文件配置的 Deny 规则,不可覆盖 |
|
||||
| **P1** | Tool-level `PermissionRule::Deny` | 工具自身拒绝执行(如 Bash 危险命令) |
|
||||
| **P2** | Session-level `PermissionChecker::Deny` | API 动态添加的会话级 Deny |
|
||||
| **P3** | Hook `PreToolUseAction::Block` | Hook 主动阻止工具执行 |
|
||||
| **P4** | Hook `PermissionRequired` | 仅当 Checker 返回 Allowed 时升级为 AskUser |
|
||||
| **P5** | Session-level `PermissionChecker::Ask` | 仅当当前为 Allowed 时升级 |
|
||||
| **P6** | Tool-level `PermissionRule::Ask` | 仅当当前为 Allowed 时升级 |
|
||||
| **P7** | `PermissionChecker::Allow` | 显式 Allow 规则 |
|
||||
| **P8** (lowest) | Implicit Allow (default) | 无任何规则匹配 → 允许 |
|
||||
|
||||
**关键规则:**
|
||||
- **Deny 不可覆盖**: P0-P2 的 Deny 规则在任何情况下生效
|
||||
- **Ask 可升级**: P4-P6 在 Allow 状态下升级为 Ask;在 Deny 状态下被忽略
|
||||
- **Block = Deny**: Hook Block 等同于 Deny,由 P0-P2 可覆盖
|
||||
- **冲突日志**: `conflict_log` 记录所有被覆盖的决策,用于审计```
|
||||
|
||||
### PermissionChecker API
|
||||
|
||||
```rust
|
||||
@@ -433,7 +456,7 @@ flowchart TD
|
||||
| `grep "ssh_config" *.rs` | ❌ 误拦(含子串 `ssh `) | ✅ 允许(首词 `grep` 在白名单) |
|
||||
| `echo "use sudo carefully"` | ❌ 误拦(含子串 `sudo `) | ✅ 允许(首词 `echo` 在白名单) |
|
||||
| `cat /usr/share/vim/vimrc` | ❌ 误拦(含子串 `vim `) | ✅ 允许(首词 `cat` 在白名单) |
|
||||
| `python script.py` | ✅ 允许 | ✅ 允许(不在黑名单,默认允许) |
|
||||
| `python script.py` | ✅ 允许 | ⚠️ 通过校验,但触发 `Ask` 用户确认(不在白名单) |
|
||||
| `vim file.txt` | ✅ 拒绝 | ✅ 拒绝(首词 `vim` 在黑名单) |
|
||||
| `$(echo sud; echo o) /etc/passwd` | ✅ 允许(绕过!) | ❌ 拒绝(检测到命令替换绕过) |
|
||||
|
||||
@@ -449,7 +472,7 @@ const SAFE_COMMANDS: &[&str] = &[
|
||||
];
|
||||
```
|
||||
|
||||
白名单内的命令**优先放行**,不经过黑名单检查。非白名单命令经过黑名单精确匹配和危险参数二次检查后默认允许。
|
||||
白名单内的命令**优先放行**,不经过黑名单检查。非白名单命令经黑名单精确匹配和危险参数检查后,通过 `bash_needs_permission()` → `RunBashTool::check_permissions()` 返回 `Ask` 规则,触发用户确认弹窗(PermissionRequestCard)。
|
||||
|
||||
### 其他约束
|
||||
|
||||
@@ -462,7 +485,7 @@ const SAFE_COMMANDS: &[&str] = &[
|
||||
### 已知局限
|
||||
|
||||
1. ~~**黑名单子串匹配**~~ — ✅ 已修复:改用首词精确匹配,`grep "ssh_config"` 不再误拦
|
||||
2. **未限制网络访问** — `curl`、`wget` 不在黑名单中
|
||||
2. ~~**未限制网络访问**~~ — ✅ 已修复:`NETWORK_COMMANDS` 名单(curl/wget/nc/socat 等)默认阻止,`AGENT_BLOCK_NETWORK=false` 可放行
|
||||
3. **未限制进程数** — fork bomb(如 `:(){ :\|:& };:`)未被检测
|
||||
4. **管道/重定向完整放行** — `<`、`>`、`|` 不做限制
|
||||
5. ~~**`$()` 命令替换**~~ — ✅ 已修复:检测首词位置 `$()` 和反引号绕过
|
||||
@@ -684,258 +707,9 @@ Diminishing Returns 检测
|
||||
|
||||
---
|
||||
|
||||
## Claude Code 权限系统对比分析
|
||||
|
||||
> 对比基准:Claude Code (`/home/fmq/program/claudecode/src/utils/permissions/`)
|
||||
> 分析日期:2026-06-17
|
||||
|
||||
### 架构差异总览
|
||||
|
||||
| 维度 | AstroResearch (当前) | Claude Code (参考) | 差距 |
|
||||
|------|---------------------|-------------------|------|
|
||||
| 规则引擎 | ✅ PermissionChecker (完成) | ✅ hasPermissionsToUseTool 多步流水线 | 相当 |
|
||||
| 规则匹配粒度 | ✅ 内容级 `Tool(content*)` 前缀/后缀/包含 | ✅ 前缀/通配/内容级 / 正则 | 小 |
|
||||
| AskUser 交互流 | ✅ SSE → PermissionRequestCard → Allow/Deny/Always Allow | ✅ 完整 SSE → Dialog → 决策 | 相当 |
|
||||
| 权限模式 | ✅ Default/AcceptEdits/Bypass/DontAsk | ✅ 6种模式 (含 plan/auto) | 小 |
|
||||
| 规则持久化 | ✅ 环境变量加载 + `PermissionChecker::from_config()` | ✅ settings.json 多层加载 (8级来源优先级) | 小 |
|
||||
| 规则来源追踪 | ✅ `PermissionRuleSource` 枚举 (Env/Session) | ✅ cliArg > command > session > userSettings > ... | 小 |
|
||||
| Bash 权限分类器 | ✅ SAFE_COMMANDS 白名单 + `check_permissions()` 集成 | ✅ AST解析 + AI分类器 + 异步推测 | 中等 |
|
||||
| 拒绝追踪/熔断 | ✅ `DenialTracker` 连续/累计计数 + ReAct 循环熔断 | ✅ 连续/总计拒绝计数 + 自动终止 | 相当 |
|
||||
| 权限 Hook 集成 | ✅ PreToolUseAction::PermissionRequired 完整流程 | ✅ 完整 PermissionRequest hook + 多路径决议 | 相当 |
|
||||
| 规则遮蔽检测 | ✅ `detect_shadowed_rules()` deny/ask 双重检查 | ✅ `shadowedRuleDetection` deny/ask 遮蔽检测 | 相当 |
|
||||
| Auto Mode (AI 分类) | ❌ 无 | ✅ YOLO classifier + 快速路径 + 安全工具白名单 | **远期** |
|
||||
| 权限解释器 | ✅ 启发式 `explain_permission()` (Bash 风险等级 + 路径检测) | ✅ Haiku 生成风险解释 | 中等 |
|
||||
| 会话内规则更新 | ✅ `POST/PUT /api/chat/sessions/:id/permissions/*` | ✅ `/permissions` 命令 + API | 小 |
|
||||
| 子代理权限继承 | ✅ 完整 `check()` 三态检查 | ✅ 完整继承父级权限上下文 | 相当 |
|
||||
| 附加目录沙箱 | ✅ `AGENT_ADDITIONAL_DIRS` + `is_path_allowed()` 扩展 | ✅ `additionalDirectories` 可配置 | 相当 |
|
||||
|
||||
### Claude Code 权限流水线 (参考架构)
|
||||
|
||||
```
|
||||
hasPermissionsToUseTool(toolName, input, context):
|
||||
Step 1a: 工具级 deny 规则检查 → deny → 返回 deny
|
||||
Step 1b: 工具级 ask 规则检查 → ask → 返回 ask (sandbox 例外)
|
||||
Step 1c: 工具自定义 checkPermissions() → 内容级规则匹配
|
||||
Step 1d: 工具实现返回 deny → deny → 返回 deny
|
||||
Step 1e: requiresUserInteraction? → ask → 强制 ask (bypass 免疫)
|
||||
Step 1f: 内容级 ask 规则 → ask → 强制 ask (bypass 免疫)
|
||||
Step 1g: 安全检查 (敏感路径等) → ask → 强制 ask (bypass 免疫)
|
||||
Step 2a: bypassPermissions 模式? → allow → 返回 allow
|
||||
Step 2b: 工具级 allow 规则 → allow → 返回 allow
|
||||
Step 3: 剩余 passthrough → ask → 返回 ask
|
||||
|
||||
外层模式变换:
|
||||
dontAsk 模式: ask → deny
|
||||
auto 模式: acceptEdits 快速路径 → 安全工具白名单 → AI分类器
|
||||
headless: hooks 先运行 → 无 hook 决定 → auto-deny
|
||||
```
|
||||
|
||||
### 关键设计决策对比
|
||||
|
||||
**1. 规则格式**
|
||||
|
||||
Claude Code 使用 `ToolName(content)` 格式支持内容级规则:
|
||||
```
|
||||
Bash → 匹配所有 bash 命令
|
||||
Bash(npm install) → 匹配精确命令
|
||||
Bash(npm *) → 前缀通配
|
||||
Bash(rm:*) → 旧版前缀(已废弃)
|
||||
Read(.env) → 文件模式
|
||||
mcp__server__tool → MCP 工具级
|
||||
mcp__server → MCP 服务级
|
||||
Agent(Explore) → 代理类型级
|
||||
```
|
||||
|
||||
AstroResearch 已实现相同格式:
|
||||
```
|
||||
"*" → 通配所有工具
|
||||
"tool_name" → 精确工具名匹配
|
||||
"tool_name(content*)" → 前缀通配(如 "run_bash(rm *)" 匹配 "rm -rf /")
|
||||
"tool_name(*suffix)" → 后缀通配(如 "read_file(*.env)" 匹配 ".env")
|
||||
"tool_name(exact)" → 包含匹配(子串命中)
|
||||
"*(content)" → 工具通配 + 内容匹配(如 "*(sudo)" 匹配任意工具的 sudo 命令)
|
||||
```
|
||||
从 args 中自动提取 `command`/`file_path`/`path`/`pattern`/`url` 字段进行内容匹配。
|
||||
|
||||
**2. 权限模式**
|
||||
|
||||
Claude Code 的 6 种模式通过 Shift+Tab 循环切换:
|
||||
- `default` — 标准逐项确认
|
||||
- `acceptEdits` — 工作目录内文件编辑自动通过
|
||||
- `bypassPermissions` — 跳过所有 Ask(deny/ask 规则仍生效;安全检查 bypass 免疫)
|
||||
- `dontAsk` — 所有 Ask 转 Deny
|
||||
- `plan` — 计划模式
|
||||
- `auto` — AI 自动分类(内部使用)
|
||||
|
||||
AstroResearch 已实现 4 种模式(通过 `AGENT_PERMISSION_MODE` 环境变量或 API 切换):
|
||||
- `default` — 标准规则链,Ask 触发用户交互
|
||||
- `acceptEdits` — 工作目录内文件编辑自动通过(路径检查由 executor 完成)
|
||||
- `bypassPermissions` — 跳过所有 Ask(Deny 规则仍生效)
|
||||
- `dontAsk` — 所有 Ask 转为 Deny
|
||||
|
||||
`PermissionChecker::from_config()` 从 `AgentConfig` 加载环境变量规则并构造完整检查器。
|
||||
|
||||
**3. 多路径权限决议**
|
||||
|
||||
Claude Code 的 AskUser 决议支持多个并行路径,任一先返回即生效(`claim()` 模式):
|
||||
- 本地 UI 对话框
|
||||
- Bridge 响应(CCR 远程)
|
||||
- Channel 响应(Telegram 等)
|
||||
- PermissionRequest hooks(后台异步运行)
|
||||
- Bash 分类器(后台推测性异步分类)
|
||||
|
||||
AstroResearch 已实现完整的 AskUser 交互流:
|
||||
- `PermissionChecker::check()` 返回 `AskUser` 时,executor 发送 `AgentStreamEvent::PermissionRequest` SSE 事件
|
||||
- 通过 `oneshot` 通道创建 `PendingPermission`,存入 `AppState::pending_permissions`
|
||||
- 等待用户通过前端 `PermissionRequestCard` 组件响应(Allow / Deny / Always Allow),120s 超时自动拒绝
|
||||
- 单一路径决议(oneshot),不支持多路径 claim 模式
|
||||
|
||||
---
|
||||
|
||||
## 优化路线图
|
||||
|
||||
### ✅ P0 — 已全部完成
|
||||
|
||||
#### P0-1: 规则加载与持久化 ✅
|
||||
|
||||
`AgentConfig::from_env_optional()` 从环境变量加载规则(`AGENT_PERMISSIONS_DENY`/`ALLOW`/`ASK`),`PermissionChecker::from_config()` 按 Deny → Ask → Allow 优先级顺序构造规则链。
|
||||
|
||||
**实现位置**: `src/agent/runtime/mod.rs:113-117`, `src/agent/runtime/permission.rs:268-290`
|
||||
|
||||
#### P0-2: 完成 AskUser 权限交互流 ✅
|
||||
|
||||
executor Phase 2.5 中完整的 AskUser 处理:
|
||||
- `AgentStreamEvent::PermissionRequest` SSE 事件 → 前端 `PermissionRequestCard` 组件
|
||||
- `oneshot` 通道 + `AppState::pending_permissions` 存储
|
||||
- 120s 超时自动拒绝,支持 Allow / Deny / Always Allow 决策
|
||||
|
||||
**实现位置**: `src/agent/runtime/executor.rs:282-422`, `src/api/agent.rs`, `dashboard/src/features/agent/PermissionRequestCard.tsx`
|
||||
|
||||
#### P0-3: 内容级权限匹配 ✅
|
||||
|
||||
`PermissionChecker::matches()` 支持 `"tool_name(content_pattern)"` 格式,前缀通配(`prefix*`)、后缀通配(`*suffix`)、包含匹配,自动从 args 提取 `command`/`file_path`/`path`/`pattern`/`url` 字段。
|
||||
|
||||
**实现位置**: `src/agent/runtime/permission.rs:183-255`
|
||||
|
||||
### ✅ P1 — 已全部完成
|
||||
|
||||
#### P1-1: 权限模式系统 ✅
|
||||
|
||||
`PermissionMode` 枚举实现 4 种模式(Default/AcceptEdits/Bypass/DontAsk),通过 `AGENT_PERMISSION_MODE` 环境变量或 API 切换。`PermissionChecker::apply_mode()` 在 executor Phase 2.5 中对检查结果进行模式变换(Bypass 将 Ask→Allowed,DontAsk 将 Ask→Denied)。
|
||||
|
||||
**实现位置**: `src/agent/runtime/permission.rs:39-60, 139-177`
|
||||
|
||||
#### P1-2: Bash 权限接入 PermissionChecker ✅
|
||||
|
||||
`RunBashTool::check_permissions()` 调用 `bash_needs_permission()` —— 安全白名单中的命令返回空规则(自动允许),非白名单命令返回 `Ask` 规则。executor Phase 2.5 中与 PermissionChecker 结果叠加。
|
||||
|
||||
**实现位置**: `src/agent/tools/filesystem/bash.rs:58-73, 362-365`
|
||||
|
||||
#### P1-3: 权限 Hook 集成 ✅
|
||||
|
||||
`PreToolUseAction::PermissionRequired` 在 executor 中被检测:若 hook 返回 `PermissionRequired` 且 PermissionChecker 返回 `Allowed`,则升级为 `AskUser` 触发用户交互。已修复 Continue 覆盖 meaningful action 的 bug。
|
||||
|
||||
**实现位置**: `src/agent/hooks.rs:378-384`, `src/agent/runtime/executor.rs:221-230`
|
||||
|
||||
#### P1-4: 子代理完整权限继承 ✅
|
||||
|
||||
`SubAgentRunner` 使用 `check(tool_name, Some(&final_args))` 进行三态检查:Deny → 注入错误跳过执行,AskUser → 自动拒绝(子代理不应打断用户),Allowed → 正常执行。
|
||||
|
||||
**实现位置**: `src/agent/subagent.rs`
|
||||
|
||||
### ✅ P2-1、P2-2 — 已实现
|
||||
|
||||
#### P2-1: 拒绝追踪与熔断 ✅
|
||||
|
||||
`DenialTracker` 追踪连续拒绝和总拒绝数,阈值触发 ReAct 循环终止。
|
||||
配置:`AGENT_DENIAL_MAX_CONSECUTIVE` (默认 3) / `AGENT_DENIAL_MAX_TOTAL` (默认 20)。
|
||||
|
||||
**实现位置**: `src/agent/runtime/denial_tracker.rs`, `src/agent/runtime/mod.rs`
|
||||
|
||||
#### P2-2: 会话内规则更新 API ✅
|
||||
|
||||
`POST /api/chat/sessions/:id/permissions/rules` — add/remove 规则
|
||||
`PUT /api/chat/sessions/:id/permissions/mode` — 切换权限模式
|
||||
通过 `AppState::session_permission_checker` (`Arc<RwLock<PermissionChecker>>`) 实现跨 turn 共享。
|
||||
|
||||
**实现位置**: `src/api/permissions.rs`, `src/agent/runtime/executor.rs` Phase 2.5
|
||||
|
||||
### 🟡 P2-3~P2-5 — 远期增强(按需实现)
|
||||
|
||||
#### P2-3: 规则遮蔽检测 ✅
|
||||
|
||||
`PermissionChecker::detect_shadowed_rules()` 检测 Deny/Ask 遮蔽 Allow 的情况,输出 `ShadowedRule` 列表(含 reason + fix 建议),在 AgentRuntime 初始化时通过 `warn!` 日志输出。
|
||||
|
||||
**实现位置**: `src/agent/runtime/permission.rs`
|
||||
|
||||
#### P2-4: 权限解释器 ✅
|
||||
|
||||
启发式 `explain_permission()` 函数,根据工具名和参数生成 `{risk_level, explanation, reasoning, risk}` 结构。Bash 命令通过关键词检测风险等级(HIGH/MEDIUM/LOW),文件操作检测系统路径。结果随 `PermissionRequest` SSE 事件推送到前端。
|
||||
|
||||
**实现位置**: `src/agent/runtime/permission_explainer.rs`, `src/agent/runtime/mod.rs` `AgentStreamEvent::PermissionRequest.explanation`
|
||||
|
||||
#### P2-5: Auto Mode (AI 权限分类器) ❌
|
||||
|
||||
使用 LLM 自动评估工具调用的风险:
|
||||
- 快速路径:`AcceptEdits` 模式自动允许工作目录内的文件编辑
|
||||
- 安全工具白名单:`read_file`、`grep_files`、`search_papers` 等只读操作自动允许
|
||||
- AI 分类:对不确定的操作调用快速模型判断安全性
|
||||
- 失败封闭:分类器不可用时拒绝所有非白名单操作(安全优先)
|
||||
|
||||
**工作量**: 3-5天
|
||||
|
||||
#### P2-4: 权限解释器
|
||||
|
||||
在执行前用 LLM 生成人类可读的风险描述:
|
||||
```
|
||||
"该命令将执行 npm install,可能修改 node_modules/ 目录并下载外部依赖包。"
|
||||
```
|
||||
|
||||
**工作量**: 1天
|
||||
|
||||
#### P2-5: 子代理最小权限 (ToolRegistry::restrict)
|
||||
|
||||
```rust
|
||||
impl ToolRegistry {
|
||||
pub fn restrict(&self, allowed_tools: &[&str]) -> Self {
|
||||
// 创建仅包含指定工具的受限注册表
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**工作量**: 0.5天
|
||||
|
||||
---
|
||||
|
||||
## 实现路线图
|
||||
|
||||
```
|
||||
已完成 (Phase 1): P0-1 规则加载 + P0-2 AskUser 交互流 + P0-3 内容级匹配
|
||||
已完成 (Phase 2): P1-1 权限模式 + P1-2 Bash 集成 + P1-3 Hook 集成 + P1-4 子代理继承
|
||||
已完成 (Phase 3): P2-1 拒绝追踪熔断 + P2-2 会话内规则更新 + P2-3 规则遮蔽检测 + P2-4 权限解释器 + 附加目录沙箱 + 规则来源追踪
|
||||
远期规划 (按需): P2-5 Auto Mode (AI 分类器) + P2-6 权限解释器 LLM 升级 + 子代理最小权限 + 文件写入大小限制
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 待完善项
|
||||
|
||||
| 优先级 | 项目 | 当前状态 | 建议 |
|
||||
|---|---|---|---|
|
||||
| ~~**HIGH**~~ | ~~PermissionChecker 集成到主执行路径~~ | ✅ **已完成** | — |
|
||||
| ~~**HIGH**~~ | ~~Bash 黑名单改为命令解析~~ | ✅ **已完成** | — |
|
||||
| ~~**P0**~~ | ~~规则加载与持久化~~ | ✅ **已完成**:`AgentConfig` 新增 `permission_deny_rules` / `permission_allow_rules` / `permission_ask_rules` / `permission_mode` 字段,通过 `AGENT_PERMISSIONS_*` 环境变量加载 | — |
|
||||
| ~~**P0**~~ | ~~AskUser 权限交互流~~ | ✅ **已完成**:executor Phase 2.5 AskUser 分支重写为完整 oneshot → SSE → 120s 超时流程。前端 `PermissionRequestCard` 组件提供 Allow / Deny / Always Allow | — |
|
||||
| ~~**P0**~~ | ~~内容级权限匹配~~ | ✅ **已完成**:`check(tool_name, tool_args)` 签名,`matches()` 支持 `"tool(content*)"` 格式(前缀/后缀/包含),自动提取 args 字段 | — |
|
||||
| ~~**P1**~~ | ~~权限模式系统~~ | ✅ **已完成**:`PermissionMode` (Default/AcceptEdits/Bypass/DontAsk),`apply_mode()` 方法,`AGENT_PERMISSION_MODE` 配置 | — |
|
||||
| ~~**P1**~~ | ~~Bash 权限集成~~ | ✅ **已完成**:`RunBashTool::check_permissions()` 调用 `bash_needs_permission()`(安全命令自动允许),executor 合并工具级检查 | — |
|
||||
| ~~**P1**~~ | ~~权限 Hook 集成~~ | ✅ **已完成**:`PreToolUseResult::is_permission_required()`,修复 Continue 覆盖 bug,executor 触发 AskUser | — |
|
||||
| ~~**P1**~~ | ~~子代理完整权限继承~~ | ✅ **已完成**:`is_denied()` → `check(tool_name, Some(&final_args))`,子代理中 AskUser 自动拒绝 | — |
|
||||
| ~~**MEDIUM**~~ | ~~拒绝追踪与熔断~~ | ✅ **已完成** | `DenialTracker`:连续/总计拒绝计数,阈值触发 ReAct 循环终止 |
|
||||
| ~~**MEDIUM**~~ | ~~会话内规则更新 API~~ | ✅ **已完成** | `POST/PUT /api/chat/sessions/:id/permissions/*` 动态 add/remove/mode |
|
||||
| ~~**MEDIUM**~~ | ~~权限解释器~~ | ✅ **已完成** | 启发式 `explain_permission()`,Bash 风险等级 + 路径检测,随 SSE PermissionRequest 推送前端 |
|
||||
| **MEDIUM** | Auto Mode (AI 分类器) | 未实现 | LLM 评估风险,快速路径 + 安全工具白名单 |
|
||||
| **LOW** | 子代理最小权限 | 继承全部父工具 | `ToolRegistry::restrict()` |
|
||||
| **LOW** | 文件写入大小限制 | 无上限 | 添加 `max_file_size` 参数 |
|
||||
| **LOW** | 网络访问控制 | `curl`/`wget` 未限制 | Bash 黑名单扩展 |
|
||||
| **LOW** | 用户权限 profiles | 不支持 | YAML/TOML 权限配置 |
|
||||
|
||||
@@ -42,7 +42,9 @@ sequenceDiagram
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|---|---|---|
|
||||
| `src/agent/skills.rs` | 847 | SkillRegistry 缓存、文件解析、热更新、条件激活、系统提示构建 |
|
||||
| `src/agent/skills.rs` | ~1100 | SkillRegistry 缓存、文件解析、热更新、条件激活、系统提示构建、SkillCreator / SelfImprovePipeline |
|
||||
| `src/agent/skills/pattern_detector.rs` | ~420 | PatternDetector — 从 agent_messages 扫描工具调用序列、子序列匹配、Jaccard 去重 |
|
||||
| `src/agent/skills/curator.rs` | ~700 | Curator — Skill 生命周期管理 + CuratorRunner — 后台空闲触发审查 |
|
||||
| `src/agent/tools/skill.rs` | 222 | LoadSkillTool — Layer 2 按需加载的 AgentTool 实现 |
|
||||
| `src/agent/runtime/mod.rs` | ~1182 | 将 `build_reminder()` 注入 SystemPrompt section 3 |
|
||||
| `src/agent/runtime/system_prompt.rs` | ~64 | 静态 system prompt 中引导 LLM 使用 load_skill |
|
||||
@@ -95,6 +97,7 @@ effort: high
|
||||
| `paths` | `string[]` | `[]`(始终激活) | 条件激活的 glob 模式,非空时 skill 仅在匹配文件路径后激活 |
|
||||
| `agent` | `string` | — | fork 模式下游的 agent 类型(如 `code-reviewer`),已定义但 LoadSkillTool 尚未使用 |
|
||||
| `effort` | `string` | — | fork 模式下的 effort 级别,已定义但 LoadSkillTool 尚未使用 |
|
||||
| `pinned` | `bool` | `false` | `true` 时 Curator 强制保持 Active 生命周期,最低质量评分 0.8,不被自动清理 |
|
||||
|
||||
### 当前项目 Skill 清单
|
||||
|
||||
@@ -112,7 +115,8 @@ effort: high
|
||||
SkillFrontmatter — serde_yaml 解析的 YAML frontmatter,含 validate() 校验方法
|
||||
│
|
||||
├──▶ SkillMeta — Layer 1 摘要(name, description, context, allowed_tools,
|
||||
│ when_to_use, disable_model_invocation, user_invocable, paths)
|
||||
│ when_to_use, disable_model_invocation, user_invocable, paths,
|
||||
│ pinned: bool)
|
||||
│
|
||||
└──▶ Skill — Layer 2 完整对象(meta + body + skill_dir)
|
||||
│
|
||||
@@ -120,6 +124,12 @@ SkillFrontmatter — serde_yaml 解析的 YAML frontmatter,含 valida
|
||||
├── skills: Vec<Skill>
|
||||
├── last_scan_mtime: Option<SystemTime>
|
||||
└── usage_stats: HashMap<String, SkillUsageStat>
|
||||
|
||||
SelfImprovePipeline — 一站式管道
|
||||
├── PatternDetector — 扫描 agent_messages 检测重复工具调用序列
|
||||
├── SkillCreator — 将 DetectedPattern 转换为 SKILL.md 文件
|
||||
└── Curator — Skill 生命周期管理(Active→Inactive→Stale→Deprecated)
|
||||
└── CuratorRunner — 后台空闲触发审查 + 心跳记录
|
||||
```
|
||||
|
||||
### 关键方法
|
||||
@@ -319,12 +329,304 @@ Box::new(LoadSkillTool::new(skill_registry)),
|
||||
- 团队成员(`teammate.rs`)
|
||||
- 后台任务 Agent(`background.rs`)
|
||||
|
||||
## Self-improving Skills — 自我进化管道
|
||||
|
||||
参考 Hermes-Agent 的 Self-improving Skills 模式,AstroResearch 实现了从**模式检测 → 自动创建 → 生命周期管理**的完整自我进化管道。
|
||||
|
||||
### 架构总览
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph Pipeline["SelfImprovePipeline::run()"]
|
||||
direction TB
|
||||
PD["PatternDetector::scan()<br/>扫描 agent_messages 表"]
|
||||
SC["SkillCreator::create_from_patterns()<br/>生成 SKILL.md 文件"]
|
||||
CR["Curator::analyze()<br/>质量评分 + 生命周期评估"]
|
||||
end
|
||||
|
||||
subgraph Background["后台定期维护"]
|
||||
direction LR
|
||||
Runner["CuratorRunner::spawn()<br/>空闲触发 + 间隔检查"]
|
||||
Archive["archive_stale_skills()<br/>30d stale / 90d deprecated"]
|
||||
end
|
||||
|
||||
PD -->|"Vec<DetectedPattern>"| SC
|
||||
SC -->|"Vec<Skill>"| CR
|
||||
CR -->|"CuratorReport"| Runner
|
||||
```
|
||||
|
||||
### PatternDetector — 模式检测器
|
||||
|
||||
从 `agent_messages` 表中自动发现跨 session 重复的工具调用序列:
|
||||
|
||||
```
|
||||
检测算法:
|
||||
1. 查询每个 session 的 tool 消息(按时间排序)
|
||||
2. 滑动窗口 (2-8 长度) 提取所有子序列
|
||||
3. 跨 session 频率计数(≥3 次为候选)
|
||||
4. Jaccard 相似度去重 + 超序列包含检测
|
||||
5. 计算 confidence = frequency_score × similarity_penalty
|
||||
```
|
||||
|
||||
**数据结构**:
|
||||
|
||||
```rust
|
||||
pub struct DetectedPattern {
|
||||
pub tool_sequence: Vec<String>, // 如 ["search_papers", "download_paper", "rag_search"]
|
||||
pub session_ids: Vec<String>, // 出现的 session
|
||||
pub frequency: usize, // 跨 session 出现次数
|
||||
pub confidence: f64, // 0.0-1.0 置信度
|
||||
pub fingerprint: String, // 去重指纹
|
||||
}
|
||||
```
|
||||
|
||||
**置信度计算**:
|
||||
```
|
||||
confidence = ln(frequency) / ln(3) × (1 - max_jaccard_similarity_with_other_patterns)
|
||||
```
|
||||
即:频率越高越好,与已有模式越不相似越好。
|
||||
|
||||
### SkillCreator — 自动 Skill 生成
|
||||
|
||||
将 `DetectedPattern` 转换为完整的 `SKILL.md` 文件:
|
||||
|
||||
```rust
|
||||
pub struct SkillCreator {
|
||||
skills_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl SkillCreator {
|
||||
/// 检测到的模式 → 写入 skills/{kebab-case-name}/SKILL.md
|
||||
pub fn create_from_patterns(
|
||||
&self,
|
||||
patterns: &[DetectedPattern],
|
||||
dry_run: bool, // dry_run=true 时只预览不移交
|
||||
) -> Result<Vec<Skill>, Error>;
|
||||
|
||||
/// 工具序列名 → kebab-case skill 名称
|
||||
fn pattern_to_skill_name(tools: &[String]) -> String;
|
||||
// 例: ["search_papers", "download_paper", "rag_search"] → "search-download-rag"
|
||||
|
||||
/// 生成 SKILL.md 正文(含 YAML frontmatter + step-by-step 指引)
|
||||
fn generate_skill_md(pattern: &DetectedPattern) -> String;
|
||||
}
|
||||
```
|
||||
|
||||
生成的 SKILL.md 自动包含:
|
||||
- `pinned: false`(初始不固定)
|
||||
- `when_to_use` 自动从工具名推断
|
||||
- 每个工具调用作为 `<step>` 写入正文
|
||||
- `version: "0.1.0"`(自动生成版本)
|
||||
|
||||
### Curator — Skill 生命周期管理
|
||||
|
||||
基于 Hermes-Agent `curator.py` 的设计,实现确定性的时间戳驱动生命周期:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Active: 创建 / 使用
|
||||
Active --> Active: seed_record (0d) / 有调用记录
|
||||
Active --> Inactive: 7d 无使用
|
||||
Inactive --> Active: 再次使用
|
||||
Inactive --> Stale: 30d 无使用
|
||||
Stale --> Deprecated: 90d 无使用
|
||||
Deprecated --> [*]: 手动删除
|
||||
|
||||
state Active {
|
||||
[*] --> Pinned: pinned=true
|
||||
Pinned --> Pinned: 强制保持 (min_score=0.8)
|
||||
}
|
||||
```
|
||||
|
||||
**生命周期状态**:
|
||||
|
||||
| 状态 | 条件 | 行为 |
|
||||
|------|------|------|
|
||||
| `Active` | 最近使用 ≤ 7 天 | 正常在 remind 列表中出现 |
|
||||
| `Inactive` | 7-30 天未使用 | 不出现在 remind 列表,可被重新激活 |
|
||||
| `Stale` | 30-90 天未使用 | 标记为 stale,出现在清理候选列表 |
|
||||
| `Deprecated` | > 90 天未使用 | 建议归档或删除 |
|
||||
|
||||
**质量评分**:
|
||||
|
||||
```
|
||||
quality_score = ln(1 + invoke_count) × 0.5^(days_since_last_use / 7)
|
||||
```
|
||||
|
||||
**Pinned 保护**:`pinned=true` 的 skill 强制 `Active` 状态,最低评分 0.8,不会出现在清理候选列表中。
|
||||
|
||||
### Seed Record — 新 Skill 锚定时钟
|
||||
|
||||
新创建或自动生成的 skill 可能没有使用统计,`seed_record` 机制防止它们被立即标记为 stale:
|
||||
|
||||
```rust
|
||||
fn evaluate_quality(&self, skill: &Skill, stats: Option<&SkillUsageStat>) -> SkillQuality {
|
||||
let (invoke_count, days_since_last_use) = match stats {
|
||||
Some(s) => (s.invoke_count, s.days_since_last_use()),
|
||||
None => (
|
||||
0,
|
||||
// seed_record: 无统计 → days_since_last_use = 0(视为刚创建)
|
||||
Some(0),
|
||||
),
|
||||
};
|
||||
// 如果 days_since_last_use <= 7 (NEW_SKILL_GRACE_PERIOD_DAYS) → Active
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
关键常量:
|
||||
- `NEW_SKILL_GRACE_PERIOD_DAYS = 7`:新 skill 在 7 天内即使零调用也保持 Active
|
||||
- `STALE_THRESHOLD_DAYS = 30`:30 天未用标记为 stale
|
||||
- `DEPRECATED_THRESHOLD_DAYS = 90`:90 天未用标记为 deprecated
|
||||
|
||||
### CuratorRunner — 后台空闲触发审查
|
||||
|
||||
参考 Hermes-Agent 的 inactivity-triggered curator:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户交互
|
||||
participant App as AgentRuntime
|
||||
participant CR as CuratorRunner
|
||||
participant Curator as Curator
|
||||
|
||||
Note over CR: 后台 tokio task
|
||||
loop 每 check_interval
|
||||
CR->>CR: should_run_now()
|
||||
alt 未暂停 AND 空闲 > min_idle AND 距上次 > interval
|
||||
CR->>Curator: run_once()
|
||||
Curator->>Curator: evaluate_quality() / archive_stale_skills()
|
||||
Curator-->>CR: CuratorReport
|
||||
else 不满足条件
|
||||
CR->>CR: skip
|
||||
end
|
||||
end
|
||||
|
||||
User->>App: 发送查询
|
||||
App->>CR: record_activity() 更新心跳
|
||||
```
|
||||
|
||||
**CuratorRunner API**:
|
||||
|
||||
```rust
|
||||
pub struct CuratorRunner {
|
||||
curator: Curator,
|
||||
db: SqlitePool,
|
||||
interval: Duration, // 最小审查间隔(默认 7 天)
|
||||
min_idle: Duration, // 最小空闲时间(默认 2 小时)
|
||||
check_interval: Duration, // 检查间隔(默认 1 小时)
|
||||
paused: AtomicBool,
|
||||
last_activity: RwLock<Instant>,
|
||||
last_run: RwLock<Option<Instant>>,
|
||||
}
|
||||
|
||||
impl CuratorRunner {
|
||||
pub fn new(curator: Curator, db: SqlitePool) -> Self;
|
||||
pub fn with_interval(mut self, interval: Duration) -> Self;
|
||||
pub fn with_min_idle(mut self, min_idle: Duration) -> Self;
|
||||
|
||||
/// 判断是否应运行:未暂停 + 空闲超时 + 距上次超间隔
|
||||
pub fn should_run_now(&self) -> bool;
|
||||
|
||||
/// 记录用户活动心跳
|
||||
pub async fn record_activity(&self);
|
||||
|
||||
/// 执行一次审查(仅在 should_run_now 时)
|
||||
pub async fn run_once(
|
||||
&self,
|
||||
usage_stats: &HashMap<String, SkillUsageStat>,
|
||||
skill_metas: &[SkillMeta],
|
||||
) -> Option<CuratorReport>;
|
||||
|
||||
/// 启动后台任务
|
||||
pub fn spawn(
|
||||
self: Arc<Self>,
|
||||
usage_stats: Arc<RwLock<HashMap<String, SkillUsageStat>>>,
|
||||
skill_metas: Arc<RwLock<Vec<SkillMeta>>>,
|
||||
check_interval: Duration,
|
||||
) -> JoinHandle<()>;
|
||||
|
||||
pub fn pause(&self);
|
||||
pub fn resume(&self);
|
||||
}
|
||||
```
|
||||
|
||||
**使用示例**:
|
||||
|
||||
```rust
|
||||
let curator = Curator::new(skills_dir.clone());
|
||||
let runner = Arc::new(
|
||||
CuratorRunner::new(curator, db_pool.clone())
|
||||
.with_interval(Duration::from_secs(7 * 24 * 3600)) // 最少间隔 7 天
|
||||
.with_min_idle(Duration::from_secs(2 * 3600)), // 空闲 2 小时后
|
||||
);
|
||||
|
||||
// 每次用户交互时更新心跳
|
||||
runner.record_activity().await;
|
||||
|
||||
// 启动后台任务
|
||||
let _handle = runner.spawn(usage_stats, skill_metas, Duration::from_secs(3600));
|
||||
```
|
||||
|
||||
### SelfImprovePipeline — 一站式管道
|
||||
|
||||
```rust
|
||||
pub struct SelfImprovePipeline {
|
||||
detector: PatternDetector,
|
||||
creator: SkillCreator,
|
||||
curator: Curator,
|
||||
}
|
||||
|
||||
impl SelfImprovePipeline {
|
||||
pub fn new(db: SqlitePool, skills_dir: PathBuf) -> Self;
|
||||
|
||||
/// 完整管道:检测 → 创建 → 审查
|
||||
pub async fn run(&self, dry_run: bool) -> Result<SelfImproveResult>;
|
||||
|
||||
/// 仅检测模式(不创建)
|
||||
pub async fn detect_only(&self) -> Result<Vec<DetectedPattern>>;
|
||||
|
||||
/// 仅分析已有 skills(不检测新模式)
|
||||
pub async fn analyze_only(
|
||||
&self,
|
||||
usage_stats: &HashMap<String, SkillUsageStat>,
|
||||
skill_metas: &[SkillMeta],
|
||||
) -> Result<CuratorReport>;
|
||||
}
|
||||
|
||||
pub struct SelfImproveResult {
|
||||
pub patterns_found: usize,
|
||||
pub skills_created: usize,
|
||||
pub skills_created_names: Vec<String>,
|
||||
pub curator_report: CuratorReport,
|
||||
}
|
||||
```
|
||||
|
||||
**管道流程**:
|
||||
|
||||
```
|
||||
SelfImprovePipeline::run(dry_run=true)
|
||||
│
|
||||
├─ 1. PatternDetector::scan()
|
||||
│ └─ 从 agent_messages 中检测 ≥3 次跨 session 重复序列
|
||||
│ └─ 结果: Vec<DetectedPattern>(按 confidence 降序)
|
||||
│
|
||||
├─ 2. SkillCreator::create_from_patterns(patterns, dry_run)
|
||||
│ ├─ dry_run=true → 只记录日志,不写入文件
|
||||
│ └─ dry_run=false → 写入 skills/ 目录 + 触发 SkillRegistry::refresh()
|
||||
│
|
||||
└─ 3. Curator::analyze(usage_stats, skill_metas)
|
||||
└─ 质量评分 + 生命周期状态 + 清理建议
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 与 Claude Code 参考设计的对应关系
|
||||
|
||||
| Claude Code 概念 | AstroResearch 实现 |
|
||||
|---|---|
|
||||
| Claude Code / Hermes 概念 | AstroResearch 实现 |
|
||||
|---|---|---|
|
||||
| `src/skills/` 目录 + `SKILL.md` | 完全相同 |
|
||||
| YAML frontmatter(name, description, context, allowed-tools...) | 相同,增加 `version`、`agent`、`effort`、`paths` 字段 |
|
||||
| YAML frontmatter(name, description, context, allowed-tools...) | 相同,增加 `version`、`agent`、`effort`、`paths`、`pinned` 字段 |
|
||||
| `<system-reminder>` Layer 1 注入 | `build_reminder()` → 结构化 XML 块 |
|
||||
| `SkillTool` Layer 2 按需加载 | `LoadSkillTool`(AgentTool trait 实现) |
|
||||
| inline 模式(注入指令内容) | ✅ 实现 |
|
||||
@@ -334,6 +636,11 @@ Box::new(LoadSkillTool::new(skill_registry)),
|
||||
| 条件 skill(paths glob) | ✅ `activate_conditional_for_paths()` |
|
||||
| 变量替换 | ✅ `${SKILL_DIR}`, `${SESSION_ID}` |
|
||||
| `Skill` 工具接口 + `skill` slash command | LoadSkillTool(tool 形式),前端的 `/skill-name` 通过 tool 调用实现 |
|
||||
| Hermes: Self-improving Skills(模式检测 + 自动创建) | ✅ `PatternDetector` + `SkillCreator` + `SelfImprovePipeline` |
|
||||
| Hermes: Curator 生命周期管理 | ✅ `Curator`(Active/Inactive/Stale/Deprecated) |
|
||||
| Hermes: Pinned Skills(不可清理) | ✅ `pinned: true` frontmatter + Curator 保护 |
|
||||
| Hermes: Seed Record(新 skill 锚定时钟) | ✅ `days_since_last_use=Some(0)` + 7 天 grace period |
|
||||
| Hermes: Inactivity-triggered Runner | ✅ `CuratorRunner`(后台 tokio task + 心跳记录) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 系统提示词架构 (System Prompt Architecture)
|
||||
|
||||
AstroResearch 的 Agent 系统提示词采用**模块化 Section 组装 + 动态注入 + 多层生命周期**架构,直接参考 Claude Code 的 System Prompt 设计。
|
||||
AstroResearch Agent 系统提示词采用**模块化 Section 组装 + 简单首次缓存**架构,参考 Claude Code 的 System Prompt 设计并针对实际场景裁剪。
|
||||
|
||||
## 整体分层
|
||||
|
||||
@@ -9,26 +9,29 @@ graph TB
|
||||
subgraph L5["Layer 5: 运行时注入"]
|
||||
Nudge["nudge / 任务恢复 / 后台通知"]
|
||||
end
|
||||
subgraph L4["Layer 4: 提示词压缩"]
|
||||
Compress["snip → micro → auto → identity"]
|
||||
subgraph L4["Layer 4: 提示词压缩 + CollapseLog"]
|
||||
Compress["snip → micro → auto → aggressive_micro"]
|
||||
Collapse["CollapseLog commit / overflow"]
|
||||
end
|
||||
subgraph L3["Layer 3: Skill 动态加载"]
|
||||
Skill["Layer1 提醒 → Layer2 全文注入"]
|
||||
end
|
||||
subgraph L2["Layer 2: 子代理隔离提示词"]
|
||||
SubSP["独立的 system_prompt"]
|
||||
subgraph L2["Layer 2: 子代理模块化提示词"]
|
||||
SubSP["6 section 组装器"]
|
||||
end
|
||||
subgraph L1["Layer 1: 主代理 SystemPrompt 组装"]
|
||||
MainSP["5 个 section 模块化组装"]
|
||||
MainSP["9 个 section 模块化组装<br/>静态(5) → 动态(4)"]
|
||||
Cache["SystemPromptCache<br/>首次计算,永久复用"]
|
||||
end
|
||||
L5 --> L4 --> L3 --> L2 --> L1
|
||||
L1 --> Cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心组装器 (`src/agent/runtime/system_prompt.rs`)
|
||||
|
||||
### 2.1 数据结构
|
||||
### 数据结构
|
||||
|
||||
```rust
|
||||
pub struct SystemPrompt {
|
||||
@@ -36,290 +39,288 @@ pub struct SystemPrompt {
|
||||
}
|
||||
```
|
||||
|
||||
简单的有序 section 列表,通过 `assemble()` 方法用双换行符 `"\n\n"` 拼接所有 section 内容。section 按添加顺序排列。
|
||||
有序 section 列表。`assemble()` 用 `"\n\n"` 拼接所有 section。顺序即最终 prompt 中出现的顺序——静态内容在前,动态内容在后。
|
||||
|
||||
### 2.2 静态常量
|
||||
### Section 缓存 (`SystemPromptCache`)
|
||||
|
||||
两个 `&'static str` 常量在所有运行时实例间共享内存:
|
||||
简化设计:**首次计算,永久缓存**。因为 session 生命周期内 CWD、platform、OS、model、工具注册表均不变,不需要 TTL 过期机制。仅在 `/clear` 或 `/compact` 事件时调用 `invalidate_all()` 全局失效。
|
||||
|
||||
**IDENTITY_SECTION**(身份声明,1 行):
|
||||
```
|
||||
你是一位专业的天体物理学研究助手,具备丰富的天文学知识。
|
||||
```rust
|
||||
pub struct SystemPromptCache {
|
||||
entries: HashMap<&'static str, String>,
|
||||
}
|
||||
|
||||
impl SystemPromptCache {
|
||||
// 首次计算,后续命中缓存
|
||||
pub fn get_or_compute(&mut self, name: &str, compute: impl FnOnce() -> String) -> String;
|
||||
// compute 返回 Option:Some 缓存并返回,None 不缓存
|
||||
pub fn get_or_compute_optional(&mut self, name: &str, compute: impl FnOnce() -> Option<String>) -> Option<String>;
|
||||
// 显式失效
|
||||
pub fn invalidate(&mut self, name: &str);
|
||||
pub fn invalidate_all(&mut self);
|
||||
}
|
||||
```
|
||||
|
||||
**PRINCIPLES_SECTION**(核心行为准则,9 条):
|
||||
```
|
||||
核心原则:
|
||||
1. 主动使用工具搜索最新文献,不要仅凭训练数据回答。
|
||||
2. 优先使用本地资源(get_paper_content / rag_search),必要时再检索新文献。
|
||||
3. 收集到足够信息后立即给出最终答案,避免无意义的重复工具调用。
|
||||
4. 回答时引用具体文献来源,使用 ADS bibcode 标注。
|
||||
5. 对于数学公式,使用标准 LaTeX 格式。
|
||||
6. 用中文回答,保持科学术语的准确性(可附带英文原文)。
|
||||
7. 对于复杂任务(如文献综述),调用 load_skill 获取方法论指引,再用 todo_write 制定计划。
|
||||
8. 如果某个工具调用失败,不要用相同参数重试,尝试换一种方式或工具。
|
||||
9. 任务状态会在每轮开始时从数据库恢复,请基于最新状态继续工作。
|
||||
```
|
||||
缓存策略:静态 section + environment + tools 全部通过 `get_or_compute` 缓存。skills 和 memory 不缓存——前者通过文件监听热更新,后者受 `save_memory` 工具实时影响。
|
||||
|
||||
### 2.3 组装顺序
|
||||
### 静态 Section 常量(6 个)
|
||||
|
||||
每轮调用 `AgentRuntime::system_prompt()` 方法(`src/agent/runtime/mod.rs:1154-1203`),按以下顺序组装 5 个 section:
|
||||
| 常量 | 内容 | 行数 |
|
||||
|------|------|------|
|
||||
| `IDENTITY_SECTION` | 身份声明 | 1 |
|
||||
| `PRINCIPLES_SECTION` | 核心行为准则(9 条) | 9 |
|
||||
| `SYSTEM_CONTEXT_SECTION` | system-reminder 标签说明 + 自动压缩 | 3 |
|
||||
| `TOOL_USAGE_SECTION` | 专用工具优先、并行调用、todo_write | 5 |
|
||||
| `SAFETY_SECTION` | 可逆性、影响范围、确认机制 | 5 |
|
||||
|
||||
### 组装顺序
|
||||
|
||||
`AgentRuntime::system_prompt()` 组装 9 个 section:
|
||||
|
||||
```
|
||||
Section 1: identity 静态 — 最大化 Anthropic prompt cache 命中率
|
||||
Section 2: tools 动态 — 从 ToolRegistry 生成工具名称+摘要列表
|
||||
Section 3: skills 动态 — 从 SkillRegistry.build_reminder() 生成(<system-reminder> XML)
|
||||
Section 4: memory 动态 — 从 MemoryManager.build_system_reminder(5) 生成(<project-memory-context> XML)
|
||||
Section 5: principles 静态 — 核心原则(放在最后 — 若需调整仅影响最后一个 cache segment)
|
||||
[1] identity ← 静态(首次计算后永久缓存)
|
||||
[2] principles ← 静态
|
||||
[3] system_context ← 静态
|
||||
[4] tool_usage ← 静态
|
||||
[5] safety ← 静态
|
||||
[6] environment ← 动态(首次计算后缓存,session 内不变)
|
||||
[7] tools ← 动态(首次计算后缓存,ToolRegistry session 内不变)
|
||||
[8] skills ← 动态(不缓存,文件监听热更新)
|
||||
[9] memory ← 动态(不缓存,save_memory 实时更新)
|
||||
```
|
||||
|
||||
**缓存策略**:静态 section 固定且不变化,放在 prompt 头部以最大化 Anthropic prompt cache 命中率。动态 section(tools、skills、memory)因内容较少,对 cache 影响可控。principles 虽然静态但放在最后,当需要调优时仅破坏最后一个 cache segment。
|
||||
**为什么没有 TTL**:CWD、platform、OS、model、tools 在 session 生命周期内全部不变。首次计算即永久正确,TTL 是多余的复杂度。
|
||||
|
||||
**为什么没有 cache_control 边界标记**:`cache_control: {"type": "ephemeral"}` 是 Anthropic API 专有特性。我们的模型(DeepSeek/Qwen 等 OpenAI 兼容 API)不支持。静态内容前置的顺序本身已足够让服务端按内容哈希自然缓存。
|
||||
|
||||
---
|
||||
|
||||
## 动态 Section 详解
|
||||
|
||||
### 3.1 工具列表 (tools section)
|
||||
### environment section
|
||||
|
||||
```rust
|
||||
let mut tools_desc = String::from("你可以使用以下工具:\n");
|
||||
fn build_environment_section(&self) -> String {
|
||||
// 包含:工作目录、Git 仓库状态、平台、OS 版本、日期、模型名称
|
||||
// 以及 Agent 配置摘要(最大步数、工具超时)
|
||||
}
|
||||
```
|
||||
|
||||
示例输出:
|
||||
```
|
||||
# 环境信息
|
||||
- 工作目录: /home/user/project
|
||||
- Git 仓库: 是
|
||||
- 平台: linux
|
||||
- OS 版本: Linux 7.0.0-22-generic
|
||||
- 日期: 2026-06-22
|
||||
- 当前模型: deepseek-v4-pro
|
||||
- 最大推理步数: 8
|
||||
- 工具超时: 120 秒
|
||||
```
|
||||
|
||||
### tools section
|
||||
|
||||
```rust
|
||||
// 从 ToolRegistry.definitions() 生成工具名称 + 80 字符摘要
|
||||
// ToolRegistry 内部有 schema_cache:工具注册表不变时复用上次计算结果
|
||||
for def in self.tool_registry.definitions() {
|
||||
let short_desc = def.function.description
|
||||
.split('。').next()
|
||||
.unwrap_or(&def.function.description)
|
||||
.chars().take(80)
|
||||
.split('。').next() // 取第一句
|
||||
.chars().take(80) // 截断 80 字符
|
||||
.collect();
|
||||
tools_desc.push_str(&format!("- {}: {}\n", def.function.name, short_desc));
|
||||
}
|
||||
```
|
||||
|
||||
- 19 个默认工具:`search_papers`, `download_paper`, `parse_paper`, `get_paper_content`, `rag_search`, `query_target`, `save_note`, `read_file`, `grep_files`, `glob_files`, `run_bash`, `file_write`, `file_edit`, `todo_write`, `compress_context`, `load_skill`, `subagent`, `save_memory`, `bg_task_run`
|
||||
- 描述仅取**第一句 + 前 80 字符**作为功能摘要
|
||||
- 完整的参数 JSON Schema 通过 API 的 `tools` 参数单独传递,不在 system prompt 中重复
|
||||
完整 JSON Schema 通过 API `tools` 参数单独传递,不在 system prompt 中重复。
|
||||
|
||||
### 3.2 技能列表 (skills section) — 两层加载
|
||||
### skills section — 两层加载
|
||||
|
||||
参考 Claude Code 的两层技能设计,定义在 `src/agent/skills.rs`:
|
||||
|
||||
**Layer 1 (system-reminder)**:`SkillRegistry.build_reminder()` 在 system prompt 中注入 `<system-reminder>` XML 块。列出所有 `user_invocable=true` 且 `disable_model_invocation=false` 的技能名称 + 描述。每个 skill 约消耗 ~20 tokens。
|
||||
**Layer 1 (system-reminder)**:`SkillRegistry.build_reminder()` 列出所有 `user_invocable=true` 的技能名称 + 描述 + when_to_use,每个约 20 tokens。
|
||||
|
||||
```xml
|
||||
<system-reminder>
|
||||
The following skills are available for use with the Skill tool:
|
||||
- methodology: 天体物理研究方法论指南 - When user asks about research methodology
|
||||
- plotting: 数据可视化与科学绘图 - When user wants to create plots
|
||||
- presentation: 学术幻灯片制作 - When user needs to prepare a presentation
|
||||
When a skill matches the user's request, invoke load_skill BEFORE generating any other response about the task.
|
||||
If you see a <command-name> tag in the current conversation turn, the skill has ALREADY been loaded - follow the instructions directly instead of calling load_skill again.
|
||||
When a skill matches the user's request, invoke load_skill BEFORE generating any other response...
|
||||
</system-reminder>
|
||||
```
|
||||
|
||||
**Layer 2 (load_skill 工具)**:LLM 按需调用 `load_skill(skill_name)` 工具,从 `skills/{name}/SKILL.md` 加载完整内容(YAML frontmatter + Markdown body),注入到消息上下文。完整 skill 约 ~2000 tokens。
|
||||
**Layer 2 (load_skill 工具)**:LLM 按需调用,从 `skills/{name}/SKILL.md` 加载完整内容(~2000 tokens),支持 `${SKILL_DIR}` / `${SESSION_ID}` 变量替换和 fork 执行模式。
|
||||
|
||||
SKILL.md 格式:
|
||||
```yaml
|
||||
---
|
||||
name: methodology
|
||||
description: 天体物理研究方法论指南
|
||||
context: inline # inline | fork
|
||||
when_to_use: When user asks about research methodology
|
||||
allowed-tools:
|
||||
- search_papers
|
||||
- rag_search
|
||||
model: inherit
|
||||
user-invocable: true
|
||||
---
|
||||
详见 [skills.md](skills.md)。
|
||||
|
||||
# Skill 正文
|
||||
详细内容...
|
||||
```
|
||||
### memory section
|
||||
|
||||
**热重载**:SkillRegistry 通过 `notify` crate 监听 skills 目录的文件变更,300ms debounce 后自动刷新。Skill 按使用频率排序(指数衰减评分,7 天半衰期)。
|
||||
|
||||
**条件激活**:Skill 可通过 `paths` frontmatter 声明 glob 模式。Agent 访问匹配文件时自动将 `disable_model_invocation` 设为 false,激活条件 skill。
|
||||
|
||||
### 3.3 项目记忆 (memory section)
|
||||
|
||||
`MemoryManager.build_system_reminder(5)` 从 `{library_dir}/memory/` 目录加载最近 5 条记忆,生成 `<project-memory-context>` XML 块:
|
||||
|
||||
```xml
|
||||
<project-memory-context>
|
||||
[PROJECT MEMORY]
|
||||
[偏好] memory-slug: 一句话描述
|
||||
内容预览前三行
|
||||
[时效提示: 此记忆已超过N天,可能已过时]
|
||||
[反馈] another-memory: 描述 [已更新→new-slug]
|
||||
内容预览...
|
||||
使用 save_memory 工具保存重要信息。记忆内容可能过时,请在使用前验证。
|
||||
</project-memory-context>
|
||||
```
|
||||
|
||||
关键特性:
|
||||
- 按 mtime 排序(最新在前),支持语义选择 + 指数衰减排序
|
||||
- 按类型标注:`[偏好]` / `[反馈]` / `[项目]` / `[参考]`
|
||||
- 过期记忆标记为 `[已更新]` 或 `[已更新→new-slug]`(归档为 `{slug}_v1.md`)
|
||||
- 超过 1 天的记忆注入时效警告
|
||||
- 索引文件 `MEMORY.md` 限制 200 行 / 25KB
|
||||
从 `{library_dir}/memory/` 加载,**按 mtime 降序排列**(最新在前),取前 5 条,注入 `<project-memory-context>` XML 块。支持时效警告、过期标记、语义选择。详见 [memory.md](memory.md)。
|
||||
|
||||
---
|
||||
|
||||
## 上下文初始化与运行时注入 (`src/agent/runtime/context.rs`)
|
||||
## 上下文初始化 (`src/agent/runtime/context.rs`)
|
||||
|
||||
### 4.1 上下文构建流程
|
||||
|
||||
`build_initial_context()` 在每轮开始时构建完整的消息列表:
|
||||
`build_initial_context()` 流程:
|
||||
|
||||
```
|
||||
1. 从数据库加载历史消息(agent_messages 表)
|
||||
2. 如果历史第一条不是 system 角色 → 在位置 0 插入系统提示词
|
||||
1. 加载历史消息(agent_messages 表)
|
||||
2. 如果第一条不是 system 角色 → 插入系统提示词
|
||||
3. 追加当前用户问题
|
||||
4. [可选] 追加任务状态恢复提醒(从 agent_tasks 表读取)
|
||||
4. [可选] 追加任务状态恢复提醒(agent_tasks 表)
|
||||
```
|
||||
|
||||
### 4.2 任务状态恢复
|
||||
运行时干预通过**注入 user 消息**实现(不修改 system prompt):
|
||||
|
||||
从 `agent_tasks` 表恢复未完成的任务,格式化注入 user 消息:
|
||||
|
||||
```
|
||||
[当前任务状态]
|
||||
以下是上次会话中持久化的任务计划,请基于最新状态继续工作:
|
||||
|
||||
⏳ [task-1] 搜索相关文献...
|
||||
🔄 [task-2] 分析论文数据... (依赖: task-1)
|
||||
✅ [task-3] 格式化引用... (指派: lead)
|
||||
|
||||
使用 todo_write 工具更新任务进度。
|
||||
```
|
||||
|
||||
### 4.3 运行时 Nudge 注入
|
||||
|
||||
在 ReAct 循环中,system prompt 组装后不再修改。运行时干预通过**注入 user 消息**实现(开闭原则):
|
||||
|
||||
| 触发条件 | Nudge 内容 |
|
||||
| 触发条件 | 注入内容 |
|
||||
|:---|:---|
|
||||
| TodoWrite 连续 3 步未更新 | "提醒:你已经连续多步未更新任务计划。建议调用 todo_write 工具…" |
|
||||
| Token 预算 diminishing returns | "检测到你的后续步骤未产生新信息…请基于已收集的全部信息直接给出最终答案" |
|
||||
| 达到最大步数 (max_steps) | "你已经执行了 N 步(最大 M 步)。请根据已有信息直接给出最终答案" |
|
||||
| 后台任务完成 | "[后台任务完成] ✅ tool_name: bibcode: summary" |
|
||||
| TodoWrite 3 步未更新 | "提醒:建议调用 todo_write 工具复盘进度" |
|
||||
| Token 预算 diminishing returns | "检测到重复操作模式,请直接给出最终答案" |
|
||||
| 达到最大步数 | "已执行 N 步(最大 M 步),请直接给出最终答案" |
|
||||
| 后台任务完成 | "[后台任务完成] ✅ tool_name: summary" |
|
||||
|
||||
---
|
||||
|
||||
## 子代理的独立系统提示词 (`src/agent/tools/subagent.rs`)
|
||||
## 子代理模块化系统提示词 (`src/agent/tools/subagent.rs`)
|
||||
|
||||
子代理拥有独立的消息上下文,通过 `SubAgentRunner::run()` 接收一个**硬编码的简化版系统提示词**:
|
||||
子代理使用完整模块化系统提示词(不再硬编码 96 字符):
|
||||
|
||||
```
|
||||
你是一位专业的天体物理学研究助手,在一个独立的子任务上下文中工作。
|
||||
你可以使用文献搜索、下载、RAG检索等工具。
|
||||
请高效完成任务,然后直接给出最终答案。不要进行不必要的重复操作。
|
||||
用中文回答,引用具体文献来源。
|
||||
[1] identity ← 与父代理相同
|
||||
[2] subagent_context ← "在独立的子任务上下文中工作,请专注于完成这项任务"
|
||||
[3] principles ← 与父代理相同
|
||||
[4] system_context ← 与父代理相同
|
||||
[5] tool_usage ← 与父代理相同
|
||||
[6] safety ← 与父代理相同
|
||||
[7] tools ← 运行时从 ToolRegistry 生成
|
||||
```
|
||||
|
||||
特点:
|
||||
- 不继承父代理的 tools/skills/memory sections
|
||||
- 共享父代理的 ToolRegistry
|
||||
- 通过 `PermissionChecker` 可在特定场景下限制工具访问
|
||||
- 独立的 ReAct 循环(步数上限通过参数传入,默认 5,最大 10)
|
||||
- 完整的 Hook 管道(PreToolUse/PostToolUse/SubagentStart/SubagentStop)
|
||||
- 包含活跃度日志(activity log),返回给父代理时附带工具调用统计
|
||||
|
||||
### 5.2 团队成员的独立提示词 (`src/agent/team/teammate.rs`)
|
||||
|
||||
队友的 `system_prompt` 和 `task_prompt` 由 `team/manager.rs`(lead 的委托逻辑)在运行时构造并传入 `run_teammate_loop()`:
|
||||
- prompt 内容完全由 lead 的决定
|
||||
- 队友不包含 `subagent` 工具(防止无限委托链)
|
||||
- 更轻量的 ReAct 循环(无 SSE、无 DB 持久化、无 hooks)
|
||||
- 步数上限更严格(min(max_steps, 5))
|
||||
- 通过文件收件箱与 lead 通信(每 5 秒 poll,最长 60 秒)
|
||||
预构建 `ToolRegistry`,在构造系统提示词前获取 `definitions()`。`SubAgentRunner` 新增 `new_with_registry_and_hooks()` 构造函数。详见 [subagent.md](subagent.md)。
|
||||
|
||||
---
|
||||
|
||||
## 上下文压缩中的独立提示词 (`src/agent/compact.rs`)
|
||||
## 上下文压缩 + CollapseLog (`src/agent/compact.rs`)
|
||||
|
||||
### 6.1 四层压缩策略
|
||||
### 四层压缩策略
|
||||
|
||||
| 层 | 方法 | API 调用 | 行为 |
|
||||
| 层 | 方法 | API | 行为 |
|
||||
|:---|:---|:---|:---|
|
||||
| Layer 0 | `snip_compact` | 无 | 消息数超过 50 时截断中间段,保留头 3 + 尾 47 |
|
||||
| Layer 1 | `micro_compact` | 无 | 将较早的工具结果替换为 `[Previous: used {tool_name}]` 占位符 |
|
||||
| Layer 2 | `auto_compact` | 1 次 | LLM 摘要对话历史(见下),注入 `[历史对话摘要]` |
|
||||
| Layer 3 | `aggressive_micro` | 无 | 保留最近 2 条工具结果,其余替换为占位符 |
|
||||
| 0 | `snip_compact` | 无 | 消息 >50 时截断中间段,保留头 3 + 尾 |
|
||||
| 1 | `micro_compact` | 无 | 较早工具结果替换为 `[Previous: used {name}]` 占位符 |
|
||||
| 2 | `auto_compact` | 1 次 | LLM 摘要对话历史 |
|
||||
| 3 | `aggressive_micro` | 无 | 保留最近 2 条工具结果 |
|
||||
|
||||
### 6.2 LLM 摘要 Prompt
|
||||
### CollapseLog
|
||||
|
||||
Layer 2 中调用 LLM 生成摘要时,使用独立的系统提示词:
|
||||
`compress_context_with_hooks_and_log()` 在每次压缩后记录结构化 commit:
|
||||
|
||||
```
|
||||
系统: "你是一个对话摘要助手。请提取对话的关键信息和结论。"
|
||||
用户: "请用简洁的中文总结以下对话历史的要点(不超过500字):
|
||||
|
||||
[用户] ...
|
||||
[助手] ...
|
||||
[工具] ..."
|
||||
```rust
|
||||
log.commit(CollapseMethod::LlmSummary, (after_count, before_count), summary);
|
||||
```
|
||||
|
||||
### 6.3 身份再注入
|
||||
|
||||
如果压缩后消息过少(≤4 条),注入身份确认块防止模型丢失上下文认知:
|
||||
|
||||
```
|
||||
[身份确认] 你是一位专业的天体物理学研究助手。以上是历史对话的压缩摘要。
|
||||
你正在进行的研究任务是回答用户的问题。请基于摘要中的关键信息继续工作,
|
||||
需要更多信息时主动使用工具搜索。
|
||||
```
|
||||
|
||||
### 6.4 安全切割
|
||||
|
||||
`find_safe_cut_point()` 确保压缩时不会破坏 `assistant(tool_calls)` / `tool_result` 配对关系,向前追溯找到完整工具交互的边界。
|
||||
|
||||
### 6.5 熔断器
|
||||
|
||||
`CompactionCircuitBreaker` 防止连续压缩失败时的无限循环。连续 3 次压缩后消息数未减少 → 打开熔断器,后续跳过自动压缩。
|
||||
超 5 条 commits 时触发**溢出合并**,将最早的 commits 合并为摘要注入消息列表。详见 `collapse.rs`。
|
||||
|
||||
---
|
||||
|
||||
## Hook 系统与提示词的交互 (`src/agent/hooks.rs`)
|
||||
## Hook 系统与提示词交互
|
||||
|
||||
Hook 系统定义 9 个生命周期事件,其中与提示词相关的交互:
|
||||
9 个生命周期事件:
|
||||
|
||||
| Hook | 与提示词的关系 |
|
||||
|:---|:---|
|
||||
| `OnSessionStart` | 在提示词组装前触发,可影响任务状态恢复逻辑 |
|
||||
| `PreToolUse::MutateInput` | 可向工具执行注入 `additional_context`(作为 user 消息追加) |
|
||||
| `PreToolUse::Block` | 阻止特定工具的执行(如取消检查) |
|
||||
| `PostToolUse::MutateOutput` | 可修改工具输出内容(影响后续 LLM 看到的 context) |
|
||||
| `OnStepComplete` | 每步结束记录 token 估算、消息数等指标 |
|
||||
| `PreCompact` | 压缩前记录消息数和 token 估算 |
|
||||
| `PostCompact` | 压缩后记录最终消息数和压缩方法 |
|
||||
| `OnSubagentStart/Stop` | 子代理启动/停止时传递 prompt 和结果摘要 |
|
||||
| `OnSessionStop` | 会话终止时清理取消状态并记录终止原因 |
|
||||
| `OnSessionStart` | 提示词组装前触发 |
|
||||
| `PreToolUse::MutateInput` | 注入 `additional_context`(追加为 user 消息) |
|
||||
| `PostToolUse::MutateOutput` | 修改工具输出(影响 LLM 看到的 context) |
|
||||
| `PreCompact / PostCompact` | 压缩前后记录指标 + CollapseLog commit |
|
||||
| `OnSubagentStart/Stop` | 传递子代理 prompt 和结果摘要 |
|
||||
|
||||
详见 [hooks.md](hooks.md)。
|
||||
|
||||
---
|
||||
|
||||
## 完整数据流
|
||||
## Claude Code 工具按需发现机制(参考分析)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
RT["AgentRuntime 创建<br/>system_prompt() 调用"]
|
||||
|
||||
RT --> S1["Section 1: identity<br/>(静态常量)"]
|
||||
RT --> S2["Section 2: tools<br/>(ToolRegistry definitions)"]
|
||||
RT --> S3["Section 3: skills<br/>(SkillRegistry.build_reminder)"]
|
||||
|
||||
S1 --> S4
|
||||
S2 --> S4
|
||||
S3 --> S4["Section 4: memory (可选)<br/>(MemoryManager.build_reminder, 5 entries)"]
|
||||
|
||||
S4 --> S5["Section 5: principles<br/>(静态常量)"]
|
||||
S5 --> ASM["assemble()<br/>join('\n\n')"]
|
||||
|
||||
ASM --> Main["主 Agent 上下文<br/>build_initial_context()<br/>+ nudge 注入 + 任务恢复 + 后台通知"]
|
||||
ASM --> Sub["子 Agent 上下文<br/>SubAgentRunner.run()<br/>(独立 system_prompt)"]
|
||||
|
||||
Main --> React["ReAct 循环"]
|
||||
Main --> NudgeInj["Nudge 消息注入 (user)"]
|
||||
Main --> Compact["压缩层<br/>generate_summary()<br/>+ identity re-injection"]
|
||||
> 当前项目工具数量较少(~25 个),尚未实现此机制。以下为 Claude Code 的设计分析,作为未来工具增长时的参考。
|
||||
|
||||
### 要解决的问题
|
||||
|
||||
当接入大量 MCP 工具(30+ 服务器,100+ 工具)时,所有工具的完整 JSON Schema 在每轮 API 调用中占据大量上下文——绝大多数工具从未被调用,却每轮都在消耗 token。Claude Code 的解决方案:**延迟加载 + 按需发现**。
|
||||
|
||||
### 两层架构
|
||||
|
||||
**Layer 1: `defer_loading` 标记**
|
||||
|
||||
工具分为两类:
|
||||
|
||||
| 类型 | 行为 | 示例 |
|
||||
|------|------|------|
|
||||
| 常驻工具 | 始终在 `tools` 数组中,立即可用 | Read、Write、Bash、Glob、Grep、Task、TodoWrite |
|
||||
| 延迟工具 | 标记 `defer_loading: true`,仅在模型主动发现后才加入 `tools` 数组 | MCP 工具、EnterPlanMode、NotebookEdit、LSP 工具 |
|
||||
|
||||
判断逻辑(`isDeferredTool()`):
|
||||
```
|
||||
1. alwaysLoad == true → 永不延迟
|
||||
2. isMcp == true → 始终延迟(除非 MCP server 设 _meta['anthropic/alwaysLoad'])
|
||||
3. shouldDefer == true → 延迟
|
||||
```
|
||||
|
||||
**Layer 2: ToolSearch 工具 + tool_reference 块**
|
||||
|
||||
模型通过 `ToolSearch` 工具按需发现延迟工具:
|
||||
|
||||
```
|
||||
模型: ToolSearch(query: "github create PR")
|
||||
系统: 返回 tool_reference 块 → [mcp__github__createPullRequest, mcp__github__listPRs]
|
||||
下次 API 调用: 这两个工具的完整 schema 加入 tools 数组
|
||||
```
|
||||
|
||||
`tool_reference` 是一个特殊的 content block 类型,API 服务端收到后会展开为完整工具定义,模型可以在后续 turn 直接调用。
|
||||
|
||||
### 消息流转
|
||||
|
||||
```
|
||||
Turn N:
|
||||
tools 数组 = 常驻工具 + ToolSearch + 之前发现过的延迟工具
|
||||
<available-deferred-tools> 块列出所有可发现的延迟工具名称
|
||||
|
||||
→ 模型调用 ToolSearch(query: "slack")
|
||||
→ tool_result 包含 tool_reference 块: [mcp__slack__sendMessage]
|
||||
|
||||
Turn N+1:
|
||||
tools 数组 = 常驻工具 + ToolSearch + [mcp__slack__sendMessage, ...]
|
||||
→ 模型可以直接调用 mcp__slack__sendMessage
|
||||
```
|
||||
|
||||
### 发现状态持久化
|
||||
|
||||
`extractDiscoveredToolNames()` 从消息历史中扫描所有 `tool_reference` 块,提取已发现的工具名。压缩时通过 compact boundary marker 的 `preCompactDiscoveredTools` 字段保存已发现集合,防止压缩丢失发现状态。
|
||||
|
||||
### 工具发现状态变更通知
|
||||
|
||||
两个机制告诉模型有哪些可发现工具:
|
||||
|
||||
**Legacy**:`<available-deferred-tools>` 块作为 user 消息前置注入(每次工具池变更都会 bust prompt cache)。
|
||||
|
||||
**Modern**:`deferred_tools_delta` 附件——diff 上次通知的工具池,仅发送增量变更(added/removed),避免 bust cache。
|
||||
|
||||
### ToolSearch 搜索方式
|
||||
|
||||
- **精确选择** `select:ToolA,ToolB` — 按名称直接取工具
|
||||
- **关键词搜索** `github create PR` — 搜索工具名 + searchHint + description,加权评分
|
||||
- **必选词** `+slack send` — `+` 前缀表示必须匹配
|
||||
|
||||
### 对项目的适用性
|
||||
|
||||
| 当前状态 | 是否需要 |
|
||||
|---------|---------|
|
||||
| ~25 个工具,schema 总共 ~8K tokens | **暂不需要** |
|
||||
| 无 MCP 工具接入 | 不需要 |
|
||||
| 工具数量稳定 | 不需要 |
|
||||
|
||||
**触发条件**:当工具数量超过 ~50 或接入 MCP 服务器时,可以按以下步骤接入:
|
||||
1. 为 MCP 工具设置 `shouldDefer: true` / `isMcp: true`
|
||||
2. 注册 `ToolSearch` 工具
|
||||
3. 在 ToolRegistry 中维护 `deferred_tool_names` 集合
|
||||
4. API 调用时过滤 tools 数组 + 注入 `<available-deferred-tools>` 块
|
||||
5. 压缩时保存 `preCompactDiscoveredTools` 快照
|
||||
|
||||
---
|
||||
|
||||
@@ -327,33 +328,27 @@ flowchart TD
|
||||
|
||||
| 文件 | 职责 |
|
||||
|:---|:---|
|
||||
| `src/agent/runtime/system_prompt.rs` | SystemPrompt 组装器 + 静态常量 |
|
||||
| `src/agent/runtime/mod.rs:1154-1203` | `system_prompt()` 方法 — 5 section 拼装 |
|
||||
| `src/agent/runtime/system_prompt.rs` | SystemPrompt 组装器 + 6 个静态常量 + SystemPromptCache |
|
||||
| `src/agent/runtime/mod.rs` | `system_prompt()` + `build_environment_section()` |
|
||||
| `src/agent/runtime/context.rs` | `build_initial_context()` — 上下文初始化 + 任务恢复 |
|
||||
| `src/agent/skills.rs` | SkillRegistry — 两层技能加载 + 热重载 |
|
||||
| `src/agent/memory/mod.rs` | MemoryManager — 记忆加载 + system reminder 构建 |
|
||||
| `src/agent/compact.rs` | 四层压缩 + LLM 摘要 prompt + 身份再注入 |
|
||||
| `src/agent/tools/subagent.rs` | 子代理系统提示词(硬编码) |
|
||||
| `src/agent/subagent.rs` | SubAgentRunner — 子代理 ReAct 循环 |
|
||||
| `src/agent/team/teammate.rs` | 队友 ReAct 循环(外部传入 system_prompt) |
|
||||
| `src/agent/hooks.rs` | 9 个生命周期 hook + 提示词交互 |
|
||||
| `src/agent/memory/mod.rs` | MemoryManager — 按 recency 排序的记忆注入 |
|
||||
| `src/agent/tools/mod.rs` | ToolRegistry — schema_cache + definition_filter |
|
||||
| `src/agent/tools/subagent.rs` | 子代理模块化系统提示词构建 |
|
||||
| `src/agent/subagent.rs` | SubAgentRunner — new_with_registry_and_hooks |
|
||||
| `src/agent/compact.rs` | 四层压缩 + CollapseLog 集成 |
|
||||
| `src/agent/compact/collapse.rs` | CollapseLog — 压缩历史记录 + 溢出合并 |
|
||||
| `src/agent/hooks/` | 生命周期 hook + 提示词交互 |
|
||||
| `docs/architecture/agent/system-prompt-optimization-plan.md` | 优化计划文档(背景、方案、对比分析) |
|
||||
|
||||
---
|
||||
|
||||
## 设计要点
|
||||
|
||||
### 优势
|
||||
|
||||
1. **模块化 section 组装**:各 section 独立管理,便于调试和迭代
|
||||
2. **静态 section 前置**:最大化 Anthropic prompt cache 命中率,降低延迟和成本
|
||||
3. **两层 skill 加载**:避免一次性注入所有 skill 的 token 浪费
|
||||
4. **压缩时身份再注入**:防止激进压缩后模型丢失角色认知
|
||||
5. **安全切割点**:`find_safe_cut_point` 确保压缩不破坏 tool_call/tool_result 配对
|
||||
6. **运行时 nudge 而非 system prompt 编辑**:遵循开闭原则,system prompt 保持稳定
|
||||
|
||||
### 潜在改进方向
|
||||
|
||||
1. **子代理系统提示词继承**:当前子代理的 system prompt 是硬编码的,可考虑让子代理也接收 section 组装器,选择性继承 skills/memory
|
||||
2. **压缩 prompt 外部化**:摘要生成和身份确认的 prompt 可配置化,便于独立调优
|
||||
3. **记忆注入锁竞争**:`memory_manager.try_lock()` 在高并发下可能静默失败,考虑使用 `RwLock::read()`
|
||||
4. **工具描述摘要策略**:80 字符截断可能丢失关键语义,可考虑 LLM 预生成工具描述摘要
|
||||
1. **静态前置,动态后置**:内容不变的 section 先出现,服务端自然按哈希缓存
|
||||
2. **首次计算,永久缓存**:session 内一切不变,不需要 TTL;仅在 /clear 时全部失效
|
||||
3. **模块化 section**:各 section 独立管理,便于调试、增删、A/B 测试
|
||||
4. **两层 skill 加载**:20 tokens 列表 vs 2000 tokens 全文,按需加载
|
||||
5. **子代理完整提示词**:共享主代理的静态常量 + 独立 tools 列表
|
||||
6. **CollapseLog 追踪**:每次压缩记录结构化 commit,溢出自动合并
|
||||
7. **运行时 nudge**:干预通过 user 消息注入,不修改 system prompt(开闭原则)
|
||||
|
||||
@@ -16,7 +16,7 @@ graph TB
|
||||
subgraph Layer2["执行协调层 — Executor"]
|
||||
direction LR
|
||||
EX["src/agent/runtime/executor.rs<br/>验证 → PreToolUse hooks → 并行调度 → PostToolUse"]
|
||||
SX["src/agent/runtime/streaming_executor.rs (流式变体)<br/>流式 tool_use 到达时立即调度 + Sibling Abort"]
|
||||
SX["src/agent/runtime/streaming_executor.rs (流式变体)<br/>流式 tool_use 到达时立即调度 + Sibling Abort<br/>非并发工具独占执行 (executing_non_concurrent 标志)"]
|
||||
end
|
||||
|
||||
subgraph Layer3["业务逻辑层 — AgentTool Trait + 工具实现"]
|
||||
@@ -185,21 +185,30 @@ LLM stream → tool_calls[]
|
||||
│ └─ mutated_args + additional_contexts 收集
|
||||
│
|
||||
▼
|
||||
┌─ execute_parallel() ─────────────────────────────────────────┐
|
||||
│ FuturesUnordered 并发调度: │
|
||||
│ 每个 PreparedCall → tokio::spawn(async { │
|
||||
│ tokio::select! { │
|
||||
│ timeout(tool_timeout_secs) → 执行工具 │
|
||||
│ cancel_fut (每 250ms 轮询) → 返回错误 │
|
||||
│ } │
|
||||
│ }) │
|
||||
┌─ execute_parallel() — ToolPartitioner 批次调度 ──────────────┐
|
||||
│ │
|
||||
│ 中断处理: │
|
||||
│ Phase 3a: 构建非拒绝工具的 (原索引, PreparedCall) 映射 │
|
||||
│ Phase 3b: ToolPartitioner::partition() 分区 │
|
||||
│ · 连续 concurrency_safe 工具 → 并行批次 │
|
||||
│ · 非 concurrency_safe 工具 → 独占串行批次 │
|
||||
│ · 示例: [read, grep, bash, read, write] │
|
||||
│ → [read∥grep], [bash], [read], [write] │
|
||||
│ │
|
||||
│ Phase 3c: 逐批次执行 │
|
||||
│ · 并行批次 → FuturesUnordered 并发 (tokio::spawn each) │
|
||||
│ · 串行批次 → 逐个执行 (前一个完成后才启动下一个) │
|
||||
│ │
|
||||
│ 中断处理(每工具内): │
|
||||
│ tokio::select! { │
|
||||
│ timeout(tool_timeout_secs) → 执行工具 │
|
||||
│ cancel_fut (每 250ms 轮询) → 返回错误 │
|
||||
│ } │
|
||||
│ InterruptBehavior::Block → 忽略 cancel_fut,等完成 │
|
||||
│ InterruptBehavior::Cancel → 响应取消,注入错误 │
|
||||
│ │
|
||||
│ 渐进式结果处理 (while exec_futs.next()): │
|
||||
│ 完成即处理,快工具不因慢工具阻塞 │
|
||||
│ 辅助函数: │
|
||||
│ execute_single_tool() — 单工具超时+取消+执行 │
|
||||
│ process_single_result() — 结果处理+SSE推送+TTL持久化+Hook │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼ (每个工具完成后逐个处理)
|
||||
@@ -221,27 +230,32 @@ LLM stream → tool_calls[]
|
||||
### 并发模型细节
|
||||
|
||||
```rust
|
||||
// executor.rs: FuturesUnordered 中的每个 future
|
||||
Box::pin(async move {
|
||||
let interrupt_behavior = tool.interrupt_behavior();
|
||||
let is_blocking = interrupt_behavior == InterruptBehavior::Block;
|
||||
|
||||
tokio::select! {
|
||||
res = tokio::time::timeout(timeout_dur, tool_fut) => {
|
||||
// 正常完成或超时
|
||||
// executor.rs Phase 3c: 逐批次执行
|
||||
for batch in &batches {
|
||||
if batch.is_parallel {
|
||||
// 并行批次: FuturesUnordered 内并发执行
|
||||
let mut exec_futs: FuturesUnordered<_> = batch.calls.iter()
|
||||
.map(|prep| execute_single_tool(...))
|
||||
.collect();
|
||||
while let Some(result) = exec_futs.next().await {
|
||||
process_single_result(...).await;
|
||||
}
|
||||
_ = cancel_fut => {
|
||||
// 仅当 !is_blocking 时此分支可达
|
||||
// Block 工具的 cancel_fut loop 不 break
|
||||
} else {
|
||||
// 串行批次: 逐个执行(非并发安全工具独占)
|
||||
for prep in &batch.calls {
|
||||
let result = execute_single_tool(...).await;
|
||||
process_single_result(...).await;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
关键特性:
|
||||
- 所有工具放入同一个 `FuturesUnordered`,不区分串行/并行批次
|
||||
- `is_concurrency_safe` 声明为语义标记(引导 LLM 并发调用),执行时全部并发
|
||||
- 实际串行化依赖工具内部的 mutex/文件锁
|
||||
- `ToolPartitioner::partition()` 将工具调用分为并行批次和串行批次
|
||||
- 并行批次内使用 `FuturesUnordered` 最大化并发
|
||||
- 串行批次内逐个执行(如 `run_bash`、`file_write` 独占)
|
||||
- 连续的并发安全工具自动合并到一个并行批次
|
||||
- `is_concurrency_safe(args)` 是**输入感知**的:同一工具可能因参数不同而安全属性不同
|
||||
- `InterruptBehavior::Block` 保护写入操作不被用户取消打断
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user