Files
DevFlow/docs/02-架构设计/专项设计/上下文管理演进-执行计划-2026-07-20.md
T
lxy faa61a9ba5 新增: 上下文管理演进 Wave1(契约定义 + T1动态阈值 + T2命名空间)
Agent 3: 共享契约类型定义
- CompressedSummary(T3输出格式)
- WorkflowContextBlock(T4工作流DAG上下文)
- WorkingContext + GoalItem/DecisionItem/StepInfo(T5常驻上下文)
- 脏标记版本追踪 + reset_all 压缩联动

T1: 动态压缩阈值
- maybe_auto_compress 加 sys_tokens 参数,触发条件从 budget*0.6 改为 available*0.6
- 调用处传 sys_tokens(loop顶部已缓存)
- 3 行核心改动

T2: 工具命名空间存储
- crates/df-ai/src/namespace_store.rs: 完整实现 + 单元测试
- process_tool_calls: Low/Med/High 路径 namespace 路由
- save_conversation: namespace 引用展开为原文再落 DB
- AiSession 新增 namespace_store 字段
2026-07-20 01:05:20 +08:00

14 KiB
Raw Blame History

上下文管理演进 — 执行计划

关联: 上下文管理演进-任务技术设计-2026-07-20.md | 状态: 待执行


Wave 划分

3 个 Wave,每个 Wave 内 agent 可并行,Wave 间串行 gate。

graph LR
    subgraph "Wave 1 (4 agent 并行)"
        A1["Agent 1: T1 动态阈值"]
        A2["Agent 2: T2 命名空间存储"]
        A3["Agent 3: 接口契约定义"]
    end
    A3 -.->|"定义 downstream 接口"| A2
    A1 --> G1["Gate 1: cargo check + 单测"]
    A2 --> G1

    G1 --> subgraph "Wave 2 (3 agent 并行)"
        B1["Agent 4: T3 结构化摘要"]
        B2["Agent 5: T4 工作流 DAG"]
    end
    B1 --> G2["Gate 2: cargo check + 集成验证"]
    B2 --> G2

    G2 --> subgraph "Wave 3 (2 agent 串行)"
        C1["Agent 6: T5 WorkingContext"]
    end

Wave 1:基础改造(4 agent 并行)

Agent 1T1 动态压缩阈值

改动清单:精确到行

文件 操作
src-tauri/src/commands/ai/agentic/context_lifecycle.rs 64 签名 maybe_auto_compresssys_tokens: u32 参数
同上 88-92 触发条件 let budgetlet available = budget.saturating_sub(sys_tokens);比较用 available 替代 budget
src-tauri/src/commands/ai/agentic/mod.rs ~1117 调用处 maybe_auto_compress(...)sys_tokens 实参(同作用域 line 964 已定义)

接口契约maybe_auto_compress 签名变更——调用方需传入 sys_tokens。不影响其他模块。

验证cargo check -p devflow;观察日志 available 值在 system prompt 大时显著小于 budget

回退:删参数 + 恢复 budget 变量,2 行。


Agent 2T2 工具命名空间存储

接口契约Wave 1 必须先执行):

// crates/df-ai/src/namespace_store.rs — 新增,其他 agent 依赖此接口
pub struct NamespaceStore { .. }

impl NamespaceStore {
    pub fn new(max_bytes: usize) -> Self;
    pub fn store(&mut self, key: &str, tool_name: &str, args: &Value, content: &str) -> String;
    pub fn read(&mut self, full_path: &str) -> Option<&str>;
    pub fn read_summary(&self, full_path: &str) -> Option<&str>;
}

pub fn should_use_namespace(content: &str, tool_name: &str) -> bool;
pub const NAMESPACE_REF_PREFIX: &str = "namespace://";
pub const NAMESPACE_BYTE_THRESHOLD: usize = 2048;

接线改动

文件 操作
crates/df-ai/src/namespace_store.rs 新增 完整实现 + 单元测试
crates/df-ai/src/lib.rs pub mod namespace_store; + pub use 重导出
crates/df-ai/src/context_helpers.rs 新增 NAMESPACE_REF_PREFIXshould_use_namespaceis_namespace_ref
src-tauri/src/commands/ai/audit/mod.rs process_tool_calls ~380 行 tool 执行完成后:if should_use_namespacens.store(...) → push 引用;else → push 原文
src-tauri/src/commands/ai/mod.rsstate.rs AiSession struct 新增 namespace_store: NamespaceStore 字段
src-tauri/src/commands/ai/conversation.rs save_conversation namespace 引用展开为原文再写 DB

验证

  1. cargo test -p df-ainamespace_store 单元测试(store → read → LRU 淘汰)
  2. 手动:read_file("large.txt") → DB 中 tool_result 内容为原文(引用已展开)
  3. history_tokens 统计应显著下降(大工具结果只计引用 token)

与 Agent 3 的边界namespace_store.rs 实现不依赖 AiSession,纯数据结构的 HashMap 操作,零耦合,可独立开发和测试。接线到 AiSession 和 process_tool_calls 由本 agent 完成。


Agent 3:接口契约定义(跨 Wave 共享)

定义 Wave 2/Wave 3 依赖的数据结构,仅定义不实现,确保下游 agent 可独立编码。

// ── Wave 2 共享契约 ──

/// 压缩摘要双格式(T3 输出,T5 消费)
pub struct CompressedSummary {
    pub json_card: String,   // JSON 卡片(主题/决策/待办)
    pub nl_summary: String,  // 自然语言摘要(向前兼容)
}

// ── Wave 3 共享契约 ──

/// 工作流 DAG 上下文块(T4 输出 → 注入 system prompt
pub struct WorkflowContextBlock {
    pub workflow_id: String,
    pub workflow_name: String,
    pub current_node: Option<String>,
    pub completed: usize,
    pub total: usize,
    pub next_steps: Vec<String>,
}

/// WorkingContext 常驻上下文(T5 实现)
pub struct WorkingContext {
    pub goals: Vec<GoalItem>,
    pub decisions: Vec<DecisionItem>,
    pub unresolved: Vec<String>,
    pub current_step: Option<StepInfo>,
    pub version: WorkingContextVersion,
}

pub enum GoalStatus { Active, Completed, Cancelled }
pub struct GoalItem { pub id: String, pub text: String, pub status: GoalStatus }
pub struct DecisionItem { pub question: String, pub decision: String, pub reason: String }
pub struct StepInfo { pub step_name: String, pub status: StepStatus }
pub enum StepStatus { Pending, InProgress, Done, Blocked }
pub struct WorkingContextVersion {
    pub goals_changed_at: u32,
    pub decisions_changed_at: u32,
    pub unresolved_changed_at: u32,
    pub step_changed_at: u32,
    pub current_turn: u32,
}

验证cargo check — 编译器验证所有 use 路径和类型引用正确,无需运行时测试。

简化:Agent 3 先把上述结构体定义放进 crates/df-ai/src/context_helpers.rspub use 重导出。Wave 2/3 的 agent 直接引用,不用等。


Gate 1Wave 1 → Wave 2

条件:
  □ cargo check --workspace 全部通过
  □ cargo test -p df-ai 全部通过
  □ cargo test -p devflow 全部通过(已有测试不受影响)

验证项:
  □ 日志: compress 触发时 available 减去 sys_tokensT1
  □ 手动: 大工具结果 DB 存原文(T2
  □ 手动: namespace 引用格式正确(T2

回滚:
  单文件回退: T1 删参数,T2 删 `namespace_store.rs` + 恢复 process_tool_calls 原文 push

Wave 2:结构化改进(3 agent 并行)

Agent 4T3 L3 结构化摘要

前置依赖Agent 3 定义了 CompressedSummary 结构

改动清单

文件 操作
src-tauri/src/commands/ai/compress.rs 45-52 签名 compress_via_llm 返回类型 Result<String>Result<CompressedSummary>
同上 82-86 prompt 构造 compress_prompt 模板要求输出 JSON+NL 双格式
同上 114-119 clean + 返回 clean_summary 适配双格式:提取 JSON 段 + NL 段,分别验证退化
同上 新增 extract_between 辅助函数(提取 <<<JSON>>>...<<<END_JSON>>> 之间的内容)
src-tauri/src/commands/ai/prompt.rs 240-290 compress_prompt 模板新增 JSON+NL 双格式输出要求
src-tauri/src/commands/ai/agentic/context_lifecycle.rs 145-155 消费处 Ok(Some(summary)) 分支中 insert_at 内容改为双格式嵌入
同上 178-210 兜底 关键词摘要降级 关键词降级不改,仍是纯文本(keyword_fallback 无 JSON

验证

  1. cargo test -p devflow — compress 单测通过
  2. 手动:触发压缩 → DB 中 system 消息含 <<<STRUCTURED_SUMMARY>>> 标记
  3. 手动:关键词摘要降级路径仍为纯文本(不受影响)

与 Agent 5 无依赖T3 只改 compress 路径,T4 只改 system prompt 注入,零耦合。


Agent 5T4 工作流 DAG 注入

前置依赖Agent 3 定义了 WorkflowContextBlockAgent 2 的 namespace 接口已就绪(弱依赖——仅用于产出关联)

改动清单

文件 操作
src-tauri/src/commands/ai/workflow_context.rs 新增 load_dag(), build_active_path(), summarize_node()
src-tauri/src/commands/ai/agentic/mod.rs ~1110-1128 build_for_request 之前:检测 conv.workflow_id → 加载 DAG → 构建 context block → 注入 system prompt
crates/df-ai/src/context_helpers.rs 新增常量 WORKFLOW_DAG_MARKER

核心逻辑

// workflow_context.rs
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(),
    }
}

关键安全

// mod.rs: system_prompt 注入处
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_id == None → 行为完全不变

验证

  1. cargo check -p devflow — 依赖 df-workflow crate 的 Dag::load 接口
  2. 手动:创建带工作流的对话 → system prompt 含 [工作流]
  3. 手动:自由对话(无 workflow)→ system prompt 不变

Gate 2Wave 2 → Wave 3

条件:
  □ cargo check --workspace 全部通过
  □ cargo test --workspace 全部通过
  □ 前端 vue-tsc --noEmit 通过

验证项:
  □ 手动: 压缩后 DB system 消息含 JSON 标记(T3)
  □ 手动: 工作流对话注入 DAG 块(T4)
  □ 手动: 自由对话不受影响(T4 安全)
  □ 全量: history_tokens 在 T2 基础上再次下降(T3 摘要更精简)

回滚:
  T3: 回退 compress.rs 签名 + prompt.rs 模板
  T4: 删 workflow_context.rs + 恢复 mod.rs system_prompt 拼接

Wave 3:常驻上下文(1 agent 串行)

Agent 6T5 WorkingContext 分层注入

前置依赖

  • Agent 3 的 WorkingContext 结构定义需在 context_helpers.rs 中(已在 Wave 1 Gate 前到位)
  • Agent 4 的 CompressedSummary 用于 reset_all_with_summary(弱依赖——无 NL 摘要时可跳过)

改动清单

文件 操作
crates/df-ai/src/context_helpers.rs 已有定义 WorkingContext / WorkingContextVersion 等已由 Agent 3 定义
crates/df-ai/src/context/mod.rs ContextManager struct 新增字段 pub working_context: Option<WorkingContext>
src-tauri/src/commands/ai/agentic/mod.rs ~960 system_prompt 拼接处 build_for_request 之前调 WorkingContext::build_injection() 注入 L1a+L1b
同上 tool_registry 注册 注册 read_context 工具(L1c
src-tauri/src/commands/ai/agentic/context_lifecycle.rs ~145-155 压缩成功后 ctx.version.reset_all_with_summary(&summary)
src-tauri/src/commands/ai/commands/chat.rs ai_chat_clear_context 归档后 reset WorkingContext(归档 = 上下文状态变更)

脏标记核心逻辑

impl WorkingContext {
    pub fn build_injection(&self, recent_turns: u32) -> String {
        let mut parts = vec![];

        // L1a: 当前步骤(每轮必带,~200 tokens
        if let Some(step) = &self.current_step {
            parts.push(format!("当前步骤: {}", step.step_name));
        }

        // L1b: 过去 recent_turns 轮内有变更才注入
        let threshold = self.version.current_turn.saturating_sub(recent_turns);
        let has_active = self.goals.iter().any(|g| g.status == GoalStatus::Active);
        if has_active && self.version.goals_changed_at >= threshold {
            let active: Vec<_> = self.goals.iter()
                .filter(|g| g.status == GoalStatus::Active)
                .collect();
            parts.push(format!("目标({}): {}", active.len(),
                active.iter().map(|g| g.text.as_str()).collect::<Vec<_>>().join("; ")));
        }
        if self.version.unresolved_changed_at >= threshold && !self.unresolved.is_empty() {
            parts.push(format!("待解: {}", self.unresolved.join("; ")));
        }
        parts.join("\n")
    }
}

验证

  1. cargo test -p df-aiWorkingContext::build_injection 单元测试(脏标记正确跳过/注入)
  2. 手动:观察 system prompt 含 L1a(当前步骤)和条件性的 L1b(有变更时)
  3. 手动:压缩触发后,下轮 L1b 正常注入(reset_all 生效)
  4. 开关:GOAL_PIN_ENABLED=false(或等价的 workincontext 开关)→ 行为恢复到改动前

汇总:执行序列

Week 1:
  Day 1:  Agent 3 定义契约(30 分钟) → 其余 3 agent 拿到接口开始编码
  Day 1-2: Agent 1 T1 + Agent 2 T2 并行编码
  Day 3:   Gate 1 验证 + 修复

Week 2:
  Day 4-5: Agent 4 T3 + Agent 5 T4 并行编码
  Day 6:   Gate 2 验证 + 修复

Week 3:
  Day 7-9: Agent 6 T5 编码(由于串行依赖,此阶段无并行)
  Day 10:  最终验证 + 全量回归测试

回滚总策略

场景 操作
T1 有问题 sys_tokens 参数 + 恢复 budget 变量 → 2 行回退
T2 有问题 namespace_store.rs + 恢复 process_tool_calls push 原文 → 1 文件回退
T3 有问题 回退 compress.rs + prompt.rsResult<String> → 2 文件回退
T4 有问题 workflow_context.rs + 恢复 mod.rs system_prompt 拼接 → 1 文件回退
T5 有问题 GOAL_PIN_ENABLED=false 等效开关 → 配置回退,不改代码
全量回退 git revert <commit-hash>