本文描述 2026-08-07 的当前合同。历史迁移方案、Presenter 拆分过程和已完成的分阶段
SDD 已删除;需要时从 Git 历史查询。
AgentManager 只按 canonical AgentDescriptor.kind 选择 backend:
kind=deepchat -> DeepChatAgentRuntime -> DeepChatAgentInstance -> DeepChatLoopEngine
kind=acp -> AcpAgentRuntime -> AcpAgentInstance -> ACP process/protocol
kind=deepchat + providerId=acp 是兼容组合:它仍运行 DeepChat loop,只把 ACP 作为 provider。
Direct ACP 不进入 DeepChatLoopEngine,也不通过 DeepChat ToolService 执行外部 agent 自己的 loop。
内部 descriptor 使用 discriminated union。旧 type / agentType 只允许在 storage、route DTO 和
renderer compatibility adapter 出现;manager/backend 不做反射式 fallback。
所有 Agent 平级。builtin、默认选择和父子关系是不同概念:
deepchat是source=builtin的受保护第一方 Agent,保持启用、不可删除并允许重置;- 默认 Agent 只表示入口未显式选择时使用哪个 Agent;硬编码
deepchat只能用于这一兼容默认; - 系统不存在主 Agent、父 Agent、
inheritsFrom或隐式模板关系; - 每个 DeepChat Agent 只从自己的持久化配置和确定性的代码默认值解析 effective config;
- 应用级默认模型和自动压缩设置属于 app settings,不以 built-in Agent 配置充当别名;
- 修改 built-in Agent 不得触发其他 Agent 的配置、Memory 或 Skill fan-out。
升级迁移会先按旧规则(包括 built-in 配置和原有 fail-closed fallback)物化每个可读 manual Agent 的 effective config,再启用独立解析;无法读取的配置也会尽量保留其旧 effective 值并记录 恢复证据。迁移完成后的独立 resolver 不再从 built-in Agent 静默补值,损坏配置在该阶段保持 fail-closed。
flowchart TD
Entry["Desktop / Remote / Scheduler / Subagent"] --> Session["Session Lifecycle / Turn"]
Session --> Manager["AgentManager"]
Manager --> Deep["DeepChat backend"]
Manager --> ACP["ACP backend"]
Deep --> Instance["one instance per loaded Session"]
Instance --> Run["one LoopRun per provider/tool round sequence"]
Run --> Provider["ProviderRuntime"]
Run --> Tool["ToolService"]
Run --> Memory["Memory ports"]
Run --> Tape["Session Tape / transcript"]
- Session 拥有长期身份、settings、transcript、Tape 和 pending input。
- Each DeepChat Agent owns its configuration and logical Skill bindings. Mutable Skill packages, the catalog cache, and the watcher belong to the application-level Skills service. Sessions store only their Agent assignment and selected Skill names.
- 已载入 Session 在一个 backend 中最多有一个 instance。
- 每次执行创建独立 UUID Run,Run 拥有取消信号、logical round、request sequence、physical attempt 和 临时输出;loop、resume 和 deferred tool execution 都不得复用旧 Run identity。
- Window、Remote endpoint 和 Cron run 只保存各自 binding,不拥有 Agent instance。
- 只读 list/query 不 hydrate backend;执行、完整 restore 或明确 backend 设置操作才允许 hydrate。
主要实现位于:
src/main/agent/deepchat/
├── instance/ # per-session runtime state
├── loop/ # LoopRun, input/context coordination and ports
├── runtime/ # provider/tool loop, interaction, compaction and projection
├── memory/ # prompt contribution and terminal ingestion
└── resources/ # system prompt resources
必须保持的合同:
- runtime 只通过构造时注入的窄接口访问 Provider、Tool、Memory 和 Session data;不得反向读取 App、 Desktop、Remote、Scheduler 或 route registry。
logicalRound统计一次模型响应及其工具结算循环;公共metadata.providerRounds和maxProviderRounds继续保留原名称,但只映射该 logical-round 计数。上限在进入下一 round 前检查, resume 从已持久化 metadata 恢复计数。requestSeq标识一份确定的 provider payload 和 ViewManifest;physicalAttempt标识该 request 的 实际发送次数。context recovery 改变 payload,因此推进 requestSeq 并把 physicalAttempt 重置为 1; 同 payload 的 transient retry 保持 requestSeq,只推进 physicalAttempt。- 每个 logical provider request 发送前都执行 context-pressure preflight。上一条成功 attempt 的 provider-reported prompt usage 只在 provider/model/generation/tool schema/消息前缀完全一致时作为 anchor,并只估新增 suffix;实际 fitted payload 与 continuation View 不一致或 usage 口径非法时不建立 该 attempt 的新 anchor,无法匹配已有 anchor 时回退全量估算。preflight 不得只因启发式估算而丢弃 system、当前 user input 或 tool protocol unit。
- Context compact 推进的是 Tape reconstruction boundary 和 provider View,不改写 raw Tape。semantic summary 失败时仍可提交 boundary-only anchor;semantic boundary recovery 只有在 durable cursor 前进且 重新派生的 View 严格变小时才算 applied。已闭合的大 tool result 可独立在当前 View 中稳定 stub 化, 原始 fact 保留在 Tape;先保护最新闭合 unit 的直接行动依据,较旧 unit 不足以解除压力时才允许压缩 最新 unit。该路径以协议闭合和 projection 确实变化为进度,不冒充 boundary progress。
- Cache-aware v2 View 把当前 Tape incarnation 第一条 effective user fact 作为 authoritative protected prefix,位于 system 之后、checkpoint 之前;连续 cursor 仍可越过该 fact,View 每次从 raw Tape 重投影。 pin 不使用 derivative 的 untrusted fence,不复制原文到 manifest,且只有 effective source entry、 source-content hash、identity 与精确前缀 hash 全部匹配时才能跨 provider round 继承。
- 一次成功 provider response 重置 sequence-level context recovery latch;同一 Run 最多使用三条 recovery sequence。恢复不能重放可能已执行副作用的原 user prompt,每个变化后的 payload 都必须写 新 requestSeq 与 ViewManifest。
- 工具 operation identity 为
(runId, requestSeq, providerToolCallId)。run_started必须先于 Run registration;dispatch fact 必须位于所有本地拒绝 gate 之后、真实副作用调用之前;known outcome 必须先于 result projection;terminal fact 必须先于 transcript、status、hook 和 renderer terminal projection。任一 strict Journal commit 失败都阻止下游边界继续。 DeepChatContextCoordinator.streamProviderAttempts是 transient retry 的唯一 owner。每个 logical round 最多重试两次,使用可取消的指数退避并服从有上限的Retry-After;context recovery 不消耗 logical-round 或 transient-retry budget,每次实际 attempt 都重新进入 provider rate gate。- 透明重放只允许发生在
outputCommitted之前。首个投影的 text、reasoning、tool lifecycle、 permission、image、plan 或 provider rate-limit event 会提交输出;usage、stop 和 error 是控制事件, 在 retry 决策完成前缓冲。无 stop 且无语义输出视为可重试的 premature EOF,已有 partial output 则保留输出并失败,绝不重放。 - message usage 对同一 logical round 内所有 physical attempt 的最终 usage snapshot 做 checked
aggregation;Tape 仍逐 attempt 保存各自 usage。
retry_scheduled、retry_started和retry_finishedobserver 只用于结构化诊断,不进入 renderer event bus,也不是持久化事实。 - provider request trace context 必须按 physical attempt 创建并闭合捕获 identity,不能读取后续可变的 Run counter。
- Run
AbortSignal必须贯穿 provider 建流和请求。AI SDK chat stream 显式使用maxRetries: 0,由 coordinator 统一重试;one-shot text/embedding 保持显式 SDK policy,ACP、image、video 和 TTS 不做 DeepChat 透明重放。 - send input 在 Session 边界正规化为 canonical request;resume 不重新解释旧 route DTO。
- permission mode 属于 Session assignment/settings;一次 Run 使用开始时的闭合快照,deferred tool execution 仍需重新检查当前安全边界。
- Run 的 effective Skills 是 Session 持久化选择与当前 Agent 有效、启用 Skill catalog 的交集;不得 回退到 built-in Agent catalog。
- paused interaction 按顺序结算;最后一项完成后创建新的 resume Run,不复用已经 settle 的 Run。
- paused terminal 只允许 permission/question action 保持 pending。任何其他
pending或loadingblock 都是 runtime invariant violation,必须在 paused terminal fact 之前转入 error settlement,不能 静默改成 success。 - no-progress tool loop 由
noProgressToolLoopGuard.ts终止;usage 由 runtime accumulator 跨 round 累加。 - provider terminal reason 必须无损正规化;普通
stop只有在确实解析出 tool call 时才能变为tool_use。
ACP 代码集中在 src/main/agent/acp/:
catalog/:registry、migration 和 settings;launch/:安装与 launch spec;client/:connection 和 protocol session owner;runtime/:capability、process、session、filesystem、terminal、permission 和 persistence;instance/:per-session ACP state;compatibility/:允许保留的外部格式 adapter。
ACP capability 必须来自 initialize result,未声明能力失败关闭。filesystem 和 terminal 请求受 workspace/path guard 约束。取消、process exit、permission resolver 和 session persistence 都由 ACP runtime 自己处理,不借用 DeepChat loop 状态。
Subagent 可用性只由当前 Agent delegation policy、正规化 slot 和 sessionKind 决定:
- 只有 regular DeepChat parent 且存在有效 slot 时才暴露
subagent_orchestrator; - Subagent child 不能再次创建 Subagent;
- admission 后的 run 使用已固定的 task/slot snapshot,真正调用前仍重新检查 host policy;
- Each child uses an independent Session, workspace authorization, Tool mapping, Memory namespace, and permission state. Its Skill catalog is the global package set filtered by the destination Agent's bindings; it does not inherit the parent's bindings or runtime Skill state.
- 完成后父 Session 记录 child Tape 的 frozen-head link,不复制 child entries。
每个 Subagent run 有独立 deadline;默认 300000ms,允许 1000-1800000ms。一个 parent 最多同时
拥有三个 nonterminal run。deadline 或手动取消会标记未完成 task、请求 child cancellation,并记录
timeout/deadline/reason;run 的终止不能无限等待被阻塞的 child cleanup。Child handoff 固定包含
Result、Evidence、Changed Files、Validation、Unresolved,调用方的 expectedOutput 只能追加
指导,不能替换基本合同。
Deletion does not depend on a currently valid Agent descriptor. It clears both backends' cached and durable bindings, then Session data, permissions, Skill selections, Agent Skill bindings, and the app-session row. Deleting an Agent preserves globally stored Skill packages and other Agents' bindings. ACP and DeepChat transfer validate the destination, commit ownership, and filter Session Skill selections through the destination Agent's assigned catalog before closing the old backend. Failure preserves the original assignment.
DeepChat Agent 删除与 Session create、detached create、Subagent create、transfer、fork 共享按 Agent ID 的 lifecycle gate。删除先阻止新的 assignment,再等待已进入的 assignment 完成,最后重新检查持久化 Session 引用;不得留下指向已删除 Agent row 的 Session。
scripts/agent-cleanup-guard.mjs prevents retired Agent/Session Presenter paths and imports from
returning. Unit and integration tests protect configuration materialization and independent
resolution, Agent Skill binding isolation, Session/catalog intersection, backend behavior, Subagents,
and Tape. There is no repository-wide heuristic architecture guard.
pnpm run test:agent:eval 提供离线 deterministic Agent 行为基线,使用 scripted provider/tool 直接
覆盖 production loop。场景包括 direct completion、多轮 tool、tool failure、permission pause、cancel、
pending yield、round guard、no-progress、empty output 和 context/provider errors;断言 persisted run
metadata、usage/cache fields、provider/tool budgets,不调用真实 provider,也不写仓库 artifact。
关键入口:
src/main/agent/manager/agentManager.tssrc/main/session/turn.tssrc/main/agent/deepchat/instance/src/main/agent/deepchat/loop/src/main/agent/deepchat/runtime/src/main/agent/acp/instance/src/main/agent/acp/runtime/test/main/agent/