# Agent 架构硬化 — deepseek-harness 对齐改造 本文档记录 2026-08 的 agent 系统架构改造:以 deepseek-harness(DeepSeek 开源的 agent harness,见 `libs/deepseek-harness`)验证过的架构不变量为 参照,对本项目 agent 子系统的四项核心改造与若干升级。所有改动以 "取其分层思想、不取其运行时插件树"为原则——本项目是垂直应用, Rust 的 trait + 注册表天然等价于 dsh 的 capability seam。 ## 1. 统一 ReAct 引擎(消除四份手写循环) **不变量**:仓库中有且仅有一个具体的 ReAct 循环实现(对应 dsh 的 "agent-loop 是 harness 中唯一包含具体循环逻辑的包")。 - `src/agent/engine.rs`:`ReactEngine` 承载完整循环(取消检查 → 压缩 → 预算/nag → 步数上限 → 后台通知 → LLM 调用含恢复阶梯 → 工具执行 → 循环)。 - 行为差异通过组合字段注入而非分叉实现: | 关注点 | lead(主代理) | subagent | teammate | |---|---|---|---| | SSE 事件 | `EventTap::channel` | `EventTap::prefixed(tx, "sub")` | `EventTap::none()` | | 持久化 | `DbMessageSink`(agent_name=lead) | `DbMessageSink`(agent_name=sub_xxx) | None | | AskUser 权限 | `AskPolicy::Interactive`(120s 挂起) | `AskPolicy::AutoDeny` | `AskPolicy::AutoDeny` | | 取消 | `CancelSource::Session`(DashMap) | 不可取消 | `CancelSource::Flag` | | 恢复阶梯/熔断/重复检测 | 共享(runtime 级 Arc) | 引擎局部实例 | 引擎局部实例 | - 队友因此获得了与主代理对等的能力(错误恢复、压缩熔断、多槽重复检测)。 - `streaming_executor.rs`(936 行零调用死代码)已删除。 ## 2. 会话级运行时生命周期 **不变量**:runtime 状态属于会话而非请求。 `src/agent/runtime/session_registry.rs` 的 `SessionRuntimeRegistry` (挂在 `AppState.agent_runtimes`): - 同一会话的请求复用同一 `AgentRuntime`——后台任务队列、压缩折叠日志、 文件缓存、拒绝追踪器、压缩熔断器、prompt cache 不再随 HTTP 请求销毁 (历史上"后台任务结果跨请求丢失、每 turn 重复压缩"两个 bug 的根因)。 - 每会话一个 turn 互斥锁:并发请求 fail-loud 返回 409。 - 空闲 2 小时的条目由后台清扫回收;会话删除时显式移除。 - 会话 ID 在请求入口预分配(`create_or_resume_session_preallocated`), 新会话的首个请求也能写入取消标记(修复 SSE 超时只 abort 不落标记)。 - 模式回放:runtime 首次创建时从 DB 读取会话模式,后续请求不再因传参 不同而静默切换行为。 - 压缩递归守卫从进程级 `AtomicBool` 改为按 session_id 的集合守卫 (`try_begin_compaction`/`end_compaction`),并发会话压缩互不干扰。 ## 3. 会话事件日志(事件溯源的生命周期侧面) **不变量**:模型可见 ⟺ 已日志化;turn/compaction 以日志化锁开闭。 新表 `agent_events`(migration 20260819000001)+ `src/agent/runtime/session_events.rs`: - `turn_start`/`turn_end`(含结构化终止原因 `reason_label`)、 `compaction_start`/`compaction_end` 构成日志化锁。 - 崩溃恢复不截断:`reconcile_interrupted` 检测开口的 start 事件并合成 `interrupted` 关闭(对应 dsh "不伪造完成、但补写中断事实"的策略)。 - `context_snapshot`:turn 内发生过压缩时,turn 结束保存折叠后上下文 + 消息高水位 `base_message_id`;下一 turn 回放"快照 + id > base 的增量" (`context.rs::load_folded_history`),不再从原始消息重建后重新压缩。 rewind/retry/branch 后快照作废(`remove_context_snapshots`)。 ## 4. 工具契约集中化(消除散弹式修改) **不变量**:工具的全部行为元数据声明在工具自身(AgentTool),消费方查询 trait 而非维护平行的名字名单。 `AgentTool` 新增声明方法: - `untrusted_output()` — 输出来自外部源需 `` 包裹 (原 `untrusted.rs` 按名列表);注册表查询不到工具时回退名字启发式。 - `hardline_check(args)` — 参数级不可绕过拒绝(原 executor 按名路由)。 - `causes_file_changes()` — 执行前触发 checkpoint 快照(原 `CHECKPOINT_TRIGGER_TOOLS` 名单)。 - `loop_signals() -> ToolSignals` — 循环行为信号(todo nag 重置/todos 持久化/手动压缩请求),替代主循环对 `todo_write`/`compress_context` 的按名特判。 - 输出契约:`ToolOutput.value`(canonical JSON,机器消费:重放/审计/剪枝) + `content`(模型侧 render 投影),value 随消息 metadata 持久化。 样例:`search_papers`、`todo_write`。 - 注册表测试从硬编码数量断言(`assert_eq!(defs.len(), 35)`)改为 inventory 不变量断言(名字唯一/元数据完整/信号与身份一致), 新增工具不再需要改测试。 ## 5. 权限单调性(deny > ask > allow,只收紧不放松) **不变量**:多源决策合并唯一入口 `permission::tighten(base, candidate)`: - Deny 粘滞——任何层不能放行另一层已 Deny 的调用; - Ask 不可被放松为 Allow,可升级为 Deny; - executor 的 hook 请求、工具级声明、会话级规则全部经 `tighten` 合并; - 无人值守上下文(子代理/队友)的 AskUser fail-closed 自动拒绝 (`AskPolicy::AutoDeny` + `engine::auto_deny_ask`)。 历史上与此语义重复且从未接线的 `resolve_permission_precedence` 已删除。 ## 6. 上下文管理升级 - **KV-cache 纪律**:system prompt 只包含字节稳定 section;技能清单/ 项目记忆以 durable 动态上下文快照(user 消息)追加在历史尾部,哈希 未变不重注入(`context.rs::build_initial_context` + `AgentRuntime::build_dynamic_context`)。 - **记忆智能选择接线**:记忆条目 > 8 时走 `select_relevant_memories` (LLM 结构化选择 + 指数时间衰减,此前为零调用代码)。 - **确定性剪枝**:`micro_compact` 对长工具结果保留头/尾预览 (`prune_tool_result`),短结果仍为纯占位符。 - **Spill 检索指引**:溢出 stub 携带工具名 + 明确取回路径(read_file + max_lines 分段)。 - **Token 估算统一**:全循环使用 `compact::estimate_message_tokens` (CJK 加权),消除主循环 `len/3` 与 compact 两套口径。 ## 7. 其他接线与修复 - **团队协作接线**:`spawn_teammate` 等 4 个工具经 `ToolRegistry::new_with_team` 注册进会话 runtime(TeamManager 按 session_key 初始化邮箱目录);队友注册表真正排除 subagent/spawn_teammate (原注释声称排除但未过滤)。 - **Continuable 子代理**:`subagent` 工具新增 `agent_name` + `followup` 参数——命名子代理历史持久化,followup 回放全部历史续话(dsh Activation 模型的冷恢复路径)。 - **IdlePoller 接线**:`AGENT_AUTONOMOUS_ENABLED=true` 时启动自治轮询, 经会话注册表获取 runtime(模式从 DB 回放)。 - **重复检测多槽**:`DuplicateDetector` 连续通道 + 滑动窗口双通道, 可检测 A/B 交替死循环(原单槽实现检测不到)。 ## 不采用的设计(与理由) - Cordis 运行时插件树 / bundle/patch/profile 配置组合层:服务于通用 harness 的第三方插件生态,本项目是单二进制垂直应用。 - Typert 类型图 RPC、host/client 双聚合、Python SDK:无对应需求。 - 适配器休眠挂载、profiles 热重载:模型链固定。