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

24 KiB
Raw Blame History

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

创建: 2026-07-20 | 状态: 设计阶段 | 关联文档: 上下文管理演进与发散思考-2026-07-20.md


目录


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 内容
    → 模型通过引用路径按需读取(或系统自动补全)

数据结构

// 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

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:持久化与生命周期

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 路径:

// 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 新增字段

// 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」,对用户透明。

情况 4:DB 持久化需不需要存 namespace

不需要。namespace 是运行时缓存优化,不是持久化真相源。全量消息已在 save_conversation 写入 ai_messages 表(uncompressed),压缩后的摘要也在 system 消息中。namespace 淘汰后,可通过 DB 重新构建,但需要保证:

// 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 只服务于运行时。

// 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"任务进展"一目了然

数据结构

// 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>,  // 节点输出的摘要(关键产出)
}

关键决策点

决策 1DAG 全量注入还是摘要注入

DAG 可能很大(50+ 节点)。全量注入 ≈ 2000+ tokens,不可接受。

方案:只注入「当前活跃路径」。从 DAG 的起始节点到当前节点 + 后续 2 层子节点。其余节点不注入。

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
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:无工作流时的行为

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 完成时自动产生:

// 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 返回类型

// 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

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 处的消费

// 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 解析验证:

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 动态压缩阈值(设计概要)

代码改动精确到行:

// 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 {
// 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 {
// 同上,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);

实施顺序验证(依赖关系)

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