架构重构: - Agent Runtime 由单文件拆为 runtime/ 目录 12 模块(熔断/流式执行/Token预算/文件缓存/权限等) - Agent Tools 由单文件拆为 tools/ 目录 20+ 模块(filesystem/astro/memory/skill/subagent/team 等) - 解析器体系重构(common.rs 836行变更),各解析器同步升级 - Download 服务重构(562行),反爬策略强化 - LLM 客户端重构(266行),流式调用优化 新子系统: - Hooks 生命周期系统(9种事件类型,PreToolUse/PostToolUse 支持输入输出拦截) - Skills 双层加载系统(system-reminder 轻量注入 + LoadSkillTool 按需加载,notify 文件监听热更新) - Memory 项目记忆管理(类型/提取/去重/衰减/保活/选择策略/护栏 7 模块) - SubAgent 上下文隔离子代理运行器(独立 ReAct 循环 + Hook 管道) - Team 多智能体团队协作(文件 inbox 通信、lead/teammate 协调) - TaskBoard DAG 任务依赖管理 - Trajectory 会话轨迹、Terminal 终止信号、Autonomous 自主模式、Background 异步通知 数据库: - agent_tasks 表(DAG 依赖模式,blocked_by JSON 数组) - agent_audit_log 表(工具调用审计:名称/状态/耗时/输出预览) - agent_identity 迁移(消息/审计/任务的 agent_name 归属,agent_team_members 团队注册表) API: - GET /chat/metrics 聚合指标端点 - GET /chat/sessions/:id/audit 会话审计查询 - GET /chat/questions + POST /chat/answer 人机交互问答 工程: - 新增依赖:serde_yaml、notify、glob、walkdir、lru - Skills 目录含 methodology/plotting/presentation 三个初始 SKILL.md - CLAUDE.md 完整项目架构文档
739 lines
21 KiB
Markdown
739 lines
21 KiB
Markdown
# Agent 架构优化分析
|
||
|
||
> 对比 Claude Code 源码 (`/home/fmq/program/claudecode/src/`) 与 AstroResearch Agent (`src/agent/`),
|
||
> 基于 2026-06-16 的代码快照。
|
||
|
||
---
|
||
|
||
## 总体评估
|
||
|
||
我们的 Agent 已经实现了一个功能完整的 ReAct 研究引擎,涵盖了工具注册/调度、三层上下文压缩、生命周期 Hooks、
|
||
多 Agent 团队协作、子代理委托、后台任务、Skills 加载等关键子系统。与 Claude Code 的架构范式高度一致。
|
||
|
||
以下按**影响优先级**列出可优化领域。
|
||
|
||
---
|
||
|
||
## 一、CRITICAL:Streaming Tool Executor
|
||
|
||
### 现状
|
||
`executor.rs` 在 LLM 流式响应**完全结束后**才通过 `join_all` 并行执行工具。
|
||
|
||
```rust
|
||
// 当前流程:LLM stream → 收集所有 tool_use blocks → join_all 执行
|
||
let results = futures_util::future::join_all(exec_futs).await;
|
||
```
|
||
|
||
### Claude Code 做法
|
||
`StreamingToolExecutor` 在模型**仍在生成** tool_use blocks 时就开始调度执行:
|
||
|
||
```
|
||
模型输出 tool_use(Read file A) → 立即开始读 A
|
||
模型输出 tool_use(Read file B) → 立即开始读 B(并发安全)
|
||
模型输出 tool_use(Bash cmd) → 排队等待(非并发安全)
|
||
模型输出结束 → 此时 A 和 B 可能已完成
|
||
```
|
||
|
||
### 优化方案
|
||
|
||
```rust
|
||
/// 流式工具执行器 —— 模型还在输出时就开始执行工具
|
||
pub struct StreamingToolExecutor {
|
||
tools: Vec<TrackedTool>,
|
||
tool_registry: Arc<ToolRegistry>,
|
||
tool_context: ToolContext,
|
||
max_concurrency: usize,
|
||
}
|
||
|
||
enum ToolStatus {
|
||
Queued,
|
||
Executing,
|
||
Completed,
|
||
Yielded,
|
||
}
|
||
|
||
struct TrackedTool {
|
||
id: String,
|
||
block: ToolCall,
|
||
status: ToolStatus,
|
||
is_concurrency_safe: bool,
|
||
handle: Option<JoinHandle<ToolOutput>>,
|
||
results: Option<Vec<ToolResultMessage>>,
|
||
pending_progress: Vec<ProgressMessage>,
|
||
}
|
||
|
||
impl StreamingToolExecutor {
|
||
/// 模型每输出一个 tool_use block 就调用此方法
|
||
pub fn add_tool(&mut self, block: ToolCall) {
|
||
let is_safe = self.tool_registry
|
||
.get(&block.name)
|
||
.map(|t| t.is_concurrency_safe(&block.args))
|
||
.unwrap_or(false);
|
||
|
||
self.tools.push(TrackedTool {
|
||
id: block.id.clone(),
|
||
block,
|
||
status: ToolStatus::Queued,
|
||
is_concurrency_safe: is_safe,
|
||
handle: None,
|
||
results: None,
|
||
pending_progress: vec![],
|
||
});
|
||
|
||
tokio::spawn(async { self.process_queue().await });
|
||
}
|
||
|
||
/// 非阻塞获取已完成的工具结果
|
||
pub fn get_completed_results(&mut self) -> Vec<ToolResultMessage> {
|
||
// 按顺序 yield 已完成的结果
|
||
// 非并发安全的工具保持顺序
|
||
// 进度消息立即 yield
|
||
}
|
||
|
||
/// 等待所有剩余工具完成
|
||
pub async fn get_remaining_results(&mut self) -> Vec<ToolResultMessage> {
|
||
// 等待 executing 的工具完成
|
||
// 然后 yield 所有结果
|
||
}
|
||
}
|
||
```
|
||
|
||
**预期收益**:大幅降低端到端延迟,尤其是当模型并行输出多个独立的 Read/Search 类工具调用时。
|
||
|
||
---
|
||
|
||
## 二、HIGH:工具并发分区
|
||
|
||
### 现状
|
||
`executor.rs` 对所有工具调用一律使用 `join_all` 并行执行,不考虑工具的并发安全性。
|
||
|
||
### Claude Code 做法
|
||
`partitionToolCalls()` 将工具调用分区为:
|
||
1. **并发安全批次** — 连续的 `isConcurrencySafe=true` 工具(如 Read、Grep、WebSearch)
|
||
2. **串行批次** — 单个 `isConcurrencySafe=false` 工具(如 Bash、Edit、Write)
|
||
|
||
并发批次用 `all()` 并行执行(max concurrency = 10),串行批次逐个执行。
|
||
|
||
### 优化方案
|
||
|
||
```rust
|
||
/// 工具特征增加并发安全声明
|
||
#[async_trait]
|
||
pub trait AgentTool: Send + Sync {
|
||
fn name(&self) -> &str;
|
||
fn description(&self) -> &str;
|
||
fn parameters(&self) -> serde_json::Value;
|
||
|
||
/// 工具是否可以与其他并发安全的工具同时执行
|
||
fn is_concurrency_safe(&self, _args: &serde_json::Value) -> bool {
|
||
true // 默认只读工具是并发安全的
|
||
}
|
||
|
||
/// 最大并发数(默认无限制)
|
||
fn max_concurrency(&self) -> Option<usize> {
|
||
None
|
||
}
|
||
|
||
async fn execute(&self, args: serde_json::Value, ctx: &ToolContext) -> ToolOutput;
|
||
}
|
||
|
||
/// 分区工具调用
|
||
fn partition_tool_calls(
|
||
calls: &[PreparedCall],
|
||
registry: &ToolRegistry,
|
||
) -> Vec<Batch> {
|
||
let mut batches: Vec<Batch> = vec![];
|
||
for call in calls {
|
||
let is_safe = registry.get(&call.tool_name)
|
||
.map(|t| t.is_concurrency_safe(&call.args))
|
||
.unwrap_or(false);
|
||
|
||
if is_safe && batches.last().map_or(false, |b| b.is_concurrency_safe) {
|
||
batches.last_mut().unwrap().calls.push(call.clone());
|
||
} else {
|
||
batches.push(Batch {
|
||
is_concurrency_safe: is_safe,
|
||
calls: vec![call.clone()],
|
||
});
|
||
}
|
||
}
|
||
batches
|
||
}
|
||
```
|
||
|
||
**预期收益**:避免 Bash/Write 等有副作用的工具与其他工具竞争导致的不确定性。
|
||
|
||
---
|
||
|
||
## 三、HIGH:Sibling Abort(兄弟中止)
|
||
|
||
### 现状
|
||
当一个工具执行出错时,其他并行执行的工具继续运行,浪费资源。
|
||
|
||
### Claude Code 做法
|
||
`StreamingToolExecutor` 中:
|
||
- 当 Bash 工具出错时,`siblingAbortController.abort("sibling_error")` 中止所有兄弟 Bash 执行
|
||
- Read/WebFetch 等独立工具的失败不影响其他工具
|
||
- 被中止的工具获得 synthetic error message
|
||
|
||
### 优化方案
|
||
|
||
```rust
|
||
/// 在并行执行时注入 sibling abort 信号
|
||
pub struct SiblingAbortController {
|
||
abort_sender: tokio::sync::broadcast::Sender<SiblingAbortReason>,
|
||
errored_tool_description: Arc<tokio::sync::Mutex<Option<String>>>,
|
||
}
|
||
|
||
enum SiblingAbortReason {
|
||
SiblingError { description: String },
|
||
UserInterrupted,
|
||
StreamingFallback,
|
||
}
|
||
|
||
impl SiblingAbortController {
|
||
/// 当工具出错时调用,如果是 Bash 类工具则通知所有兄弟
|
||
pub fn notify_error(&self, tool_name: &str, tool_desc: &str) {
|
||
if is_bash_like_tool(tool_name) {
|
||
let _ = self.abort_sender.send(SiblingAbortReason::SiblingError {
|
||
description: tool_desc.to_string(),
|
||
});
|
||
}
|
||
}
|
||
|
||
/// 每个工具执行前检查是否已被兄弟中止
|
||
pub fn check_aborted(&self, this_tool: &str) -> Option<String> {
|
||
// 如果是本工具报的错,不生成 synthetic error(避免重复)
|
||
}
|
||
}
|
||
```
|
||
|
||
**预期收益**:避免无效的后续执行,减少等待时间和 API 调用浪费。
|
||
|
||
---
|
||
|
||
## 四、HIGH:Error Recovery Ladder(错误恢复阶梯)
|
||
|
||
### 现状
|
||
`compact.rs` 只有**主动压缩**(在达到 token 限制前触发)。如果压缩不够激进,413 `prompt_too_long` 错误会直接暴露给用户。
|
||
|
||
### Claude Code 做法
|
||
`query.ts` 实现了多层恢复阶梯:
|
||
|
||
```
|
||
第1层:Context Collapse drain(便宜,commit 已 staged 的 collapse)
|
||
↓ 失败/不可用
|
||
第2层:Reactive Compact(fork agent 摘要整个对话)
|
||
↓ 失败
|
||
第3层:Max Output Tokens Escalate(临时提升到 64k token cap)
|
||
↓ 再次命中
|
||
第4层:Multi-turn Recovery(注入 meta message,继续对话)
|
||
↓ 全部失败
|
||
最终:Surface the error(暴露给用户)
|
||
```
|
||
|
||
每层都有 `hasAttempted` 守卫防止无限重试,autocompact 有 circuit breaker(连续 3 次失败后停止)。
|
||
|
||
### 优化方案
|
||
|
||
```rust
|
||
/// 错误恢复策略枚举
|
||
enum RecoveryStrategy {
|
||
/// 尝试更激进的 micro_compact
|
||
AggressiveMicroCompact,
|
||
/// LLM 摘要整个对话历史
|
||
ReactiveCompact,
|
||
/// 提升 max_tokens 上限
|
||
MaxTokensEscalate,
|
||
/// 注入 metacognitive 消息
|
||
MultiTurnRecovery,
|
||
/// 放弃,暴露错误给用户
|
||
Surface,
|
||
}
|
||
|
||
struct RecoveryState {
|
||
attempted_micro_compact: bool,
|
||
attempted_reactive_compact: bool,
|
||
attempted_max_tokens_escalation: bool,
|
||
autocompact_failure_count: u32,
|
||
}
|
||
|
||
const MAX_AUTOCOMPACT_FAILURES: u32 = 3;
|
||
|
||
impl RecoveryState {
|
||
fn next_strategy(&mut self, error: &ModelError) -> RecoveryStrategy {
|
||
match error {
|
||
ModelError::ContextOverflow(_) => {
|
||
if !self.attempted_micro_compact {
|
||
self.attempted_micro_compact = true;
|
||
return RecoveryStrategy::AggressiveMicroCompact;
|
||
}
|
||
if !self.attempted_reactive_compact
|
||
&& self.autocompact_failure_count < MAX_AUTOCOMPACT_FAILURES
|
||
{
|
||
self.attempted_reactive_compact = true;
|
||
return RecoveryStrategy::ReactiveCompact;
|
||
}
|
||
if !self.attempted_max_tokens_escalation {
|
||
self.attempted_max_tokens_escalation = true;
|
||
return RecoveryStrategy::MaxTokensEscalate;
|
||
}
|
||
RecoveryStrategy::MultiTurnRecovery
|
||
}
|
||
_ => RecoveryStrategy::Surface,
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**预期收益**:显著提高长对话的鲁棒性,减少用户遇到的 "context too long" 错误。
|
||
|
||
---
|
||
|
||
## 五、MEDIUM:Time-Based Microcompact
|
||
|
||
### 现状
|
||
我们的 micro_compact 只基于消息数量/大小触发,不考虑时间因素。
|
||
|
||
### Claude Code 做法
|
||
`microcompactMessages()` 首先检查 `evaluateTimeBasedTrigger()`:
|
||
- 计算距上一条 assistant 消息的时间间隔
|
||
- 如果超过配置阈值(如 5 分钟),服务器的 prompt cache 已经过期
|
||
- 此时直接 content-clear 旧的 tool results(保留最近 N 个)
|
||
- 因为 cache 已冷,修改消息内容不会有额外代价
|
||
|
||
### 优化方案
|
||
|
||
```rust
|
||
pub struct TimeBasedMCConfig {
|
||
pub enabled: bool,
|
||
/// 触发阈值(分钟)
|
||
pub gap_threshold_minutes: u64,
|
||
/// 保留最近 N 个工具结果
|
||
pub keep_recent: usize,
|
||
}
|
||
|
||
impl Default for TimeBasedMCConfig {
|
||
fn default() -> Self {
|
||
Self {
|
||
enabled: true,
|
||
gap_threshold_minutes: 5,
|
||
keep_recent: 4,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 检查时间触发是否应激活
|
||
fn evaluate_time_based_trigger(
|
||
messages: &[ChatMessage],
|
||
config: &TimeBasedMCConfig,
|
||
) -> Option<TimeBasedTrigger> {
|
||
let last_assistant = messages.iter()
|
||
.rev()
|
||
.find(|m| m.role == MessageRole::Assistant)?;
|
||
|
||
let elapsed = last_assistant.timestamp.elapsed().unwrap_or_default();
|
||
let gap_minutes = elapsed.as_secs() / 60;
|
||
|
||
if gap_minutes >= config.gap_threshold_minutes {
|
||
Some(TimeBasedTrigger { gap_minutes })
|
||
} else {
|
||
None
|
||
}
|
||
}
|
||
```
|
||
|
||
**预期收益**:在长时间闲置后自动清理过期上下文,防止用户回到对话时遇上 context overflow。
|
||
|
||
---
|
||
|
||
## 六、MEDIUM:增强的 Hook 系统
|
||
|
||
### 现状
|
||
5 个生命周期事件,简单的 `HookAction::Continue/Block` 二元决策。
|
||
|
||
### Claude Code 做法
|
||
27 个 hook 事件,4 种 hook 类型(command/prompt/agent/http),结构化 JSON 协议:
|
||
|
||
```json
|
||
{
|
||
"continue": true,
|
||
"decision": "approve",
|
||
"reason": "...",
|
||
"systemMessage": "...",
|
||
"hookSpecificOutput": {
|
||
"hookEventName": "PreToolUse",
|
||
"permissionDecision": "allow",
|
||
"updatedInput": { ... },
|
||
"additionalContext": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
### 优化建议(按价值排序)
|
||
|
||
1. **PreToolUse 输入修改**:Hook 可以修改工具参数后再执行(如自动修正 bibcode 格式)
|
||
2. **PostToolUse 输出修改**:Hook 可以后处理工具结果(如自动翻译、格式化)
|
||
3. **additionalContext 注入**:Hook 可以向 LLM 注入附加上下文
|
||
4. **SessionStart watchPaths**:启动时注册文件监控路径
|
||
5. **SubagentStart/Stop**:子代理生命周期事件
|
||
|
||
```rust
|
||
/// 增强的 PreToolUse 输出
|
||
pub struct PreToolUseHookOutput {
|
||
pub decision: HookDecision,
|
||
pub updated_input: Option<serde_json::Value>,
|
||
pub additional_context: Option<String>,
|
||
pub system_message: Option<String>,
|
||
}
|
||
|
||
pub enum HookDecision {
|
||
Allow,
|
||
Deny { reason: String },
|
||
Ask { reason: String },
|
||
}
|
||
|
||
/// 增强的 PostToolUse 输出
|
||
pub struct PostToolUseHookOutput {
|
||
pub updated_output: Option<String>,
|
||
pub additional_context: Option<String>,
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 七、MEDIUM:Permission Pipeline(权限管道)
|
||
|
||
### 现状
|
||
工具没有权限系统。所有工具对 Agent 都同样可用。
|
||
|
||
### Claude Code 做法
|
||
多层权限评估管道:
|
||
```
|
||
Step 1: Deny rule 匹配 → deny(不可覆盖)
|
||
Step 2: Ask rule 匹配 → ask(除非 sandbox override)
|
||
Step 3: Tool.checkPermissions → 工具自身逻辑
|
||
Step 4: Safety checks → ask(绕过免疫)
|
||
Step 5: bypassPermissions 模式 → allow
|
||
Step 6: Allow rule 匹配 → allow
|
||
Step 7: 默认 → ask
|
||
```
|
||
|
||
权限规则格式:`ToolName(pattern:*)`,支持多源优先级链。
|
||
|
||
### 优化方案
|
||
|
||
```rust
|
||
/// 权限行为
|
||
pub enum PermissionBehavior {
|
||
Allow,
|
||
Deny,
|
||
Ask,
|
||
}
|
||
|
||
/// 权限规则来源(优先级从高到低)
|
||
pub enum RuleSource {
|
||
Policy, // 企业策略
|
||
UserSettings,
|
||
ProjectSettings,
|
||
LocalSettings,
|
||
CliArg,
|
||
Session,
|
||
}
|
||
|
||
/// 权限规则
|
||
pub struct PermissionRule {
|
||
pub source: RuleSource,
|
||
pub behavior: PermissionBehavior,
|
||
pub tool_pattern: String, // "search_papers" 或 "bash(git *)"
|
||
pub content_pattern: Option<String>,
|
||
}
|
||
|
||
/// 权限检查器
|
||
pub struct PermissionChecker {
|
||
rules: Vec<PermissionRule>,
|
||
}
|
||
|
||
impl PermissionChecker {
|
||
pub fn check(
|
||
&self,
|
||
tool_name: &str,
|
||
input: &serde_json::Value,
|
||
) -> PermissionDecision {
|
||
// 1. 检查 deny 规则(不可覆盖)
|
||
// 2. 检查 ask 规则
|
||
// 3. 工具自身 check_permissions
|
||
// 4. 安全检查
|
||
// 5. bypass 模式
|
||
// 6. allow 规则
|
||
// 7. 默认 ask
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 八、MEDIUM:Progress Streaming(进度流式传输)
|
||
|
||
### 现状
|
||
工具执行期间没有任何进度反馈,直到执行完成才发送结果。
|
||
|
||
### Claude Code 做法
|
||
`StreamingToolExecutor` 支持工具的进度消息立即 yield,即使工具还在执行中。进度消息类型为 `"progress"`,在 UI 中显示为短暂的状态更新。
|
||
|
||
### 优化方案
|
||
|
||
```rust
|
||
/// 为长时间运行的工具增加进度回调
|
||
#[async_trait]
|
||
pub trait AgentTool: Send + Sync {
|
||
// ... 现有方法 ...
|
||
|
||
/// 带进度回调的执行(默认委托给 execute)
|
||
async fn execute_with_progress(
|
||
&self,
|
||
args: serde_json::Value,
|
||
ctx: &ToolContext,
|
||
progress: mpsc::UnboundedSender<ProgressUpdate>,
|
||
) -> ToolOutput {
|
||
let _ = progress; // 默认忽略
|
||
self.execute(args, ctx).await
|
||
}
|
||
}
|
||
|
||
/// 进度更新
|
||
pub struct ProgressUpdate {
|
||
pub tool_call_id: String,
|
||
pub message: String,
|
||
pub percentage: Option<u8>,
|
||
}
|
||
```
|
||
|
||
**适用工具**:download_paper(下载进度)、parse_paper(解析进度)、search_papers(搜索进度)。
|
||
|
||
---
|
||
|
||
## 九、MEDIUM:Token Budget Management
|
||
|
||
### 现状
|
||
没有 token 预算跟踪。Agent 可以无限制地消耗 tokens。
|
||
|
||
### Claude Code 做法
|
||
- `budget.total` — 用户设定的 token 预算上限
|
||
- `budget.spent()` — 当前已消耗的 output tokens
|
||
- `budget.remaining()` — 剩余可用 tokens
|
||
- 硬上限:达到 total 后 `agent()` 调用会抛错
|
||
- 软上限:在接近限制时注入 nudge 消息提醒模型
|
||
|
||
### 优化方案
|
||
|
||
```rust
|
||
pub struct TokenBudget {
|
||
total: Option<u64>,
|
||
spent_output_tokens: u64,
|
||
spent_input_tokens: u64,
|
||
}
|
||
|
||
impl TokenBudget {
|
||
pub fn new(total: Option<u64>) -> Self { ... }
|
||
|
||
pub fn record_usage(&mut self, input: u64, output: u64) {
|
||
self.spent_input_tokens += input;
|
||
self.spent_output_tokens += output;
|
||
}
|
||
|
||
pub fn remaining(&self) -> Option<u64> {
|
||
self.total.map(|t| t.saturating_sub(self.spent_output_tokens))
|
||
}
|
||
|
||
/// 在接近限制时生成提醒消息
|
||
pub fn nudge_message(&self) -> Option<String> {
|
||
if let (Some(total), Some(rem)) = (self.total, self.remaining()) {
|
||
if rem < total / 10 {
|
||
Some(format!(
|
||
"注意:token 预算已使用 {:.0}%,剩余约 {} tokens。请尽快给出最终答案。",
|
||
(self.spent_output_tokens as f64 / total as f64) * 100.0,
|
||
rem
|
||
))
|
||
} else {
|
||
None
|
||
}
|
||
} else {
|
||
None
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 十、LOW-MEDIUM:Interrupt Behavior(中断行为分类)
|
||
|
||
### 现状
|
||
取消信号对所有工具一视同仁。
|
||
|
||
### Claude Code 做法
|
||
每个工具声明 `interruptBehavior()`:
|
||
- `"cancel"` — 用户中断时立即取消(如 Read、Search)
|
||
- `"block"` — 用户中断时继续执行完毕(如 Edit、Write,防止文件损坏)
|
||
|
||
### 优化方案
|
||
|
||
```rust
|
||
pub enum InterruptBehavior {
|
||
/// 用户中断时立即取消(默认,适合只读工具)
|
||
Cancel,
|
||
/// 用户中断时继续执行到完成(适合写入工具)
|
||
Block,
|
||
}
|
||
|
||
#[async_trait]
|
||
pub trait AgentTool: Send + Sync {
|
||
// ... 现有方法 ...
|
||
|
||
fn interrupt_behavior(&self) -> InterruptBehavior {
|
||
InterruptBehavior::Cancel // 默认安全取消
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 十一、LOW:Circuit Breaker 模式
|
||
|
||
### 现状
|
||
compact 失败没有熔断机制,可能无限重试。
|
||
|
||
### Claude Code 做法
|
||
- `MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3`
|
||
- fallback model 调用遇到 `529 Overloaded` 时切换到备选模型
|
||
- 所有恢复策略都有 `hasAttempted` 守卫
|
||
|
||
### 优化方案
|
||
|
||
```rust
|
||
pub struct CircuitBreaker {
|
||
max_failures: u32,
|
||
failure_count: u32,
|
||
state: CircuitState,
|
||
}
|
||
|
||
enum CircuitState {
|
||
Closed, // 正常工作
|
||
Open, // 熔断,拒绝请求
|
||
HalfOpen, // 试探性恢复
|
||
}
|
||
|
||
impl CircuitBreaker {
|
||
pub fn check(&mut self) -> Result<(), CircuitOpenError> {
|
||
match self.state {
|
||
CircuitState::Open => Err(CircuitOpenError),
|
||
CircuitState::HalfOpen | CircuitState::Closed => Ok(()),
|
||
}
|
||
}
|
||
|
||
pub fn record_success(&mut self) {
|
||
self.failure_count = 0;
|
||
self.state = CircuitState::Closed;
|
||
}
|
||
|
||
pub fn record_failure(&mut self) {
|
||
self.failure_count += 1;
|
||
if self.failure_count >= self.max_failures {
|
||
self.state = CircuitState::Open;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 十二、LOW:Context Collapse / Projection System(架构级)
|
||
|
||
### 现状
|
||
压缩直接修改消息数组,原始上下文永久丢失。
|
||
|
||
### Claude Code 做法
|
||
`ContextCollapse` 采用 **commit log + projection** 模式:
|
||
1. 不再直接修改消息
|
||
2. 将旧的上下文段替换为摘要 + metadata
|
||
3. 摘要存储在独立的 collapse store 中
|
||
4. 每次查询循环入口通过 `projectView()` 重放 commit log 重建临时消息视图
|
||
5. Commit 是 staged(先暂存)再 committed(on overflow)
|
||
|
||
### 适用场景
|
||
我们的论文研究场景中,Agent 可能会在同一个 session 中研究多篇论文。Context collapse 可以在切换论文时保留之前的研究摘要而不是完全丢弃。
|
||
|
||
### 优化方案
|
||
|
||
```rust
|
||
/// 上下文段
|
||
pub struct ContextSegment {
|
||
pub id: String,
|
||
pub summary: String,
|
||
pub original_message_count: usize,
|
||
pub original_token_estimate: usize,
|
||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||
}
|
||
|
||
/// Collapse 存储
|
||
pub struct CollapseStore {
|
||
segments: Vec<ContextSegment>,
|
||
commit_log: Vec<CollapseCommit>,
|
||
}
|
||
|
||
impl CollapseStore {
|
||
/// Stage 一个 collapse(还不提交)
|
||
pub fn stage(&mut self, segment: ContextSegment) { ... }
|
||
|
||
/// Commit 所有 staged collapses
|
||
pub fn commit_staged(&mut self) -> usize { ... }
|
||
|
||
/// 重放 commit log,生成当前消息视图
|
||
pub fn project_view(
|
||
&self,
|
||
recent_messages: &[ChatMessage],
|
||
) -> Vec<ChatMessage> { ... }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 实施优先级建议
|
||
|
||
| 优先级 | 优化项 | 预计工作量 | 收益 |
|
||
|--------|--------|-----------|------|
|
||
| P0 | Streaming Tool Executor | 3-5 天 | 延迟大幅降低 |
|
||
| P1 | 工具并发分区 | 1-2 天 | 正确性提升 |
|
||
| P1 | Error Recovery Ladder | 2-3 天 | 鲁棒性大幅提升 |
|
||
| P1 | Sibling Abort | 1 天 | 资源浪费减少 |
|
||
| P2 | Time-Based Microcompact | 1 天 | 长会话体验 |
|
||
| P2 | Token Budget Management | 1-2 天 | 成本控制 |
|
||
| P2 | Progress Streaming | 1-2 天 | UX 提升 |
|
||
| P3 | 增强 Hook 系统 | 2-3 天 | 可扩展性 |
|
||
| P3 | Permission Pipeline | 2-3 天 | 安全性 |
|
||
| P3 | Interrupt Behavior | 0.5 天 | 可靠性 |
|
||
| P4 | Circuit Breaker | 1 天 | 稳定性 |
|
||
| P4 | Context Collapse | 5-7 天 | 长期架构 |
|
||
|
||
---
|
||
|
||
## 架构范式已对齐的部分
|
||
|
||
以下方面我们的实现已经与 Claude Code 的范式高度一致,无需大幅改动:
|
||
|
||
1. ✅ ReAct Loop 结构(Thought → Act → Observe)
|
||
2. ✅ Tool trait + Registry 模式(虽可细化但已完整)
|
||
3. ✅ 三层上下文压缩(micro/auto/aggressive)
|
||
4. ✅ 安全切割点(不切断 tool_call/tool_result 配对)
|
||
5. ✅ Transcript 持久化(压缩前保存)
|
||
6. ✅ 生命周期 Hooks(有基础的 5 事件)
|
||
7. ✅ 重复调用检测(DuplicateDetector)
|
||
8. ✅ Skills 两层加载系统
|
||
9. ✅ 子代理委托(SubAgentRunner)
|
||
10. ✅ 后台任务系统(BgNotificationQueue + mpsc)
|
||
11. ✅ 多 Agent 团队(TeamManager + file inbox)
|
||
12. ✅ SSE 流式事件到前端
|
||
13. ✅ Todo/Task 持久化到 SQLite
|
||
14. ✅ Session 生命周期管理
|