Files
DevFlow/docs/02-架构设计/专项设计/上下文管理演进-任务技术设计-2026-07-20.md
T
lxy b1d7deece1 新增: 上下文管理演进设计文档(发散思考 + 任务技术设计)
发散思考文档涵盖:
- ContextManager 三维评估(效率/合理性/成本)
- 业界方案对标(Claude Code/OpenAI SDK/Mem0/LangGraph 等)
- 融合设计: 结构化分层上下文引擎(L1常驻/L2历史/L3摘要)
- 9 种方法交叉论证(决策矩阵/ROI/帕累托/Kano/风险矩阵等)
- 7 个发散方向与冲突分析,推荐派系 A 路线

任务技术设计文档涵盖:
- T2 工具命名空间: 大工具结果不进主队列,NamespaceStore 详细设计
- T4 工作流 DAG 注入: 活跃路径裁剪,结构化 DAG 进 system prompt
- T3 L3 结构化摘要: JSON+NL 双格式压缩摘要
- T1 动态压缩阈值: 精确到行的 3 处改动
- 4 种边界情况推演,AI coding 多 agent 并行策略
2026-07-20 00:53:53 +08:00

733 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 上下文管理演进 — 任务技术设计
> 创建: 2026-07-20 | 状态: 设计阶段 | 关联文档: `上下文管理演进与发散思考-2026-07-20.md`
---
## 目录
- [T2 工具命名空间(详细设计)](#t2-工具命名空间详细设计)
- [T4 工作流 DAG 注入(详细设计)](#t4-工作流-dag-注入详细设计)
- [T3 L3 结构化摘要(设计概要)](#t3-l3-结构化摘要设计概要)
---
## T2 工具命名空间(详细设计)
### 现有流程 vs 新流程
```
现有流程:
AiToolRegistry.execute(name, args)
→ 工具执行 → 全量结果 String
→ push(ChatMessage::tool_result(全量内容)) ← 不管大小,全塞进消息队列
→ build_for_request 时,大 tool_result 撑爆预算
→ 压缩/裁剪 → 丢失细节
新流程:
AiToolRegistry.execute(name, args)
→ 工具执行 → 全量结果 String
→ if should_use_namespace(全量内容)
→ store_to_namespace(key, 全量内容) ← 存入独立存储
→ push(ChatMessage::tool_result(引用路径)) ← 主队列只插引用
else
→ push(ChatMessage::tool_result(全量内容)) ← 小结果不动
→ build_for_request 时,引用只占 ~20 tokens
→ 压缩不影响 namespace 内容
→ 模型通过引用路径按需读取(或系统自动补全)
```
### 数据结构
```rust
// crates/df-ai/src/namespace_store.rs
/// namespace 引用路径格式: "namespace://tool_name/args_hash"
/// 例: "namespace://read_file/a1b2c3d4"
pub const NAMESPACE_REF_PREFIX: &str = "namespace://";
/// 落入 namespace 的字节阈值(> 2048 bytes
pub const NAMESPACE_BYTE_THRESHOLD: usize = 2048;
/// 落入 namespace 的行数阈值(> 50 行)
pub const NAMESPACE_LINE_THRESHOLD: usize = 50;
/// namespace 存储
///
/// 内存中为 HashMap,提供常规 CRUD
/// 外部可注入持久化实现(接 SQLite / 内存 fallback)。
/// 设计为独立于 ContextManager 的结构——不被压缩/裁剪影响。
pub struct NamespaceStore {
/// key = 引用路径 hash, value = 原始工具结果
entries: HashMap<String, NamespaceEntry>,
/// 总字节数上限(防内存爆炸,超限淘汰最旧条目)
max_bytes: usize,
current_bytes: usize,
}
pub struct NamespaceEntry {
pub key: String, // 完整引用路径
pub tool_name: String,
pub args: serde_json::Value,
pub content: String, // 原始工具执行结果
pub content_summary: String, // extract_key_info 后的摘要(供 LLM 预览)
pub created_at: Instant,
pub access_count: u64,
}
```
### 关键决策点
#### 决策 1:何时进 namespace
```rust
pub fn should_use_namespace(content: &str, tool_name: &str) -> bool {
// 1. 明确标记为"大结果"的工具(read_file / list_directory 几乎总是大)
if is_always_large_tool(tool_name) {
return true;
}
// 2. 按大小阈值判定
content.len() > NAMESPACE_BYTE_THRESHOLD
|| content.lines().count() > NAMESPACE_LINE_THRESHOLD
}
fn is_always_large_tool(tool_name: &str) -> bool {
matches!(tool_name, "read_file" | "list_directory" | "grep" | "diff_files")
}
```
#### 决策 2:模型看到什么
LLM 在 tool_result 消息中看到的不是全量内容,而是:
```
当前(小结果原样,大结果原样):
[tool_result] 342 行文件内容...(占 2000 tokens
新流程(引用路径 + 摘要预览):
[tool_result] <namespace://read_file/a1b2c3>
[tool_result]
文件: config.rs
行数: 342
关键节点: timeout:15 (行15), pool_size:10 (行42)
```
**LLM 的理解能力验证**:如果模型需要读取完整内容,system prompt 需说明:
```
[上下文说明]
当工具结果以 <namespace://path> 格式返回时,表示完整内容已存储。
如果你需要查看完整内容,回复 /read_namespace path
系统将自动拉取完整内容替换当前引用。
```
或自动更激进——当 LLM 的回复引用某 namespace 路径时,系统在下一轮自动拉取:
```
LLM: "config.rs 第 15 行的 timeout 需要改"
系统检测: /read_namespace 触发 → 从 namespace 拉取 config.rs 完整内容
→ 下轮 tool_result 消息中替换为完整内容
→ LLM 看到原文,精确引用行号
```
#### 决策 3:持久化与生命周期
```rust
impl NamespaceStore {
/// 存入 namespace
pub fn store(&mut self, key: &str, tool_name: &str, args: &Value, content: &str) -> String {
let summary = extract_key_info(content); // 复用现有 extract_key_info
let entry = NamespaceEntry {
key: key.to_string(),
tool_name: tool_name.to_string(),
args: args.clone(),
content: content.to_string(),
content_summary: summary,
created_at: Instant::now(),
access_count: 0,
};
self.current_bytes += content.len();
// 超限淘汰:从最旧开始删
while self.current_bytes > self.max_bytes {
// ... LRU 淘汰
}
format!("{}{}/{}", NAMESPACE_REF_PREFIX, tool_name, key)
}
/// 读取完整内容
pub fn read(&mut self, full_path: &str) -> Option<&str> {
let key = self.parse_key(full_path)?;
let entry = self.entries.get_mut(&key)?;
entry.access_count += 1;
Some(entry.content.as_str())
}
/// 读取摘要(用于自动补全判断)
pub fn read_summary(&self, full_path: &str) -> Option<&str> {
let key = self.parse_key(full_path)?;
self.entries.get(&key).map(|e| e.content_summary.as_str())
}
}
```
### 接线点:process_tool_calls
改动点位于 `audit/mod.rs::process_tool_calls`,工具执行完成后的 push 路径:
```rust
// current (audit/mod.rs ~380行附近)
pub(crate) async fn process_tool_calls(..., conv_id: &str) -> usize {
// ... 现有审批/执行逻辑 ...
for (index, draft) in tc_list {
// ... 执行工具 ...
let result = tools_arc.execute(&draft.name, &draft.args).await;
// ★ 新增:namespace 判断
let content = if should_use_namespace(&result, &draft.name) {
let session = session_arc.lock().await;
let ns = &mut session.namespace_store; // AiSession 新增字段
let ref_path = ns.store(&draft.name, &draft.args, &result);
ref_path // ← 主队列只推引用路径
} else {
result // ← 小结果原样推
};
// 后续 push 不变
let msg = ChatMessage::tool_result(draft.tool_call_id.clone(), &content);
session.conv(conv_id).messages.push(msg);
}
}
```
### AiSession 新增字段
```rust
// src-tauri/src/commands/ai/mod.rs 或 state.rs
pub struct AiSession {
pub conversations: HashMap<String, AiConversation>,
pub current_conv: Option<String>,
pub generating: HashSet<String>,
// ★ 新增
pub namespace_store: NamespaceStore,
}
```
### 边界情况推演
#### 情况 1:模型不理解为 namespace 引用
```
LLM 收到 [tool_result] <namespace://read_file/a1b2c3>
LLM 回复: "读了 config.rs,但我不知道内容是什么"
→ 用户体验差
```
**对策**:引用路径不是结束。在 `process_tool_calls` push 引用时,额外 push 一条 assistant 消息或修改 tool_result 内容格式:
```
[tool_result] 文件 config.rs (342 行)
| 关键行: timeout:15 (行15), pool_size:10 (行42)
| 完整内容: <namespace://read_file/a1b2c3>
```
即:`extract_key_info(已有)+ 引用路径` 混合格式。LLM 可以靠摘要感知文件内容,只有需要精确行号时才触发 `/read_namespace`
#### 情况 2:同一文件在 2 轮内被多次引用
```
第 3 轮: read_file("config.rs") → namespace 存 342 行 → 引用
第 5 轮: 模型需要第 15 行 → /read_namespace → 系统拉取
```
第 5 轮拉取后,第 6 轮应该做什么?
**方案 A(推荐)**:拉取后仅在**当轮** `tool_result` 替换为完整内容。下一轮重新压缩时,如果内容大再次进入 namespace。
```
第 5 轮: [tool_result] config.rs 342 行全文(/read_namespace 触发的)
第 6 轮: 压缩 → config.rs 内容被压缩掉 → 正常
第 7 轮: 模型再次需要 → 再次 /read_namespace → namespace 还在
```
**方案 B**:拉取后一直保留在消息队列中。
→ 不推荐,回到了 tool_result 膨胀的老路。
#### 情况 3namespace 内存爆炸
```
最大对话: 500 轮,平均每轮存 2 个 namespace 条目,每条约 3k bytes
→ 500 × 2 × 3k = 3MB
```
`max_bytes = 10MB`(宽松上限),最旧条目自动淘汰。淘汰后如果有模型再次引用:
```
LLM: /read_namespace namespace://read_file/a1b2c3
系统: 条目已淘汰 → 重新执行工具 → 重新 namespace 存储 → 返回内容
```
等价于「cache miss」,对用户透明。
#### 情况 4DB 持久化需不需要存 namespace
**不需要**。namespace 是运行时缓存优化,不是持久化真相源。全量消息已在 `save_conversation` 写入 `ai_messages` 表(uncompressed),压缩后的摘要也在 system 消息中。namespace 淘汰后,可通过 DB 重新构建,但需要保证:
```rust
// save_conversation 中
if msg.content.starts_with(NAMESPACE_REF_PREFIX) {
// 写 DB 前将引用替换回原始内容(从 namespace 中取)
// 替代方案:DB 也存引用路径,恢复时从 namespace 重建
}
```
**建议**:DB 存引用路径。恢复时 namespace 可能已淘汰,此时:
1. 重新执行工具(不可行——工具可能有副作用)
2. DB 保留原文(`save_conversation` 时展开引用)
**结论**DB 存原文。save 时 namespace 条目肯定存在(刚执行完),展开引用写入 DB。恢复时直接读 DB 原文,namespace 只服务于运行时。
```rust
// save_conversation 中展开引用
let content = if msg.content.starts_with(NAMESPACE_REF_PREFIX) {
namespace_store.read(&msg.content)
.unwrap_or(&msg.content) // 兜底:用引用路径自身(可能性低)
} else {
&msg.content
};
record.content = content.to_string();
```
### 推演:T2 在 T-heavy 场景中的行为
```
第 3 轮: read_file("config.rs") → 342 行
应进 namespace ✓
主队列: [tool_result] 文件 config.rs (342行), 行15:timeout, 行42:pool
主队列 token: ~80(→原来 2000
节省: 1920 tokens
第 4-7 轮: 另有 3 次大工具结果 → 每次省 ~1500-2000 tokens
第 8 轮: 压缩触发
当前: 主队列总 history_tokens ≈ 5000(含 4 条引用)
→ 未达压缩阈值(原应为 20000+)
压缩不触发 ✓ 用户无感知
第 15 轮: 用户回来 "config.rs 的 timeout 在第几行?"
LLM 在压缩摘要中仍能找到"行15:timeout" ← 摘要来自 extract_key_info
→ 精确回答 ✓ 不需要 /read_namespace
第 16 轮: "删掉那行,改成 30"
LLM 需要完整文件结构来做 diff
→ /read_namespace namespace://read_file/xxx
→ 系统拉取完整 342 行
→ 第 16 轮 tool_result 出现完整内容
→ 第 17 轮压缩 → 完整内容又被 namespace 吸收 → 回到摘要+引用
```
**总节省**:15 轮对话中,原本平均每轮 4000 tokens 的大 tool_result 占用 → 现在每轮 ~100 tokens 引用 + 摘要。**累积节省 ~60k tokens,约 $0.18Sonnet)。**
---
## T4 工作流 DAG 注入(详细设计)
### 现有流程 vs 新流程
```
现有:
run_agentic_loop system_prompt 拼接:
[系统指令] + [工具定义] + [pinned_goals] + [知识注入]
→ build_for_request
→ LLM 只能从消息历史中推断"当前在做什么"
新流程:
run_agentic_loop system_prompt 拼接前:
if conv.workflow_id != None:
dag = df_workflow::Dag::load(conv.workflow_id)
dag_block = render_dag_to_system_block(dag)
system_prompt = dag_block + system_prompt_原有内容
→ build_for_request(system_prompt + ...)
→ LLM 看到结构化 DAG"任务进展"一目了然
```
### 数据结构
```rust
// src-tauri/src/commands/ai/workflow_context.rs
/// 工作流上下文块 — 注入 system prompt 的 DAG 摘要
pub struct WorkflowContextBlock {
pub workflow_id: String,
pub workflow_name: String,
pub total_nodes: usize,
pub completed_nodes: usize,
pub current_node: Option<WorkflowNodeSummary>,
pub next_nodes: Vec<WorkflowNodeSummary>,
}
/// DAG 节点摘要 — 不进完整 DAG,只进关键上下文
pub struct WorkflowNodeSummary {
pub node_id: String,
pub node_type: String, // "AINode" | "ScriptNode" | "HumanNode" | ...
pub label: String, // 用户或 LLM 设定的节点名称
pub status: String, // "completed" | "running" | "pending" | "blocked"
pub output_summary: Option<String>, // 节点输出的摘要(关键产出)
}
```
### 关键决策点
#### 决策 1:DAG 全量注入还是摘要注入
```
DAG 可能很大(50+ 节点)。全量注入 ≈ 2000+ tokens,不可接受。
```
**方案**:只注入「当前活跃路径」。从 DAG 的起始节点到当前节点 + 后续 2 层子节点。其余节点不注入。
```mermaid
graph LR
subgraph "DAG 全量(50 节点)"
N1["N1 ✅"] --> N2["N2 ✅"]
N1 --> N3["N3 ✅"]
N2 --> N4["N4 🚧 当前"]
N2 --> N5["N5 pending"]
N3 --> N6["N6 pending"]
N4 --> N7["N7 pending"]
N5 --> N8["N8 pending"]
N6 --> N9["N9 pending"]
N3 --> N10["N10 pending"]
N4 --> N11["N11 pending"]
end
subgraph "注入内容(当前活跃路径,5 节点)"
I1["N1: 读源码 ✅"]
I2["N2: 分析依赖 ✅"]
I3["N4: 改配置 🚧"]
I4["N5: 验证配置 pending"]
I5["N7: 提交变更 pending"]
end
```
```rust
pub fn build_active_path(dag: &Dag, current_node_id: &str) -> WorkflowContextBlock {
let path = dag.path_to_root(current_node_id); // 到根的全路径
let next = dag.children(current_node_id, 2); // 后续 2 层
WorkflowContextBlock {
workflow_id: dag.id.clone(),
workflow_name: dag.name.clone(),
total_nodes: dag.nodes.len(),
completed_nodes: dag.nodes.iter().filter(|n| n.status == "completed").count(),
current_node: summarize_node(dag.get_node(current_node_id)),
next_nodes: next.into_iter().map(|n| summarize_node(n)).collect(),
}
}
```
#### 决策 2:注入位置
注入 system prompt 的最前方(优先级高于工具定义):
```
[system]
[工作流] 审批模块改造 (ID: wf-abc)
✅ 1/3 读 config.rs 源码 — 产出: timeout:15 位于行15
🚧 2/3 改 timeout 配置 — 当前步骤
⬜ 3/3 验证配置生效
──────────────────────────────────────
[工具定义] 30 个工具的 JSON schema...
[目标提示] ...
[system prompt 原有内容]
```
**理由**:工作流状态是会话的最上层语境。模型先看到"我们在做什么",再看到"有什么工具可用"。
#### 决策 3:无工作流时的行为
```rust
if let Some(wf_id) = &conv.workflow_id {
if let Ok(dag) = df_workflow::Dag::load(db, wf_id) {
let block = build_active_path(&dag, &conv.current_node_id);
system_prompt = format!("{}\n{}", block.to_system_text(), system_prompt);
}
// Dag::load 失败 → 静默跳过(workflow 可能被删了)
}
// workflow_id == None → 行为完全不变
```
### 推演:T4 在 T-resume 场景中的行为
```
上午:
用户在工作流 wf-abc 中:
N1: 读源码 ✅
N2: 分析依赖 ✅
N3: 改配置 🚧(当前)
最后操作: 改了一半配置,离开
下午回来:
现有: 消息历史被压缩 → 摘要"用户讨论过配置修改"
→ LLM 答:"你讨论了配置,要继续吗?"
→ 用户需要重新说明
T4: system prompt 注入:
[工作流] 审批模块改造
✅ 1/3 读 config.rs — timeout:15
✅ 2/3 分析 db.rs 依赖 — 连接池:10
🚧 3/3 改 timeout 配置 — 已设为30,未验证
→ LLM: "你上午在改 timeout 配置,已经改了还没验证,
要继续验证还是改其他?"
→ 用户直接继续,不需要重新说明
```
**精度依赖**`WorkflowNodeSummary.output_summary` 是关键。如果工作流节点在完成时已有结构化的产出记录(`read_file → 行号:timeout:15`),LLM 就能精确回溯。这需要 df-workflow 的节点在完成时主动记录产出摘要——目前不一定有。
**缺口**:如果工作流节点跑完但没有产出摘要(`output_summary: None`),LLM 只能看到"节点已完成",不知道完成了什么。`output_summary` 需要在 `AiNode::execute` 完成时自动产生:
```rust
// df-nodes/src/ai_node.rs: 执行完成后
if let Some(workflow_node_id) = current_workflow_node {
let summary = extract_key_info(&result); // 复用
workflow::update_node_output(db, workflow_node_id, summary);
}
```
这个联动在 T4 之前需要确保。
### T4 + T2 组合推演
```
T2 提供了 namespace 精确行号回溯
T4 提供了工作流节点结构化映射
组合:
LLM: "config.rs 的 timeout 在哪一行?"
T4 DAG 节点: "N1: 读 config.rs"
T2 namespace: 关联 namespace://read_file/xxx 到 N1 的产出
→ LLM 从 DAG 节点取到 "timeout:15 位于行15"
→ 不需要调用 /read_namespace
→ 精确行号回答,零额外 token
如果 T2 没有、T4 没有:
LLM: 从压缩摘要猜 → 可能错
如果 T2 有、T4 没有:
LLM: 从 DAG 节点取出 "读 config.rs",但需要/read_namespace 拿行号
→ 多一次 tool round
如果 T2 没有、T4 有:
LLM: DAG 节点只有 "读源码 ✅",没有行号细节
→ 重新 read_file
组合后: 最大精度,最小 token 开销。
```
---
## T3 L3 结构化摘要(设计概要)
### 改 compress_via_llm 返回类型
```rust
// current
pub(crate) async fn compress_via_llm(...) -> Result<String, String>;
// new
pub struct CompressedSummary {
/// JSON 卡片序列化字符串
pub json_card: String,
/// 自然语言摘要(向前兼容)
pub nl_summary: String,
}
pub(crate) async fn compress_via_llm(...) -> Result<CompressedSummary, String>;
```
### 改 compress_prompt
```rust
pub(crate) fn compress_prompt(lang: &str) -> &'static str {
match lang {
"en" => "You are a conversation summarizer. Compress the following conversation \
into a structured summary that preserves the essential context for \
continuing the work. \
\n\
**You MUST output TWO parts separated by a delimiter:**\n\
\n\
Part 1 — JSON (between <<<JSON>>> and <<<END_JSON>>>):\n\
{\n\
\"topics\": [\"topic1\", \"topic2\"],\n\
\"decisions\": [{\"what\": \"...\", \"why\": \"...\"}],\n\
\"unresolved\": [\"question1\"],\n\
\"key_files\": [{\"path\": \"...\", \"change\": \"...\"}],\n\
\"token_saved\": <estimated_tokens>\n\
}\n\
\n\
Part 2 — Natural language summary (between <<<NL>>> and <<<END_NL>>>):\n\
A concise paragraph summarizing the conversation.\n\
\n\
Rules:\n\
- JSON must be valid.\n\
- Keep the natural language summary concise; prefer bullet points.\n\
- Preserve file paths, identifiers, and error messages verbatim in both parts.\n\
- Drop small talk; keep only technically load-bearing facts.\n\
- Do NOT invent facts.\n",
_ => {
// 中文版本同上,翻译为中文
}
}
}
```
### insert_at 处的消费
```rust
// context_lifecycle.rs: 压缩成功后
match compress_outcome {
Ok(Some(summary)) => {
// ★ 新格式:JSON + NL 双格式嵌入 system 消息
let system_text = format!(
"{}<<<STRUCTURED_SUMMARY>>>\n{}\n<<<END_STRUCTURED_SUMMARY>>>\n{}",
SUMMARY_MARKER,
summary.json_card,
summary.nl_summary,
);
conv.messages.insert_at(0, ChatMessage::system(&system_text));
// ...
}
Ok(None) => { /* noop */ }
Err(e) => {
// LLM 压缩失败 → 关键词摘要兜底(同前)
// 关键词摘要是纯文本格式,不需要 JSON 结构
// → insert_at(0, system(keyword_fallback)) ← 与旧行为一致
}
}
```
### 退化检测适配
当前 `clean_summary` 函数只处理纯文本退化。新格式引入后,退化检测需增加 JSON 解析验证:
```rust
fn clean_summary(raw: &str) -> Result<CompressedSummary, String> {
// 提取 JSON 段
let json = extract_between(raw, "<<<JSON>>>", "<<<END_JSON>>>")?;
let nl = extract_between(raw, "<<<NL>>>", "<<<END_NL>>>")?;
// 验证 JSON 合法性
let card: SummaryCard = serde_json::from_str(&json)
.map_err(|e| format!("JSON 解析失败: {}", e))?;
// 退化检测 NL 段
if is_degenerated_repetition(&nl) {
return Err("NL 段退化重复".to_string());
}
Ok(CompressedSummary {
json_card: json,
nl_summary: nl,
})
}
```
### 推演:T3 在检索场景中的行为
```
无 T3(当前):
压缩摘要: "用户讨论了审批配置,把 timeout 改成了 30"
LLM 读取后知道"改过",但具体决策原因不清晰
→ 需要推测或 ask user
有 T3:
system 消息中嵌入:
<<<STRUCTURED_SUMMARY>>>
{"decisions": [{"what": "timeout: 15→30", "why": "用户反馈15min太短"}]}
<<<END_STRUCTURED_SUMMARY>>>
自然语言: 用户讨论审批配置,决定超时改为 30 分钟
LLM 直接从 JSON 读取决策原因:
→ "15→30,原因是用户反馈太短"
→ 精确回答 ✓
→ 不需要 ask user
```
**收益量化**:每次压缩后,JSON 卡片占 ~300 tokens,自然语言占 ~200 tokens。比纯自然语言的 ~400 tokens 多了 ~100 tokens。但 JSON 的结构化让 LLM 的检索精度从「需要推理」变为「可以直接读」,减少了后续追问的轮次。
---
## T1 动态压缩阈值(设计概要)
代码改动精确到行:
```rust
// src-tauri/src/commands/ai/agentic/mod.rs: 调用处(line ~1117
if maybe_auto_compress(
&session_arc,
&conv_id,
&app_handle,
&provider,
&provider_config,
&llm_concurrency,
iteration,
sys_tokens, // ★ 新增参数
).await {
```
```rust
// src-tauri/src/commands/ai/agentic/context_lifecycle.rs: 函数签名(line ~72
pub(super) async fn maybe_auto_compress(
session_arc: &Arc<Mutex<AiSession>>,
conv_id: &str,
app_handle: &AppHandle,
provider: &Box<dyn LlmProvider>,
provider_config: &AiProviderRecord,
llm_concurrency: &LlmConcurrency,
iteration: usize,
sys_tokens: u32, // ★ 新增
) -> bool {
```
```rust
// 同上,line ~88-92 触发条件
let budget = mgr.budget_limit();
let available = budget.saturating_sub(sys_tokens); // ★ 新增
let should = protect_start > 0
&& (available as u64) * 6 / 10 < history_tokens as u64 // ★ 改 budget→available
&& mgr.has_compressible_messages(protect_start);
```
---
## 实施顺序验证(依赖关系)
```mermaid
graph LR
T1["T1: 动态阈值<br>~4 行改动"] --> T2["T2: 命名空间<br>~300 行"]
T1 -.->|可选前置| T3["T3: 结构化摘要<br>~100 行"]
T2 --> T4["T4: 工作流DAG<br>~500 行"]
T3 --> T5["T5: WorkingContext<br>~500 行"]
T2 -.-> T4
T3 -.-> T4
style T1 fill:#c8e6c9
style T2 fill:#c8e6c9
style T3 fill:#fff9c4
style T4 fill:#fff9c4
style T5 fill:#ffccbc
```
**并发策略(AI coding 多 agent 并行)**
| 并行流 | 任务 | 前置 | 可独立启动? |
|--------|------|------|------------|
| 流 A | T1 + T2 | T1 无前置,T2 无前置 | ✅ 立即,T1 与 T2 可并行编码 |
| 流 B | T3 | 无(与 T2 独立) | ✅ 可同时启动 |
| 流 C | T4 | 依赖 T2 的 namespace 接口签名(非实现) | ⚠️ 接口定义后即可启动 |
| 流 D | T5 | 依赖 T3 的 CompressedSummary 结构 | ⚠️ T3 验收后 |