# 上下文管理演进 — 任务技术设计

> 创建: 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 膨胀的老路。

#### 情况 3：namespace 内存爆炸

```
最大对话: 500 轮，平均每轮存 2 个 namespace 条目，每条约 3k bytes
→ 500 × 2 × 3k = 3MB
```

设 `max_bytes = 10MB`（宽松上限），最旧条目自动淘汰。淘汰后如果有模型再次引用：

```
LLM: /read_namespace namespace://read_file/a1b2c3
系统: 条目已淘汰 → 重新执行工具 → 重新 namespace 存储 → 返回内容
```

等价于「cache miss」，对用户透明。

#### 情况 4：DB 持久化需不需要存 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.18（Sonnet）。**

---

## 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 验收后 |
