一. Agent短期记忆

二. 长期记忆污染

1. codex

## Codex 项目信息污染处理机制

codex 项目的记忆系统(`codex-rs/memories/`)有一整套多层防护机制来处理长期记忆中的错误/污染信息。整个系统分为 Phase 1(提取)和 Phase 2(整合)两个阶段,污染处理贯穿始终。

---

### 1. 源污染标记 — `memory_mode = "polluted"`

**这是第一道防线**。当会话中使用了外部上下文源(MCP 工具搜索、Web 搜索等),且配置了 `disable_on_external_context = true`,该线程的 `memory_mode` 会被标记为 `"polluted"`。

关键代码在 `codex-rs/core/src/stream_events_utils.rs:141-151`:

```rust
pub(crate) async fn mark_thread_memory_mode_polluted_if_external_context(
    sess: &Session,
    turn_context: &TurnContext,
    item: &ResponseItem,
) {
    if !turn_context.config.memories.disable_on_external_context
        || !response_item_may_include_external_context(item)
    {
        return;
    }
    state_db::mark_thread_memory_mode_polluted(/* ... */)
}
```

被标记为 `polluted` 的线程会被 **永久排除** 在 Phase 1 作业选取之外(SQL 查询中有 `WHERE threads.memory_mode = 'enabled'` 过滤,见 `codex-rs/state/src/runtime/memories.rs:227`),从而从源头阻止污染信息进入记忆系统。

---

### 2. Phase 1 入口过滤

在 Phase 1 的 rolling 作业声明阶段(`phase1.rs:149-187`),系统会进行多层筛选:

- **来源过滤**:只允许 `INTERACTIVE_SESSION_SOURCES` 的交互式会话
- **时间窗口**:超过 `max_rollout_age_days`(默认 **10 天**)的线程不会被提取
- **空闲期要求**:线程最后一次活动后必须至少空闲 `min_rollout_idle_hours`(默认 **6 小时**),避免对活跃会话过早提取
- **重复声明防护**:通过 DB 租赁机制,已声明/处理中的作业不会被重复提取
- **扫描和声明上限**:控制每次启动的处理量

此外,Phase 1 运行前还会先执行一次 **prune 清理**(`phase1.rs:111-133`):

```rust
pub async fn prune(context: &MemoryStartupContext, config: &Config) {
    // 删除超过 max_unused_days(默认 30 天)未使用的 stage-1 输出行
    db.memories()
        .prune_stage1_outputs_for_retention(max_unused_days, PRUNE_BATCH_SIZE)
        .await
}
```

---

### 3. 内容级过滤

Phase 1 的 `sample` 阶段对 rollouts 内容做了严格过滤(`phase1.rs:403-425`):

- 排除开发人员消息(`role == "developer"` 的直接丢弃)
- 排除 `# AGENTS.md instructions` 标记片段
- 排除 `<skill>` 标记片段
- 排除 `SessionMeta`、`Compacted`、`EventMsg` 等非记忆相关条目
- **密钥脱敏**:通过 `redact_secrets()` 对提取的 `raw_memory`、`rollout_summary`、`rollout_slug` 进行全面脱敏

---

### 4. Phase 2 整合层面的"遗忘"机制

Phase 2 是最关键的污染清理环节。它在整合新旧记忆时有一套精确的"增删改"协议,详述在 `consolidation.md` 这个 LLM 提示词中:

**增量更新的遗忘机制**(`consolidation.md:157-177`):

```
- 通过 git diff 识别变更部分
- 新增/修改 → 吞入队列
- 删除 → 遗忘/清理队列
- 对于删除的 rollout_summaries/*.md 或 extensions/*/resources/*.md:
  在 MEMORY.md 中搜索并删除仅由已删除输入支持的记忆
- 如果 MEMORY.md 块中混合了已删除和仍存在的证据:
  只删除过时引用和过时本地指导,保留共享或仍有支持的内容
- MEMORY.md 清理完毕后,再同步清理 memory_summary.md 中的过时内容
```

**质量终审规则**(`consolidation.md:852-873`):

```
- 删除明显冗余/低信号的 rollout 摘要
- 验证 memory_summary.md 开头是 v1
- 删除不太可能对未来有用的过时或低信号块
- 删除仅指向已删除输入或缺失文件的块/任务
- 运行全局 rollout 引用审计,修复意外重复
- 降级或删除主要是探索性讨论、仅助手推荐或一次性印象的记忆
```

---

### 5. 时间维度的自然衰减

| 参数                       | 默认值     | 作用                           |
| -------------------------- | ---------- | ------------------------------ |
| `max_rollout_age_days`     | **10 天**  | 超过此时间的线程不被提取       |
| `max_unused_days`          | **30 天**  | 超过此时间未使用的记忆被 prune |
| `min_rollout_idle_hours`   | **6 小时** | 线程空闲足够久才提取           |
| extension `RETENTION_DAYS` | **7 天**   | 扩展资源文件过期自动清理       |

这些时间窗口确保错误信息不会永久驻留,会随时间自然过期。

---

### 6. 文件系统的 Git 差异检测

Phase 2 使用 **git 工作区差异**(`phase2.rs:153`)来决定是否需要运行整合 agent。只有当工作区确实有变更时才触发 LLM 整合,避免了无效运行。整合完成后会重置 git baseline,形成完整的变更追踪链。

---

### 7. 速率限制保护

在启动记忆流水线之前,系统会检查 Codex API 速率限制(`guard.rs`)。如果剩余配额低于 `min_rate_limit_remaining_percent`(默认 **25%**),整个记忆流水线会被跳过。这防止了在资源紧张时产生低质量记忆。

---

### 总结

codex 的信息污染处理是一个**纵深防御体系**:

1. **源头阻断**:外部上下文污染线程直接标记排除
2. **入口筛选**:只处理符合条件的交互式会话
3. **内容过滤**:剥离敏感和非记忆相关信息,脱敏密钥
4. **时间衰减**:过期记忆自动清理(10天/30天/7天多级时间窗)
5. **LLM 智能整合**:通过详细的 prompt 指导 LLM 识别矛盾、删除过时内容、保存仍有价值的信息
6. **索引同步清理**:`MEMORY.md` 和 `memory_summary.md` 联动更新,确保索引层不会引用已删除的记忆

三. toolCall失败

1. codex

已经收集了足够的代码证据,让我总结分析。

---

## 项目工具调用失败处理策略分析

该项目(codex-rs,即 Claude Code CLI 的 Rust 实现)对工具调用失败采用了**分层处理、逐级重试**的策略,从底层 HTTP 传输一直覆盖到顶层 Session 循环。

### 1. 错误类型体系

错误类型分为四层(`codex-rs/protocol/src/error.rs`):

```
ToolError                        ← 工具层:给调用方看的
├── Rejected(String)             ← 用户/策略拒绝了执行
└── Codex(CodexErr)              ← 系统层:包装详细错误
    └── details: CodexErrorDetails  ← 语义分类
        ├── Sandbox(SandboxErr)     ← 沙箱层错误
        │   ├── Denied { output, network_policy_decision }
        │   ├── Timeout { output }
        │   ├── Signal(i32)
        │   └── ...
        ├── Stream(String)          ← SSE 流断连
        ├── Timeout / RequestTimeout
        ├── ConnectionFailed / ResponseStreamFailed
        ├── InternalServerError
        ├── UsageLimitReached / QuotaExceeded
        ├── ContextWindowExceeded
        └── ... (共 ~30 种变体)
```

`CodexErr` 还携带一个可选的 `retry_delay: Option<Duration>`,由服务端通过 HTTP 头 `Retry-After` 提供。

### 2. 可重试性判定

`CodexErr::is_retryable()` (`protocol/src/error.rs:359-397`) 明确划分了可重试/不可重试的边界:

**可重试**(通常是瞬态网络/服务错误):
- `Stream` — SSE 流中断
- `Timeout` / `RequestTimeout` — 超时
- `UnexpectedStatus` — 意外的 HTTP 状态码
- `ResponseStreamFailed` / `ConnectionFailed` — 连接失败
- `InternalServerError` / `InternalAgentDied` — 服务端内部错误
- `Io` / `Json` / `TokioJoin` — 本地 I/O 或解析错误

**不可重试**(通常是用户配额/策略/上下文问题):
- `TurnAborted` / `Interrupted` — 用户取消了
- `SessionBudgetExceeded` — 配额耗尽
- `ContextWindowExceeded` — 上下文窗口不够
- `UsageLimitReached` / `QuotaExceeded` / `UsageNotIncluded` — 用量限制
- `Sandbox(..)` — 沙箱拒绝(但这触发独立的"升级重试"路径)
- `InvalidRequest` / `InvalidImageRequest` — 请求本身有问题

### 3. 各层重试机制

#### 3.1 层一:HTTP 传输层 (`codex-client/src/retry.rs`)

最底层的通用 HTTP 重试,可配置:

```rust
RetryOn {
    retry_429: bool,       // 限流
    retry_5xx: bool,       // 服务端错误
    retry_transport: bool, // 网络超时
}
```

退避算法:`base_delay * 2^(attempt-1)` + 10% 随机抖动。

#### 3.2 层二:MCP 客户端握手重试 (`rmcp-client/src/streamable_http_retry.rs`)

MCP 服务初始化时的重试策略:
- **固定延迟序列**:`[250ms, 1000ms]`,最多 3 次尝试
- 重试条件:HTTP 请求失败、传输通道关闭、服务器返回 `500/502/503/504`
- 受总体 `timeout` 约束,超时就不再重试

#### 3.3 层三:Session Turn 流重试 (`core/src/session/turn.rs:1331-1408`)

Session 层的主循环重试,专门处理模型 API 的 SSE 流中断:

```
max_retries = provider.info().stream_max_retries()
// 默认 5 次,上限 100 次 (model-provider-info/src/lib.rs:27-31)
```

重试流程:
1. 检查 `err.is_retryable()` — 不可重试就直接返回错误
2. 如果**重试次数已达上限**,尝试 **WebSocket → HTTPS 降级传输**(fallback transport),重置计数器从头再来
3. 如果**还有重试次数**,使用指数退避:`backoff(attempt)` = `200ms * 2^(attempt-1)` + 10% 抖动(`core/src/util.rs:86-91`)
4. 如果服务端给了 `Retry-After` 头,优先用服务端指定的延迟
5. 向 UI 发送 `Reconnecting... N/M` 通知

#### 3.4 层四:工具编排层重试 (`core/src/tools/orchestrator.rs`)

这是工具执行层的核心重试策略,采用**两阶段"沙箱升级"模型**:

**第一阶段:沙箱内执行**
1. 审批检查 → 沙箱选择 → 执行
2. 成功则直接返回

**第二阶段:沙箱外重试**(仅在沙箱拒绝时触发)
触发条件 ALL 必须满足:
- 错误是 `SandboxErr::Denied`(被沙箱拒绝了)
- `tool.escalate_on_failure()` 返回 `true`(工具允许升级,默认 `true`)
- 策略允许非沙箱执行(`unsandboxed_execution_allowed`)
- 工具允许请求无沙箱审批(`wants_no_sandbox_approval`)

升级流程:
1. **重新请求用户审批**(除非已批准过且符合条件的 bypass)
2. **使用无沙箱模式重新执行**
3. 记录 `otel.sandbox_outcome("escalated")` 或 `"denied"`/`"timed_out"`/`"signal"`

损失情况就是一次重试机会——如果沙箱外也失败,直接返回错误,不再重试。

### 4. 失败信息的用户呈现 (`core/src/tools/events.rs`)

`ToolEmitter::finish()` 将 `ToolError` 转换为用户可见事件,分为三种失败模式:

| 失败类型                                | 事件阶段            | 用户看到的状态                    | 模型收到的                                        |
| --------------------------------------- | ------------------- | --------------------------------- | ------------------------------------------------- |
| `ToolError::Codex(SandboxErr::Timeout)` | `Failure(Output)`   | 命令超时 + 耗时                   | `FunctionCallError::RespondToModel`(带输出内容) |
| `ToolError::Codex(SandboxErr::Denied)`  | `Failure(Output)`   | 沙箱拒绝 + 输出                   | 同上                                              |
| `ToolError::Codex(其他)`                | `Failure(Message)`  | "execution error: ..."            | 同上                                              |
| `ToolError::Rejected(msg)`              | `Failure(Rejected)` | 统一消息(如 "rejected by user") | 同上                                              |

**关键设计:所有失败都使用 `FunctionCallError::RespondToModel`**,这意味着即使工具执行失败,**错误信息也会作为工具调用结果返回给模型**,让模型知道发生了什么并尝试其他方法。这是一种"将失败转化为对话信号"的策略。

### 5. 特殊处理

**Patch 工具的部分成功**:`apply_patch` 被沙箱拒绝时,如果已经提交了部分文件变更(`applied_patch_delta`),事件阶段不是 `Failure` 而是 `Success`——已写入的文件 diff 会被保留,确保 TurnDiff 追踪的一致性。

**审批拒绝消息截断**:拒绝消息上限为 900 token(`REJECTION_MESSAGE_MAX_TOKENS`),防止错误消息撑爆上下文窗口。

**Strict Auto Review**:严格自动审查模式下,重试需走 Guardian 审查(而非用户审批),防止自动化绕过。

### 总结

整体策略是:

```
HTTP 请求失败 → 指数退避重试(429/5xx)
MCP 握手失败  → 固定间隔重试 2 次(250ms, 1000ms)
SSE 流中断    → 指数退避重试 N 次 → 降级传输协议 → 再重试 N 次
沙箱拒绝      → 一次升级重试(去沙箱 + 重新审批)
其他工具错误  → 不重试,将错误返回模型
配额/策略错误  → 不重试,直接报错
```

这种设计体现了**分层防御**思想:传输层处理网络抖动,Session 层处理服务端波动,工具编排层处理权限/沙箱策略冲突,每一层只处理自己能解决的问题,无法处理的向上传递。

四. 工具调用副作用

  • 区分查询类和写操作类。查询类(查公告、财务指标、新闻)可自动执行。写操作类(生成报告、提交审批、发送通知、修改状态)不能让模型直接执行——模型只能生成调用意图,后端再经规则校验、必要时用户确认。给工具做白名单、权限校验、参数校验、审计日志。工具执行还要有幂等 ID,防止重复执行。

1. codex

好,以下是项目对"工具调用副作用是不可靠的操作"这一问题的处理策略:

---

## 1. 去重:`call_id` 是关键

系统不允许同一个 `call_id` 出现两次。模型每次发出的 tool call 都带有一个唯一的 `call_id`,运行时在三个层面校验:

- **`ExecutedToolCallRecorder`**:记录时按 `call_id` 去重(`core/src/tools/executed_tool_calls.rs:113`)
- **`Rollout Trace`**:同一个 `tool_call_id` 重复 start 直接 bail error(`rollout-trace/src/reducer/tool.rs:57-58`)
- **MCP 关联**:同一个 tool_call 重复关联 MCP UUID 也 bail(`rollout-trace/src/reducer/tool.rs:180-181`)

模型可能因为网络重试发出重复调用,但系统以 `call_id` 为幂等键,第二次出现就是错误。

---

## 2. 对账:`ExecutedToolCall` 注入回 prompt

每次构建新一轮请求的 prompt 时,`attach_pending_to_prompt` 把"本轮已经执行过哪些 tool call"附到对应的 `FunctionCallOutput` item 上(`executed_tool_calls.rs:174-243`)。模型在下一次推理时能看到"这些调用已经发生过了",从而避免基于过期状态的重复决策。

关键是 **`retry_cache`**:一旦某个 `(item_type, call_id)` 的记录被消费过一次,就被缓存,后续同 key 直接返回缓存值,不会因为 prompt 构建多次调用而产生不一致。

---

## 3. 恢复:已完成的不丢弃

**Task 取消时的三段式处理**(`tools/parallel.rs:180-220`):

| 状态                          | 行为                                         |
| ----------------------------- | -------------------------------------------- |
| 工具已执行完毕                | 拿回结果,不丢弃                             |
| 工具正在执行 + 支持运行时取消 | 通知进程终止,等待清理                       |
| 工具尚未开始                  | 返回 `AbortedToolOutput`,让模型知道被取消了 |

**Turn 结束时的流失控**(`session/turn.rs:2106-2130`):`drain_in_flight` 遍历所有在飞行中的工具 future,已完成的结果全部持久化到对话历史——turn 中断不丢副作用。

---

## 4. 串行化:副作用操作互斥

`supports_parallel` 标记决定工具是拿读锁还是写锁(`tools/parallel.rs:153-157`)。有副作用的工具(如 `apply_patch`)独占写锁执行,确保不会和任何其他工具产生竞争。只读工具可以共享读锁并发执行。

---

## 5. 暂停点可恢复:`ThreadActiveFlag`

```rust
Active { active_flags: Vec<ThreadActiveFlag> }
// WaitingOnApproval — 等用户批准副作用操作
// WaitingOnUserInput — 等用户回答问题
```

Thread 的状态机暴露了"卡在哪里等用户",外部系统可以精确恢复,不会把"等用户批准"和"执行中"混淆。

---

**一句话总结**:副作用被建模为"以 `call_id` 为幂等键的事件",记录到不可变账本(ExecutedToolCall + Trace),通过回注入 prompt 让模型对账,通过串行化门锁防冲突,通过终态标记防丢失。

五. 怎么评估 Agent 的效果?如何设计 benchmark、case、指标?

六. 多轮执行中 Agent 反复读同一文件/重复调用同一工具,怎么识别重复状态?

七. 单 Agent vs 多 Agent:分别适用什么场景?如何判断需要多 Agent?多 Agent 怎么协作?

1. codex

## 主子 Agent 协作交互

### 核心交互工具(V2)

主 Agent 通过 4 个工具和子 Agent 交互:

| 工具            | 用途                                          |
| --------------- | --------------------------------------------- |
| `spawn_agent`   | 创建子 Agent,发初始任务                      |
| `send_message`  | 发消息,**不打断**子 Agent 当前工作           |
| `followup_task` | 发后续任务,**会触发**子 Agent 的新 turn      |
| `wait_agent`    | 等待 mailbox 有消息(任意子 Agent 完成/来信) |

### 完整交互流程

```
主 Agent                             子 Agent
   |                                     |
   |── spawn_agent("写测试") ──────────→ |  创建线程 + 发初始消息
   |                                     |  开始执行...
   |                                     |
   |── followup_task("改一下") ───────→  |  触发新 turn
   |                                     |  继续执行...
   |                                     |
   |── wait_agent() ─────────────────→   |  (阻塞等待 mailbox)
   |                                     |  完成! → 发 InterAgentCommunication
   |   ←── 收到完成通知 ──────────────   |
   |                                     |
   |   (自动: completion watcher 把结果   |
   |    作为消息推入主 Agent 的 mailbox)   |
```

### 关键实现细节

**1. Spawn — 创建即发任务**(`spawn.rs`)
```rust
// 构建 InterAgentCommunication,trigger_turn=true 让子 Agent 立即开始
communication_from_tool_message(author, child_path, message, source, /*trigger_turn*/ true)
// 调用 AgentControl 创建线程并发送
agent_control.spawn_agent_with_communication(config, communication, ...)
```

**2. send_message vs followup_task**(`message_tool.rs`)
区别仅在于 `MessageDeliveryMode`:
- `QueueOnly` → 消息排队,不触发 turn(子 Agent 空闲时自己取)
- `TriggerTurn` → 立即触发子 Agent 新 turn

```rust
// send_message: 只排队
MessageDeliveryMode::QueueOnly  // trigger_turn=false

// followup_task: 触发 turn
MessageDeliveryMode::TriggerTurn  // trigger_turn=true
```

**3. wait_agent — 事件驱动等待**(`wait.rs`)
不再等特定 Agent,而是等 mailbox 事件:
```rust
enum WaitOutcome {
    MailboxActivity,  // 有消息/完成通知
    Steered,          // 用户输入打断
    TimedOut,         // 超时
}
```

**4. 自动完成通知**(`control.rs:459-547`)
spawn 时会启动一个后台 watcher,订阅子 Agent 的状态变更。子 Agent 到达终态(completed/errored)后,自动构造 `InterAgentCommunication` 发回父 Agent 的 mailbox,**不需要父 Agent 主动轮询**。

**5. 寻址**(`agent_resolver.rs`)
主 Agent 通过 AgentPath 引用子 Agent:
- 相对路径:`task_3` → 解析为 `/root/task1/task_3`
- 绝对路径:`/root/task2/task_3`
- 根 Agent 不能被 followup_task 直发(`message_tool.rs:78` 有校验)

### 一句话总结

主 Agent **spawn 时发初始任务** → 运行时通过 **send_message(不打断)/ followup_task(打断)** 追加任务 → **wait_agent 阻塞等待** mailbox 中有任何子 Agent 的结果 → 子 Agent 完成后**自动通知**父 Agent,无需轮询。

八. Agent 的规划(Planning)能力怎么实现?

1. codex

Now I have the complete picture. Let me write it up cleanly.

---

## 整体流程(端到端)

```
用户操作                TUI 层                              Core 层                            LLM
────────               ──────                               ────────                            ───
```

### 阶段 0:进入 Plan Mode

```rust
// 三种触发方式,最终都修改 active_collaboration_mask
chat.set_collaboration_mask(plan_mask)  // mask.mode = Some(ModeKind::Plan)
```

`effective_collaboration_mode()` 合并 `current_collaboration_mode + active_collaboration_mask`,产出最终送往 Core 的 `CollaborationMode { mode: Plan, model: "...", reasoning_effort: ... }`。

---

### 阶段 1:用户发送消息

用户输入 "帮我设计一个用户认证方案" 后按 Enter。

`input_submission.rs:338-349` 构造 `Op::UserTurn`:
```rust
let op = AppCommand::user_turn(
    items,                                       // UserInput 列表
    effective_mode.model(),
    effective_mode.reasoning_effort(),
    collaboration_mode: Some(CollaborationMode {
        mode: ModeKind::Plan,
        model: "gpt-5.2",                        // <-- Plan 预设的模型
        reasoning_effort: Some("medium"),         // <-- Plan 预设的推理强度
    }),
    ...
);
```

这个 Op 发往 App Server,最终到达 Core 的 Session。

---

### 阶段 2:Core 构建 Turn

Session 接到 turn 请求,创建 `TurnContext`:

```rust
TurnContext {
    mode: ModeKind::Plan,       // <-- 来自 CollaborationMode
    sub_id: "xxx",
    config: ...,
    ...
}
```

关键:`mode` 字段在这里确定,整条链路后续都依据它分流。

---

### 阶段 3:注入 Plan Mode 系统指令

`CollaborationModeState::render_diff()` 被 WorldState 调用:

```rust
// collaboration_mode.rs:28
ModeKind::Plan => messages.plan.as_ref()  // 取 catalog 中的 plan 指令
```

`plan.md` 模板作为 developer message 注入到模型请求中,内容包括:
- 三阶段要求(探索→意图对齐→方案对话)
- 非破坏性约束(允许读/搜索/构建,禁止写/格式化/补丁)
- `<proposed_plan>` 标签格式要求
- `request_user_input` 工具的使用规则

最终发给 LLM 的消息大约是:
```
[developer message: plan.md 完整指令]
[user: "帮我设计一个用户认证方案"]
```

---

### 阶段 4:流式解析 LLM 输出

`turn.rs:2211-2213`:
```rust
let plan_mode = turn_context.mode == ModeKind::Plan;
let mut parsers = AssistantMessageStreamParsers::new(plan_mode);  // plan_mode=true
let mut plan_mode_state = plan_mode.then(|| PlanModeStreamState::new(...));
```

流式循环中收到 LLM token 时,`AssistantMessageStreamParsers` 内部使用 `ProposedPlanParser` 逐行解析:

```
LLM 输出:
  我先看一下项目结构...
  <proposed_plan>
  先读 config.toml 确定...
  ## 用户认证方案
  ...

解析为 ProposedPlanSegment 序列:
  Normal("我先看一下项目结构...\n")
  Normal("先读 config.toml 确定...\n")     ← 探索性文本,不是计划
  ProposedPlanStart
  ProposedPlanDelta("## 用户认证方案\n")
  ProposedPlanDelta("- JWT 无状态认证\n")
  ProposedPlanDelta("- bcrypt 密码哈希\n")
  ProposedPlanEnd
```

`emit_streamed_assistant_text_delta` 判断:
```rust
if let Some(state) = plan_mode_state {
    if !parsed.plan_segments.is_empty() {
        handle_plan_segments(...)  // 走 Plan Mode 路径
        return;
    }
}
// 非 Plan Mode 则走普通路径
```

`handle_plan_segments()` 分流:
- `Normal(text)` → 经 `maybe_emit_pending_agent_message_start` 发送 `AgentMessageContentDelta` 事件
- `ProposedPlanStart/Delta` → 发送 `PlanDelta` 事件,TUI 渲染为特殊计划流式 cell
- `ProposedPlanEnd` → 无操作

期间 LLM 可能还在进行非破坏性探索(读文件、搜索代码、运行测试等),这些工具调用结果正常返回。

---

### 阶段 5:计划完成

流结束或模型发出完整的 `</proposed_plan>` 后:

`streaming.rs:176` — `on_plan_item_completed()`:
```rust
self.transcript.latest_proposed_plan_markdown = Some(plan_text);
// plan_text = "<proposed_plan> 标签内的完整 markdown"
```

turn 结束时 `turn_runtime.rs:232-249` 判断:
- `active_mode == Plan` ✓
- `saw_plan_item_this_turn` ✓ (本 turn 确实产出了计划)
- 没有其他 modal 遮挡
- 没有 rate limit 弹窗

条件满足 → `open_plan_implementation_prompt()` 弹出选择框。

---

### 阶段 6:用户选择执行方式

#### 选项 A:"Yes, implement this plan"

```rust
// plan_implementation.rs:35-41
SubmitUserMessageWithMode {
    text: "Implement the plan.",       // 固定文本
    collaboration_mode: default_mask,  // 切回 Default 模式
}
```

`input_flow.rs:229` `set_collaboration_mask_from_user_action(default_mask)` 切模式后,
`submit_user_message("Implement the plan.")` 发新 turn。

**LLM 收到的上下文** = 整个对话历史(包含刚才 Plan Mode 下产出的 `<proposed_plan>` 全部内容) + "Implement the plan."

LLM 能直接从上下文中"读到"计划,不需要额外传递。

#### 选项 B:"Yes, clear context and implement"

```rust
// plan_implementation.rs:56-63
ClearUiAndSubmitUserMessage {
    text: "A previous agent produced the plan below...\n\n{plan_markdown}"
}
```

`event_dispatch.rs:52-67`:
```rust
self.clear_terminal_ui();        // 清空整个 TUI
self.reset_app_ui_state_after_clear();
self.start_fresh_session_with_summary_hint(
    Some(ThreadStartSource::Clear),
    create_initial_user_message(Some(text), ...),  // 计划文本作为首条消息
    None,
);
```

**新对话的首条消息**:
```
A previous agent produced the plan below to accomplish the user's task.
Implement the plan in a fresh context. Treat the plan as the source of
user intent, re-read files as needed, and carry the work through
implementation and verification.

<proposed_plan>
## 用户认证方案

### 技术选型
- JWT 无状态认证
- bcrypt 密码哈希
...
</proposed_plan>
```

计划文本从 `latest_proposed_plan_markdown` 显式嵌入首条消息,因为上下文已被清空。

---

### 阶段 7:update_plan(独立 TODO 工具,Plan Mode 中禁止)

`update_plan` 是给 Default/Multi-Agent 模式用的 checklist 工具。当用户说 "add a task"、"mark done" 等时,LLM 可能调用它。

`plan.rs:84-88` 互斥检查:
```rust
if turn.mode == ModeKind::Plan {
    return Err("update_plan is a TODO/checklist tool and is not allowed in Plan mode");
}
```

`plan.md` 模板中也明确:
> Separately, `update_plan` is a checklist/progress/TODOs tool; it does not enter or exit Plan Mode. Do not confuse it.

**它与 Plan Mode 是两套独立机制**,名称相似但互斥。

---

### 总结链路

```
/plan → set_collaboration_mask(Plan) → 
用户发消息 → Op::UserTurn{collaboration_mode:Plan} → 
Core构建 TurnContext{mode:Plan} → 
注入 plan.md 指令到 developer message → 
LLM 受约束探索+规划 → 输出含 <proposed_plan> → 
ProposedPlanParser 拆分标签 → TUI 渲染计划cell →
计划完成 → latest_proposed_plan_markdown 存档 →
弹窗: 
  A) 同上下文实现 → 切回Default,发"Implement the plan."
     → LLM 从对话历史中读计划 → 执行
  B) 清空上下文实现 → 新线程,首条消息=计划文本
     → LLM 从当前消息中读计划 → 执行
  C) 继续规划 → 留在 Plan Mode
```

九. 上下文压缩后如何保证关键信息不丢失?

1. codex

### 机制一:结构化摘要提示词

提示词来自 `prompts/templates/compact/prompt.md`,它不是一个笼统的"请总结对话",而是规定了四项必须包含的信息维度:

```
You are performing a CONTEXT CHECKPOINT COMPACTION.
Create a handoff summary for another LLM that will resume the task.

Include:
- Current progress and key decisions made
- Important context, constraints, or user preferences
- What remains to be done (clear next steps)
- Any critical data, examples, or references needed to continue
```

生成摘要后,前面还会拼接一个前缀(`summary_prefix.md`),告知下一个模型"以下内容是上一个 LLM 的思考总结,请据此继续工作"。最终在 `build_compacted_history` 中,摘要被包装成一条 `role: "user"` 的消息放在压缩后的历史末尾——这让模型将其视为用户的"交接指令"来处理,优先级最高。

---

### 机制二:WorldState 差分重注入

压缩后历史的对话部分被大幅精简,环境上下文(系统指令、工具列表、权限规则、环境变量等)更是被完全剥离。如果直接让模型基于压缩历史继续工作,它会丢失当前环境的所有信息。

WorldState 解决这个问题的方式是**完全不在压缩环节处理环境状态**,而是压缩完成后独立恢复。

**快照与差分**:每次向模型注入环境上下文时,`WorldState` 会把各分段的当前状态序列化成 `WorldStateSnapshot`(一个 `BTreeMap<String, Value>`)。压缩后需要恢复时,调用 `render_history_diff`:

- **有精确快照** → 用 RFC 7386 `merge_patch_from` 对比当前值和上次快照,只输出变化的部分。比如工具列表多了两个新工具,就只注入这两个新工具的信息,已有工具不重复发送。
- **快照丢失但历史里还能找到遗留片段** → 标记 `Unknown`,触发完整重渲染,确保不漏信息。
- **两者都没有** → 标记 `Absent`,当作全新环境输出。

**分段独立管理**:环境状态被拆成 12 个独立的 `WorldStateSection`,每个各自维护快照和差分逻辑。比如 `ContextWindowGuidanceState` 的 `render_diff` 只需一行比较——消息没变就返回 `None`(不注入),变了才输出,**避免了"每次都全量重发"的 token 浪费**。

**插入位置精确**:恢复的上下文插入在"最后一个真实用户消息之前"(`insert_initial_context_before_last_real_user_or_summary`),保证模型看到的结构始终是 `[环境上下文] → [用户消息] → [摘要]`,符合训练数据的组织方式。

十. Agent 生成了错误 patch,怎么做回滚、验证和再尝试?

十一. Agent 多轮执行出错怎么定位

  • 思路:全链路 trace(每步输入/工具/输出) + LangSmith/LangFuse 可观测平台;按步看哪一步偏离预期。

十二. 主流agent选型

# 阿里面试:你对主流 Agent 有什么看法?

> 来源:牛客网讨论帖 · https://www.nowcoder.com/discuss/893547954303188992
> 核心:一道"主流 Agent 框架怎么选"的开放题,教你按维度选型而非背框架名。

## 先搞清楚:面试官问这题,到底在考什么

很多同学一听"主流 Agent 怎么选",张口就背框架名字:LangChain、AutoGen、CrewAI、Dify……背完一串面试官"嗯"一声,这题就废了。这道题根本不是考你认识几个框架,它在考三件事:

1. **你有没有真的用 Agent 做过东西**——用过的人会下意识从"我要解决什么问题"倒推选型,没用过的人只会平铺罗列。
2. **你的技术判断力**——面对一堆功能重叠的工具,能不能讲清它们的边界和取舍。
3. **你的工程 sense**——能不能从成本、可控性、可维护性这些真实落地维度思考,而不是只看 demo 炫不炫。

正确打开方式:**先讲选型维度,再用维度去切框架,最后给场景化结论。**

## 把主流框架按层级摆进坐标系

### 一、代码框架派

- **LangChain / LangGraph**:生态最大、组件最全的"瑞士军刀"。LangChain 把模型、工具、记忆、检索标准化;LangGraph 在其上补了"图结构的流程编排",能做有状态、有循环、可回溯的复杂 Agent。优点:社区大、轮子多、出问题能搜到答案。缺点:抽象层多、版本变动快,简单需求容易"杀鸡用牛刀"。适合:需要高度自定义、流程复杂、要长期演进的生产级应用。
- **LlamaIndex**:偏"数据/检索"的 Agent 框架,RAG 是强项。适合核心诉求是"让模型基于我的私有知识库回答"。
- **AutoGen(微软)**:主打多 Agent 对话协作,多个角色像开会一样互相讨论分工。适合研究型、需要多角色博弈/审查的场景。缺点:自由对话不确定性高,生产要花力气兜底。
- **CrewAI**:把多 Agent 协作做得更工程化(明确的 Role/Task/Process),上手比 AutoGen 直观。适合快速搭"分工明确的 Agent 小队"。
- **MetaGPT**:用"软件公司"的隐喻做多 Agent(产品、架构、工程师各司其职),偏特定工作流最佳实践沉淀。

### 二、低代码 / 平台派

- **Dify**:开源 LLM 应用平台,可视化编排 + API + 私有化部署,工程完整度高。适合企业内部快速搭应用、又想自己掌控部署和数据。
- **Coze / 扣子(字节)**:拖拽式搭 Bot,插件生态丰富,发布到各渠道方便。适合个人/运营快速做 Agent,不想碰代码。
- **通义 / 百炼(阿里)**:阿里云的模型与 Agent 应用平台,和阿里云生态、企业服务结合紧。适合已在阿里云体系内、要做企业级集成的团队。

### 三、模型厂商原生派(最薄、最贴模型)

- **OpenAI Agents SDK / Claude Agent SDK**:模型厂商官方出的轻量 Agent 框架,工具调用、循环、上下文管理这些原语做得干净,跟自家模型贴合最好。优点:薄、稳、官方维护。适合单一模型厂商、想要可控且轻量的生产应用。**这两年明显趋势是"框架越来越薄、能力下沉到模型本身"。**

## 第三步:给直接背的递进逻辑

1. **先问要不要写代码?** 不想写/快速验证 → Coze、Dify(要私有化就 Dify);要工程化定制 → 往下走。
2. **再问单 Agent 还是多 Agent?** 单 Agent + 强检索 → LlamaIndex 或 LangChain;单 Agent + 复杂有状态流程 → LangGraph;多 Agent 协作 → CrewAI(工程化)/ AutoGen(对话研究)。
3. **最后问绑不绑定单一模型厂商?** 绑定且求轻量可控 → OpenAI / Claude 官方 Agent SDK;要多模型灵活切换 → LangChain 系。

**一句话收尾:没有最好的框架,只有最匹配「你的场景 + 你的可控性要求 + 你的团队栈」的框架。**

## 可复用的回答模板

> "我会先反问业务诉求,因为 Agent 选型本质是在自主性和可控性之间做权衡。如果是快速验证或非技术同学用,我选低代码平台(Dify、Coze);如果是生产级、流程复杂、要长期维护,我用 LangGraph 这类能做有状态编排的代码框架;如果是多角色协作,CrewAI 或 AutoGen 更合适;如果绑定单一模型厂商、追求轻量稳定,我倾向厂商官方 Agent SDK。我自己做过 ×× 项目,当时因为 ×× 原因选了 ××,踩过 ×× 坑,后来怎么解决的——"

主流框架每隔几个月换一茬,今天背的名字明年可能过气,但"按维度选型"的思维方式不会过时。

> `#AI求职记录#`