Files
deepseek-harness/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.zh.md
T
Turtle f290a8b851 refactor(cli)!: one shared base config with per-surface overlays
`dsh` shipped two config trees that were 43 rows the same: apps/cli/cordis.yml
composed web as 74 flat rows, while the TUI booted examples/tui-agent/cordis.yml
whose single `@deepseek-ai/dsh-tui-demo` row mounted twelve plugins behind a
twenty-key pass-through Config. Neither file was what its location claimed —
apps/cli hardcoded the "example" as the product default and the "demo" bundle
was the application — and every capability change had to be made twice.

- apps/cli/base.cordis.yml holds the 43 shared rows; tui.cordis.yml and
  web.cordis.yml are patch lists stating only what differs per surface
- overlays apply as SIBLING patch lists at one include level, because include
  patches never cross an include boundary. Precedence: base < surface <
  (--config | personal ~/.dsh/config.yaml) < launcher flag/profile patches
- `--config` now applies an overlay INSTEAD OF the personal one, so a demo or
  test tree never inherits the user's route; new `--config-replace` boots a file
  as the entire tree (the old `--config` behaviour). Both survive /resume
- vendor/include: index each `insert`ed row as it is added so a later patch can
  configure or disable it. Upstream built the id index once before the patch
  loop, leaving every surface-only row — the whole TUI front door — silently
  unpatchable from user config. Logged as local modification 8
- session identity moves to dsh-agent-loop's CONFIGURED_AGENT_IDENTITIES_KEY;
  dsh-tui's MAIN_SESSION_ID_KEY is deleted (only the bundle read it)
- delete examples/tui-agent, examples/cordis-agent, packages/examples/tui-demo;
  TUI tests → apps/cli/tests, cordis e2e → packages/cordis/tool-cordis/tests,
  examples/code-mode survives as an overlay leaf
- `dsh web` gains --config, threaded into AppCLIEntry as an extra overlay

Three latent defects surfaced and are fixed here: the TUI captured the optional
sessionQuery service once at construction and could permanently disable /resume
when it won the mount race; the session-store root silently reverted to a
project-local ./.sessions; --config-replace was dropped by the resume handoff.

Verified by booting each tree through the real Loader (TUI 55 entries, web 75,
zero unsettled) rather than reading YAML. All eight terminal snapshots replay
byte-identically; 14/14 PTY smoke, 112/112 snapshots, 25/25 doc-sync, hygiene
and lint clean.
2026-07-29 21:15:42 +08:00

5.6 KiB

Agent Note: 移除 stdio 和 Echo agent

Status: implemented

English | 中文

问题

DeepSeek Harness 在 TUI 和 Headless coding agent 之外,还提供了两个重复的产品 agent(智能体)。面向行的 stdio agent 使用混合的提示符/输出协议,同时重复实现终端交互与非交互执行。Echo 则以无需联网的 mock 模型加一个教学工具重复实现 Headless,把测试 fixture(测试前置数据)变成面向用户的 agent 和默认快速上手路径。

两个 agent 的配套实现都不止叶节点配置。stdio 拥有 UI 插件、app 包(package)、SDK 接口、REPL 叶节点、提示符协议和 Loader 测试。Echo 拥有可运行命令、mock 适配器、工具、CI 演示门禁、图谱条目、教学引用和共享测试 fixture。保留其中任何产品路径,都会间接保留这个重复的 agent。

标准输入输出仍是 ACP、JSON-RPC、MCP 和子进程的协议边界。确定性模型适配器也仍可用于测试。这些机制不足以成为保留面向行或仅使用 mock 的产品 agent 的理由。

决策

彻底移除 stdio 和 Echo agent,不提供兼容包、模式、命令或别名。删除 stdio UI 包与 app 包、examples/repl-agent、examples/echo-agent、demo:repl、demo:echo、各自的专属测试,以及相关的 manifest(元数据清单)、门禁、图谱和文档条目。

保留的应用角色均有明确归属:

  • @deepseek-ai/dsh-tui 负责终端交互式执行。它会在 Loader 启动前拒绝非 TTY 流;examples/tui-agent 拥有完整 coding 组装、Code Mode 覆盖层、PTY 覆盖和终端快照。
  • @deepseek-ai/dsh-cli-demo 负责非交互式执行,包括管道方式。examples/headless-agent 拥有真实模型的单次任务组装、回放快照、通用真实 agent 测试套件,以及仅供测试使用的无密钥 Loader fixture。
  • @deepseek-ai/dsh-acp-demo 和 @deepseek-ai/dsh-jsonrpc 负责各自的分帧协议集成。

SDK 工程模型与 create/config 工作流将 stdio 运行接口选项替换为 tui;生成的 TUI 工程组合 @deepseek-ai/dsh-tui,并创建或恢复一个确切会话。仓库中的演示文档要求 DeepSeek API key,并优先引导到真实的 Headless 或 TUI agent。

无密钥验证由测试负责。Headless Loader 冒烟测试使用 fixture 适配器验证真实工具往返;CLI built-bin 测试套件固定输出、持久化、失败和信号语义;各包专属的 Loader 测试则将确定性适配器放在对应场景旁。其中任何一项都不会作为可运行的 mock agent 对外暴露。

验证

TUI 与 Headless 的 Loader 覆盖以源码和构建产物两种模式运行真实 app 包。由 PTY 驱动的子进程覆盖仅用于 TUI 生命周期;其他入口冒烟测试使用单次管道协议。Headless 验证任务/结果契约和工具调用契约。生成图谱与仓库搜索会拒绝陈旧的包、命令、叶节点、SDK 接口、createStdioChat 和 StdioRuntime 引用。

TUI PTY 冒烟测试包含 Code Mode 覆盖层组装,而 examples/cordis-agent/tests/keyless-smoke.e2e.ts 会基于真实 Cordis-agent Loader 目录树执行最小 PTY 启动。构建后的 dsh 可执行文件会在 Loader 启动前拒绝通过管道启动 TUI,并指向其单次 -p 模式;apps/cli/tests/built-bin.e2e.ts 固定了该执行路径,而 cli-demo 的 built-bin 套件在普通 Node 下运行文本、JSON 和经过结构化解析的 stream-json 输出,持久化新建会话,并在不污染 stdout 的情况下拒绝无效参数和缺失配置。时间上下文集成通过真实 Headless 组装执行两个有序轮次,而更细粒度的耗时行为由时间上下文的包级测试负责。

曾考虑的替代方案

  • 仅为 pipe 保留面向行 agent:不予采纳,因为 Headless 已提供有界任务契约、格式纯净的 stdout、持久完成边界和进程退出状态。
  • 将 readline helper 作为包保留、折叠或提升:不予采纳,因为它只有一个 app 消费方,并不存在可独立替换的契约。将它折叠进 stdio app 虽然移除了没有正当理由的支撑包边界,却仍保留了重复产品;将来要重新引入这个包,独立的面向行 UI 必须先有真正的第二个消费方。
  • 保留 Echo 作为无密钥快速上手路径:不予采纳,因为首次产品体验应使用真实模型和受支持的 coding agent,而不是带专用工具的脚本化适配器。
  • 只为 CI 演示命令保留 Echo:不予采纳,因为由测试持有的 Headless fixture 可以覆盖相同的 Loader 和构建产物边界,无需保留 mock 产品叶节点。
  • 移除所有 stdio 或 mock 机制:不予采纳,因为分帧协议、进程 I/O 和确定性测试适配器是独立基础设施,并不是被移除的 agent。

后果

  • 交互式与非交互式产品执行分别只有一个归属方和一个可运行的 coding 叶节点。
  • 仓库没有面向用户的无密钥 agent 演示;本地 agent 演示需要 DEEPSEEK_API_KEY。
  • CI 通过测试 fixture 保留针对真实入口的无密钥覆盖,而不是依赖产品命令。
  • 既有 stdio agent 配置、Echo 命令和 SDK --interface=stdio 调用会直接失败,不会被转换。
  • 有意移除了单进程内基于管道的多轮交互,以及面向非 TTY ask_user_question 的 readline 提供方;恢复会话可以满足持久多轮工作,非 TTY 组装则必须自行提供交互提供方。