hey 0.1.0

Minimal terminal AI coding agent: kernel loop + MCP/Skills self-evolution
Documentation
# hey — AI Coding Agent 设计(v6)

> 最精简的终端瑞士军刀。一个 Rust 二进制。**能力不预置,靠 MCP + Skills 自我进化。**

## 1. 定位与原则

- **内核只有循环**:agent loop + 3 个 trait(Provider/Tool/Output),其余都是文件
- **自我进化**:内核不内置业务能力。Skills 是文件(agent 能写文件 → 能给自己造技能);MCP 是配置(agent 能改配置 → 能接新服务)。写一个 SKILL.md,下次会话自动生效
- **零框架感**:所有"功能"都是目录约定 / 配置文件 / CLI 参数,无注册表无插件系统
- **单 crate**(~12 文件),无 TUI(REPL 即可),同步回调输出
- **协议无关内核**:统一 IR(§3)+ 配置声明 `protocol`,支持 OpenAI 兼容 / Anthropic / Google,加协议 = 加适配器文件,内核与 IR 零改动

## 2. 结构

```text
hey/
├── Cargo.toml
└── src/
    ├── main.rs     # CLI + 拼装 + REPL
    ├── agent.rs    # 循环(请求→流式→工具→回填)+ /reload,主循环在本文件
    │   ├── prompt.rs    # 系统提示(AGENTS.md 合并 + skills 注入)
    │   ├── context.rs   # token 估算 / 整对裁剪 / thinking 剥离(纯函数)
    │   ├── execution.rs # ToolRunner:预检→并行/串行→保序回填 + 权限判定
    │   └── compress.rs  # compress 工具(无损摘要;resume 重建复用)
    ├── prompt.rs    # @file 展开 + CLI prompt 组装(stdin 管道合并)
    ├── completer.rs # REPL tab 补全(/help /quit /model /...)
    ├── ctrl_c.rs    # Ctrl-C 处理 + termios 保存恢复
    ├── lib.rs       # 库化入口(kernel API)
    ├── llm/         # Provider trait + 统一 IR + 协议适配器
    │   ├── mod.rs       # trait + Delta + LlmError + 工厂(按配置选适配器)
    │   ├── ir.rs        # 协议无关消息模型(Message/ContentBlock/ToolCall)
    │   ├── openai.rs    # OpenAI 兼容:SSE + reasoning_content + tool_calls
    │   ├── responses.rs # OpenAI Responses API(o3/gpt-5系:input 块 + reasoning.summary)
    │   ├── anthropic.rs # Anthropic Messages:content_block 流 + thinking + tool_use
    │   ├── google.rs    # Gemini(可选,v2)
    │   └── retry.rs     # 请求级重试包装(按错误分类退避)
    ├── config.rs   # TOML 配置 + env 展开 + provider 选择
    ├── proxy.rs    # 代理解析(CLI > 配置 > 环境变量)
    ├── tools.rs    # Tool trait + 5 内建 + 注册表 + bash 输出过滤
    ├── mcp.rs      # MCP stdio/HTTP 客户端
    ├── skills.rs   # Skills 发现 + 注入
    ├── sessions.rs # JSONL + resume + 摘要持久化
    ├── trust.rs    # 项目信任门控
    └── ui.rs       # 终端 / JSON / 测试输出
```

## 3. 三个抽象(全部扩展点)

```rust
#[async_trait]
pub trait Provider: Send + Sync {
    fn model(&self) -> &str;
    /// 流式完成:增量经 on_delta 吐出(区分正文/推理),结束返回完整消息
    async fn stream(&self, req: &ChatRequest,
                    on_delta: &mut dyn FnMut(Delta)) -> Result<Completion, LlmError>;
}

/// 流式增量:Thinking = reasoning_content / thinking block,Text = 正文
pub enum Delta { Text(String), Thinking(String) }

/// 错误分类:驱动重试策略(见 §4)
pub enum LlmError {
    Auth,                                   // 401 → 不重试,提示检查 key
    RateLimit { retry_after: Option<Duration> },  // 429 → 退避重试,尊重 Retry-After
    Overloaded,                             // 503 → 退避重试
    QuotaExhausted,                         // 配额耗尽 → 不重试,提示换模型/provider
    Network,                                // 连接/超时 → 退避重试
    BadRequest,                             // 400 → 不重试
    StreamInterrupted,                      // SSE 中断 → 视有无部分响应决定
}

/// 协议无关统一消息模型(ir.rs)——所有适配器共享,内核不感知协议差异
pub enum ContentBlock { Text(String), Thinking(String) }  // thinking 统一化
pub struct ToolCall { pub id: String, pub name: String, pub arguments: String }
pub struct Message {
    pub role: Role,                    // System / User / Assistant / Tool
    pub content: Vec<ContentBlock>,
    pub tool_calls: Vec<ToolCall>,     // Assistant 消息带
    pub tool_call_id: Option<String>,  // Tool 消息带
}

#[async_trait]
pub trait Tool: Send + Sync {
    fn name(&self) -> &str;
    fn schema(&self) -> serde_json::Value;
    async fn run(&self, args: serde_json::Value) -> Result<String, String>;
}

pub trait Output {
    fn text_delta(&mut self, s: &str);
    fn thinking_delta(&mut self, s: &str) {}  // 默认空实现(终端灰字/JSON 单独事件)
    fn tool_call(&mut self, name: &str, args: &str);
    fn tool_result(&mut self, name: &str, ok: bool, out: &str);
    // 注:无权限询问(M12 对齐 pi:工具默认全权限;allow 列表收紧,未匹配直接 blocked)
}
```

## 4. Agent 循环

```text
loop {
    resp = provider.stream(messages, on_delta).await?
    if resp.tool_calls 为空 { break }
    for call in resp.tool_calls {
        if !permit(call) { messages.push("用户拒绝"); continue }
        result = tools.run(call).await
        messages.push(工具结果)
    }
}
```

- 终止:无工具调用 / `max_turns` / 预算耗尽 / Ctrl-C;达到 `max_turns` 时输出"未完成摘要(进度/剩余)",用户说"继续"即可续跑(复用 resume)
- **长任务检查点**:系统提示指导 agent 在多轮任务中维护 `.hey/task.md`(目标/已完成/决策/剩余),关键步骤后更新——压缩、resume、崩溃重启后先读它恢复进度,防"摘要丢失细节→重复劳动"(pi 的 TODO.md 哲学,零新机制)
- 权限:`allow 列表`(pi 方式:默认全权限;配置 allow 即收紧,未匹配 → blocked 回填模型;无交互询问)
- **thinking**:`--effort <off|minimal|low|medium|high|xhigh|max>` 统一强度枚举(对齐 pi / Claude Code)——各协议按原生语义映射:OpenAI `reasoning_effort` 字符串直通(**Off → `none`**,官方关闭语义;"off" 不在 OpenAI 值域会 400);Anthropic 新 API adaptive thinking + `output_config.effort` 直通(对齐 Claude Code 真实做法);Anthropic 老 API(`legacy_thinking=true`)映射 `budget_tokens`;Google 按模型自动双轨——gemini-3.x 用 `thinkingLevel` 字符串直通(xhigh/max 收敛 high;**3 无法关闭 thinking,off 降级 minimal**)、gemini-2.5 用 `thinkingBudget` 数字(唯一一张映射表 1024~32000)。Anthropic `max_tokens` 自动按档位给足(官方要求 budget < max_tokens,否则 400),可用 `[provider.*] max_tokens` 覆盖。配置 `[provider.<name>].effort = "high"`。**显示与强度分离**:`/effort` 动态调强度,`/thinking` toggle 显示(仅终端灰字;JSON 数据流始终输出 thinking 事件;显示不影响回传——thinking 回传是协议必需,想省 token 用 `/effort off`)。推理文本经 `Delta::Thinking` 转发给 Output;DeepSeek R1 系增量 `reasoning_content` 同通道。
- 上下文:系统提示**每轮动态构建**(AGENTS.md + skills 清单 + 造技能指南),不进会话历史——这是 `/reload` 即时生效的前提;超预算按"工具调用+结果整对"裁剪中间消息,仍超则复用 provider 发摘要替换旧消息
- **重试(分层,免费模型友好)**:请求级(llm.rs)按错误分类——`429/503/超时/网络` → 指数退避(2s 起、上限 60s、抖动防惊群、尊重 429 的 Retry-After);`401/400/配额耗尽` → 不重试(提示检查 key 或换模型);Turn 级(agent.rs)仅当整轮无部分响应时重试,有部分响应则失败上报(防重复副作用)
- **`/reload`**:重新扫描 skills、重连 MCP、重读配置——**自我进化的最后一环**(agent 造完技能/改完配置立即生效,无需重启)

## 5. 工具(5 内建,够写文件和跑命令即可)

| 工具 | 说明 |
| --- | --- |
| `bash` | 执行命令(cwd 限项目根、超时、**输出过滤**、截断)——过滤规则见 §13 |
| `read_file` / `write_file` | 行号范围读、原子写 |
| `edit` | 精确文本替换 |
| `grep` | 检索(ignore 规则) |

> 5 个工具能完成"读码 → 改码 → 跑测试"的闭环,其余能力全部来自 MCP/Skills。

## 6. 扩展与自我进化(核心)

### 6.1 Skills — 进化的载体

- **目录约定**:`~/.config/hey/skills/` 与 `.hey/skills/`,目录含 `SKILL.md`(或根 `.md` 文件)
- **frontmatter**:`name` + `description` 必填;系统提示只列 name+description(渐进披露),措辞强制"任务匹配技能描述时必须先 `read` 该 SKILL.md 再执行",命中时模型用 `read` 加载全文
- **造技能指南**:系统提示固定注入一小段(frontmatter 格式 + 存放位置 + 需 `/reload`)——agent 无需猜测即可自行造技能,这是进化闭环的知识前提
- **自我进化路径**:agent 在 `.hey/skills/<name>/SKILL.md` 写入新技能 → `/reload` → 该技能进入未来所有会话
- 校验失败仅告警不阻断;`/skill:name` 可强制加载

### 6.2 MCP — 能力的来源

- 配置 `[mcp.<name>]` 声明 server(stdio 子进程或远程 HTTP 端点),启动时连接,`tools/list`/`tools/call` 汇入注册表(与内建工具同名则 MCP 覆盖)
- 传输方式:`url` 字段存在 → HTTP(reqwest POST JSON-RPC,支持任意远端 MCP);否则 stdio(子进程 stdin/stdout 单通道)
- 自我进化路径:agent 写配置声明新 server → `/reload` → 新工具可用
- 故障处理:server 启动失败仅告警不阻断(该 server 工具不可用);工具调用超时返回错误文本给模型;崩溃不自动重启(v1)
- **schema 控重**:MCP 工具 description 截断(~300 字符)防 schema 膨胀;`--no-mcp` 可快速全关(省每轮请求空间)

### 6.3 自我进化闭环

```text
agent 遇到不会的能力 → write_file 造 SKILL.md 或改配置 → /reload → 立即拥有
```

内核从不预置业务能力(浏览器、搜索、文档处理…都不内置),全部由 agent 在运行中通过写文件获得。这就是"瑞士军刀":刀柄是内核,刀片是它自己磨的。

## 7. 会话

- JSONL 追加(**每条带稳定 id**,消息 + 用量),存 `~/.local/state/hey/sessions/<id>.jsonl`;`--resume <id>` 指定、`-c` 续最近;只存对话消息,系统提示不存(每轮动态构建)
- **resume 复用压缩逻辑**:恢复时同样按"摘要 + 保留窗口"重建上下文,不加载全部原始历史(防长会话 token 爆炸)
- 无分支、无树、无 export/import —— 线性够用

## 8. 配置(单文件)

`--config` > `.hey/config.toml` > `~/.config/hey/config.toml`,`HEY_*` env 覆盖:

```toml
[default]                        # 默认选择:CLI -m/HEY_MODEL > env > 此配置 > 首个 provider
provider = "openai"
model = "gpt-4o"

[provider.openai]
base_url = "https://api.openai.com/v1"
api_key = "${OPENAI_API_KEY}"    # 可省略:省略则无认证请求(本地 Ollama/vLLM/内网网关)
models = ["gpt-4o", "o3-mini"]
effort = "medium"              # per-provider 默认推理强度

[provider.deepseek]
base_url = "https://api.deepseek.com/v1"
api_key = "${DEEPSEEK_API_KEY}"
models = ["deepseek-chat", "deepseek-reasoner"]

[provider.anthropic]                 # 非兼容协议示例
protocol = "anthropic"               # 缺省 "openai"(兼容协议)
base_url = "https://api.anthropic.com/v1"
api_key = "${ANTHROPIC_API_KEY}"
models = ["claude-sonnet-4-5"]
effort = "high"                 # adaptive thinking + output_config.effort(新 API 直通)
# legacy_thinking = true        # 老 API/兼容网关:映射 budget_tokens

[provider.local]                 # 无认证(Ollama/vLLM)
base_url = "http://localhost:11434/v1"
models = ["qwen3:14b"]

[proxy]
url = "http://127.0.0.1:7890"     # 缺省用环境变量

[agent]
max_turns = 20
budget_tokens = 40000            # 按模型上下文窗口调整(8k~200k 不等)

[retry]
enabled = true
max_retries = 5                 # 免费模型更宽容
base_delay_ms = 2000
max_delay_ms = 60000            # 退避上限 60s
jitter = true                   # 抖动防惊群
respect_retry_after = true      # 尊重 429 的 Retry-After

[compaction]
dedup = true                    # 同工具同参数只留最新输出
purge_errors_after = 4          # 出错输入的保留轮数
nudge_threshold = 0.7           # 上下文使用率提示阈值(0-1)
compress_tool = true            # 注册 compress 工具给模型

[permission]
allow = ["bash: cargo *"]

[mcp]
servers = [{ name = "fs", command = "npx", args = ["-y", "@modelcontextprotocol/server-filesystem"] }]
```

**AGENTS.md**:`~/.config/hey/AGENTS.md`(全局)+ 当前目录**向上到 git 仓库根为止**逐级拼接,注入系统提示(AGENTS.md 是 OpenAI agents.md 规范的事实标准,CC/Codex/pi 通用;git 边界截断防止读到无关目录)。
**信任**:极简 —— 交互模式加载项目 `.hey/` 资源前问一次"信任此项目?"(存 `~/.config/hey/trust.json`);非交互默认不加载,`--approve` 显式信任。信任只防"静默加载",不防恶意内容——信任前应审阅项目 skills/配置(与 pi 同)。

### 文件全景(用户视角)

| 位置 | 文件 | 角色 |
| --- | --- | --- |
| `~/.config/hey/` | `config.toml` | 全局配置(`--config` 可另指) |
| | `AGENTS.md` | 全局指令 |
| | `skills/*/SKILL.md` | 全局技能 |
| | `trust.json` | 项目信任决策 |
| `~/.local/state/hey/sessions/` | `<id>.jsonl` | 会话历史(自动写入) |
| 项目 `.hey/` | `config.toml` | 项目配置 |
| | `skills/*/SKILL.md` | 项目技能(含 agent 自造) |
| | `task.md` | 长任务检查点(agent 维护,仅长任务出现) |

全部固定文件 = 2 配置 + 1 信任;其余按技能/会话/任务数线性增长,无其他状态。

## 9. 代理

`resolve_proxy(cli, config, env) -> ProxyConfig`(纯数据)+ `build_client(ProxyConfig)`。
优先级 `--proxy` > 配置 > env;认 `HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY`(含 socks5)与 `NO_PROXY`。

## 10. CLI

```text
hey [@files...] [消息...]       # REPL(-p 打印模式,支持 stdin 管道)
hey --output json "消息"        # JSONL 事件流:供脚本/CI/测试消费(jq 过滤、断言、录制)
hey -t high "..."               # 推理强度 --effort(off|minimal|low|medium|high|xhigh|max)
hey -t off "..."                # 关闭推理(省 token);REPL 里 /effort 实时切换
hey -m deepseek/deepseek-reasoner  # 标准 provider/model 选择(对齐 codex/opencode)
hey --base-url http://localhost:11434/v1 -m qwen3  # 零配置临时 provider(--protocol/--api-key 可选)
hey --max-turns 100            # 无人值守长跑时调大(默认 20)
hey --proxy http://127.0.0.1:7890
hey --no-mcp / --no-skills      # 关闭 MCP / Skills(省系统提示与 schema 空间)
hey -c                          # 续最近会话;--resume <id> 指定会话
hey --approve                   # 信任项目 .hey/ 资源
hey /usage                      # 会话 token 用量与压缩统计
hey /compact                    # 手动压缩上下文(可带指令)
hey /model                      # 列出全部 provider 模型并切换
hey /reload                     # REPL 内:重扫 skills / 重连 MCP / 重读配置
hey /skill:name args            # 强制加载技能
```

## 11. 依赖与里程碑

**依赖**:tokio · reqwest(proxy) · serde/serde_json · clap · toml · tracing · thiserror · async-trait · anyhow · dirs

| 阶段 | 内容 | 验收 |
| --- | --- | --- |
| M1 骨架 | CLI + 多 provider 选择 + Provider(SSE) + 代理 + 单轮 + **测试骨架**(MockServer + FakeProvider + 事件流断言) | 能问答;代理生效;`cargo test` 绿 |
| M2 能干活 | 5 工具(含 bash 输出过滤)+ 权限 + 多轮 + 压缩 + 分层重试 + **Anthropic 适配器**(第二协议验证 IR 设计) | 完成"改码+跑测试";测试输出被过滤 |
| M3 进化 | Skills + MCP + `/reload` + 会话 resume + DCP 三件套(去重/错误清理/compress) | **自我进化闭环跑通**:让 agent 造个 skill,reload 后下轮即可用 |
| M4 打磨 | 信任、`--output json`、文档、错误恢复 | 长时间会话稳定 |
| M5 Google | Gemini 适配器(streamGenerateContent + SSE):thinkingConfig、functionCall 分片 deep-merge、id→name 映射、无认证 | 第三协议全绿:多轮工具循环 wire 级通过 |
| M6 REPL 打磨 | `/model` 运行时切换(重建 provider)、resume 大会话「摘要+窗口」重建、MCP 崩溃懒重启 | 长会话不丢中间对;MCP 崩了自动恢复 |
| M7 会话工具 | `--sessions` 列表 + `/sessions` 清理 + `--output json` 补 usage/error 事件 + `[tools] bash_filter` 外部过滤命令 | 会话可查可清;JSON 事件完整;外部过滤可接 curl 类工具 |
| M8 权限收紧 | 权限白名单每段校验(防 `&&` 链绕过)+ `/reload` 重建 MCP + rustyline 历史 + `/sessions prune` | 白名单无法被命令链绕过;reload 后 MCP 工具可用 |
| M9 thinking 回传 | thinking 协议化回传(DeepSeek 400 修复:reasoning_content / thinking block 按协议回传)+ REPL `@file` 展开 + finish_reason 校验 | 带 thinking 的模型不再 400;单测覆盖 |
| M9b thinking 裁剪 | `prune_old_thinking` 请求层只留最近一轮 thinking(持久化保留全量审计) | 省输入 token;推理信息不继承 |
| M10 真实面验证 | 真实 MCP filesystem server(npx)全链路 + 真实代理验证 + compress 真实摘要 + REPL 窗口化 + **MCP 工具名 sanitize**(点/空格→`_`,OpenAI 工具名限 `[a-zA-Z0-9_-]`) | 真实 wire 抓出并修复工具名 400 bug;代理/摘要/窗口化实测通过 |
| M10b MCP 截断 | MCP 工具输出截断(8000 字符 + 提示)+ 会话写失败告警 | 大文件不爆上下文;写失败可见 |
| M11 shell 选择器 | `resolve_shell`:显式 `[tools] shell` > 平台默认(非 Windows 恒 `sh`;Windows 有 `sh` 用 `sh` 否则 PowerShell);`bash_filter` 跟随 shell | 跨平台命令执行一致;单测覆盖 Windows 分支 |
| M12 并行 + pi 权限 | 工具并行执行(`[agent] parallel_tools`,默认 true,输出/消息严格保序)+ **pi 方式权限**(删交互确认,默认全权限,allow 列表收紧,未匹配 blocked) | 3×150ms 并行 <380ms;事件保序;权限对齐 pi 安全立场 |
| M13 远程 MCP | `McpConfig.url` 字段 + HTTP 传输(reqwest 直连远端端点)+ SSE 响应解析 + 503 自动重试 | 真实 Context7(HTTPS/SSE)resolve-library-id → query-docs 全链路通过 |

## 11.5 工具生态设计原则(实测教训,2026-08)

给 agent 设计工具的**第一原则:工具必须让模型会用**。"模型认不认得出"不是模型能力的责任,
是工具设计者的责任——模型不会用 = 设计失败(Claude Code 的模型认得出工具,是因为宿主在训练数据
里教过;第三方 agent 没有这个特权,只能提供模型已会的工具形态)。

实测:35B 对发明的新工具名(`git_status` 等)连续 4 次 fallback 到 bash git,而不是调用工具。
结论:自定义工具 = 模型不会用 = 无效表面。三条落地铁律:

1. **工具名贴主流先验**:`read_file`/`write_file`/`bash`/`edit`/`grep` 是跨模型共识名。
   主流 agent(Claude Code / pi / Codex / Gemini CLI)都不发明 git 专用工具——git 全靠 bash。
2. **能力织入模型已有路径,不另起炉灶**:宿主想要的能力(完整输出 / 权限粒度 / 审计)通过
   bash 等模型已会的工具实现——git diff/status/log/show 输出永不过滤(`is_git_full_output`),
   权限用 `allow = ["bash: git"]` 按命令段精细管控(`is_allowed` 的 `&&/;/|` 拆分逐段匹配)。
   工具体系曾加过 3 个 git 专用工具(feat cc90590)后发现全冗余:bash 路径等价且模型更熟,
   回退为 5 内置(fix→本修订)。
3. **工具描述即契约**:描述里写明工具语义边界(如"git diff 输出不被过滤"),让模型从
   描述获得确定性,而不是赌它认识名字。

## 12. 明确不做

TUI、消息队列、分支/树、templates、工具过滤 CLI、主题、插件系统、沙箱、sub-agents、plan mode、to-dos。
协议支持策略:v1 内置 OpenAI 兼容;Anthropic M2 加入、Google M5——协议 = 配置声明的适配器,加协议不改内核与 IR。
缺什么 → 让 agent 造个 skill 或接个 MCP server,这就是它的工作。
无人值守长跑用 `--max-turns` + task.md 检查点(默认全权限;要收紧用 allow 列表);MCP 崩溃懒重启(首次传输失败时重建进程重试一次,仍失败提示 `/reload`)。

**实现期细节(编码时定,不展开设计)**:MCP 工具名冲突后加载覆盖;Ctrl-C 取消当前 turn(连按退出);`@文件` 超限截断;localhost 默认排除代理;env 引用解析失败按无认证处理;MCP 工具输出截断(8000 字符,防爆上下文);MCP 注册名 sanitize(点/空格→`_`,OpenAI 工具名限 `[a-zA-Z0-9_-]`,实测带点号 400);REPL 会话历史用尾部窗口读取(`read_msgs_tail`,500 条 + 工具配对对齐,防大会话 O(n) 反序列化);**工具并行执行**(`[agent] parallel_tools`,默认 true):同 turn 多 tool_call 用 `join_all` 并行(输出/消息严格按模型调用序保序),`compress` 单独串行(改上下文),权限判定纯函数化(pi 方式:无交互确认,默认全权限,allow 列表收紧,未匹配 blocked——对齐 pi 的 security 立场);tool_call 事件在**执行前**按序输出(提示「开始执行」),tool_result 在执行完成后按原序输出——M12 曾后置到执行完,M13b 后恢复执行前反馈,事件顺序为 tool_call(全部) → tool_result(逐个)。

## 13. 省 token 三件套(RTK + DCP 类)

**bash 输出过滤(RTK 类)**:bash 工具输出经启发式过滤——优先 stderr、提取错误/警告/结果行(`error`/`FAILED`/`passed`/`tests:` 等模式)、去重复行、行数上限;`--raw` 绕过(对应 rtk 的 proxy 绕过)。纯确定性逻辑,~100 行,收益直接(bash 是最大 token 消耗工具)。**内置实现,不依赖外部 rtk**(单二进制零依赖,不能假设用户环境有 rtk);可选 `[tools].bash_filter` 配置外部过滤命令(默认 `builtin`),有 rtk 类工具可接上。

**shell 选择(全平台)**:`[tools].shell` 显式配置 > 平台默认(非 Windows 恒 `sh`;Windows 上 PATH 有 `sh`(Git Bash/WSL)用 `sh`,否则回退 PowerShell `-NoProfile -Command`)。对齐行业主流(Claude Code/Codex/Goose):Windows 默认 PowerShell、可配置。`bash_filter` 外部命令同样跟随所选 shell。

**DCP 类(动态上下文修剪)**,全部确定性逻辑、零外部依赖:

1. **去重**:同工具+同参数重复调用只留最新输出,旧的重置为 `[pruned: duplicate call]`(`[compaction].dedup`)
2. **错误清理**:出错调用的输入几轮后剥离,保留错误消息供模型恢复(`[compaction].purge_errors_after`)
3. **`compress` 工具**:注册给模型——指定消息范围(依赖稳定 id),输出无损技术摘要(保留路径/行号/决策/错误),摘要替换原始消息(`[compaction].compress_tool`)
4. **主动提示**:上下文使用率超 `[compaction].nudge_threshold`(默认 70%)时系统提示注入"考虑压缩已完结工作流"
5. **保护**:最近 3 轮免疫一切修剪;`write`/`edit`/`compress` 永不修剪
6. `/usage` 查看用量与压缩统计;`/compact [指令]` 手动压缩

实现成本:bash 过滤 ~100 行;DCP 全量 ~300-400 行(唯一推理调用是压缩摘要本身,复用 §4 摘要机制)。

## 14. 测试策略

**分层**:

1. **单元测试**(模块内):纯函数全覆盖——config 解析与选择优先级、proxy 解析(env/cli/配置/NO_PROXY/socks5)、bash 过滤规则、edit 匹配、预算计算、裁剪"整对"逻辑、SSE 行解析、重试退避计算(含 Retry-After/抖动)、skills frontmatter 校验、JSONL 读写与 id 稳定
2. **集成测试**(tests/):
   - **MockServer**:自写 tiny tokio HTTP server(~50 行)模拟各协议 wire——OpenAI 兼容(正常 SSE 流、429+Retry-After、503、流中断、无认证校验、代理转发校验)+ Anthropic(content_block 事件流、thinking、tool_use);每个适配器独立用例
   - **FakeProvider**(内存实现,预设流/错误序列):驱动 agent 循环,配 **TestOutput**(收集事件)断言事件序列——多轮工具循环、权限拒绝/放行、压缩触发(整对裁剪/摘要替换)、重试(429→成功、连败→放弃)、resume 重建
   - **FakeMCP**(假 stdio server,预设 tools/list + tools/call):测 MCP 客户端与故障路径(启动失败告警);**HTTP 传输**:本地起 tokio HTTP server 模拟远端 MCP 端点,验证握手/工具列表/调用
3. **端到端冒烟**(`#[ignore]`,需真实 API key):单轮问答、跨 provider 切换、真实代理

**关键场景**:多轮工具循环、权限、压缩、重试、resume、进化闭环(造 skill→reload→可用)、MCP 故障。

**原则**:配置/代理/过滤/预算/退避等逻辑全部纯函数化(无副作用、无 IO),测试不碰真实网络;`cargo test` 全绿是每个里程碑的验收项。