TL;DR:

  1. Agent 不再把 Markdown 塞进聊天窗口,而是维护一个可交互的 HTML 工作台——人类在页面上操作,Agent 读取状态继续推进。
  2. 我做了个开源脚手架来验证这件事,测试效果很爽,已开源。

0. 起因:Claude Code 负责人的一篇文章

前两天 Anthropic 的 Claude Code 团队成员 Thariq Shihipar 发了一篇博客:

《The Unreasonable Effectiveness of HTML》

核心观点足够颠覆——

当你跟 Agent 协作时,应该要求它输出 HTML 而不是 Markdown。

为什么?

因为 Markdown 本质上是一种线性文本格式

它能做的事情,HTML 全都能做,且做得更好:SVG 图表、可折叠面板、颜色编码、交互式组件、页面内导航……

而 Markdown 在这些场景下只有纯文本。

这篇文章让我想到一个更远的问题:

如果 HTML 比 Markdown 更适合 Agent输出,那它能不能更进一步——成为 Agent 跟人类双向交互的介质?

于是我做了个脚手架来验证这件事。

1. 问题:Markdown 对话的根本瓶颈

先看一个你一定经历过的场景——

你让 Agent 帮你做项目规划,它输出了一个优先级列表:

Q2 功能优先级:
1. 功能 A (P0)
2. 功能 B (P1)
3. 功能 C (P2)

你想把功能 B 提到 P0,于是打字说:"功能 B 应该提到 P0,功能 A 降到 P1"。Agent 收到后重新输出一遍列表。

这个循环有两个根本问题:

第一,信息是"读后即焚"的。上下文存在于对话流中,滚动即消失。你必须不断回滚才能看到之前的决策。

第二,人类只能用自然语言回复。Agent 输出了结构化数据(优先级列表),但人类的反馈方式是非结构化文本。Agent 需要重新解析"功能 B 提到 P0"这句话,才能理解你的意图。这一步不仅低效,还容易出错。

Thariq 的文章解决了第一个问题——用 HTML 做更丰富的输出。但它仍然是单向的:Agent 输出 HTML,人类阅读,然后用自然语言回复。

我们需要的是双向闭环

2. 我们做了什么:WS Workspace
打开网易新闻 查看精彩图片
2. 我们做了什么:WS Workspace

这里之所以用“我们”,是因为项目完全是由 Hermes 开发的。

WS Workspace 是一个开源脚手架,它可以实现:

  1. 人和 Agent 一起在一个网页里交互完成上下文构建
  2. Agent 根据人的需求生成网页,人来点选做出决策,Agent 接受、执行
  3. 所有协作过程留在网页上,随着项目一期生长

实现方式:

  1. 一套脚手架,项目启动时直接初始化一套空的网页交互环境
  2. 提供 CLI 和 Skills,Agent 可以使用 CLI 向网页中注入需要的组件
  3. 通过 MCP 服务,接收用户在网页的操作,回传给 Agent 作为上下问

整个架构是一个三层闭环:

Agent  ──CLI 指令──→  服务器  ──WebSocket──→  浏览器(人类操作)
Agent ←──REST API── 服务器 ←──WebSocket── 浏览器(状态回流)

Agent 不大量写 HTML/CSS/JS。它只需要使用 CLI 发命令:

ws add kanban --columns "想法,评估中,已确认"
ws add decision-card --question "先做哪个?" --options "功能A|功能B|功能C"

人类不需要打字。直接在页面上拖拽卡片、点选方案、填写表单。

打开网易新闻 查看精彩图片

action-to-context

Agent 读取结构化状态:

ws state
→ {"selected": "功能B", "kanban": {"已确认": ["功能B"]}}

没有歧义,不需要 NLU 解析,没有"我以为你说的是 P0 不是 P1"的问题。

3. 组件体系:7 个组件覆盖 80% 场景

我们没有一开始就做 50 个组件。第一版只做了 7 个核心组件,但它们能覆盖 Agent 跟人类协作的绝大多数场景:

组件 用途 人类操作 → Agent 收到的数据Kanban 看板优先级排序、状态跟踪 拖拽位置、新增/编辑卡片Decision Card 决策卡方案选择、A/B 决策 选了哪个选项 + 备注✅ Todo List 待办任务清单、验收标准 勾选/取消/新增/编辑Form 表单结构化输入 完整表单数据 JSONTable 表格数据对比、规格展示 单元格编辑✏️ Rich Editor 编辑器自由书写、头脑风暴 富文本内容Heading 标题分区、上下文说明 纯展示

每个组件的使用方式都是一条 CLI 命令。Agent 不需要写前端代码,只需要描述意图

举个例子,下面是 Agent 注入一个决策卡的完整流程:

# Agent 发指令
ws add decision-card \
--question "技术方案选型" \
--options "原生WebSocket|Socket.IO|SSE"

# 人类在页面上点选了"Socket.IO"

# Agent 读取结果
ws state decision-card-xxx
→ {"selected": "Socket.IO", "comment": "自动重连很重要"}

就这么简单。

4. 看起来什么样

下面是一个真实的工作台截图。所有组件都是 Agent 通过 CLI 指令注入的,人类可以在页面上直接操作:

  1. 选题规划看板,选完直接激活子 Agent 开写
打开网易新闻 查看精彩图片
  1. 课程形式设计,菜单式选择,可以补充说明,Agent 就可以直接开干
打开网易新闻 查看精彩图片
  1. Agent 需要根据定价设计课程宣发?给个表单让人填写
打开网易新闻 查看精彩图片

所有这些组件,Agent 只用了1 条 CLI 命令 + 参数就注入完毕。

人只需要守在网页前面点点点

5. 跟传统方案比,多了什么 能力 Markdown 聊天 Claude Artifacts HTML 工作台 信息密度 ✕ 纯文本 ✓ 富 HTML ✓ 富 HTML + 交互组件 双向交互 ✕ 只能打字 △ 有限表单 ✓ 拖拽/选择/填写 状态持久性 ✕ 滚动即消失 △ Artifact 内保留 ✓ 持久页面,状态累积 Agent 驱动 ✕ Agent 写文本 △ Agent 生成代码 ✓ CLI 指令注入 可分享性 ✕ 截图/复制 △ 需 Claude 账号 ✓ 链接直接打开 Agent 读取状态 ✕ 需解析自然语言 ✕ 无法读取 ✓ 结构化 JSON API

最关键的区别是最后一行:Agent 能不能读取人类的操作结果。

在 Markdown 对话中,Agent 只能读到人类打的字。在 Claude Artifacts 中,Agent 根本无法感知人类在 Artifact 里做了什么。

但在 HTML 工作台中,Agent 通过 REST API 获取完整的结构化状态——哪个选项被选中了、卡片被拖到了哪一列、表单填了什么值——全部是 JSON,零歧义。

6. 技术实现:简单到出乎意料

整个项目只有两个运行时依赖

{
"dependencies": {
"express": "^5.2.1",
"ws": "^8.20.0"
}
}

没错,就是 Express + WebSocket。没有 React,没有构建工具,没有框架。

ws-workspace/
├── server.js # Express + WebSocket 服务器(约 400 行)
├── ws-cli.js # Agent CLI 工具(12 个子命令)
├── mcp-server.js # MCP 服务器(8 个工具,JSON-RPC stdio)
├── public/
│ └── index.html # 前端工作台(单文件,内联 CSS + JS)
└── skills/
└── ws-workspace.md # Agent Skill(使用指南)

三层设计:

  • CLI 层(Agent 的手):ws addws statews update,声明式操作,返回 JSON
  • MCP 层(Agent 的耳):WebSocket 双向通道,wait_for_user可暂停等待交互
  • Skill 层(Agent 的脑):告诉 Agent 有哪些组件、怎么用、什么场景用什么

前端是一个单 HTML 文件,所有组件通过 Web Components 渲染。Claude 暖色调主题,卡片式布局。用户操作自动通过 WebSocket 同步到服务器。

一行命令启动:

npx ws-workspace start
7. 一些踩过的坑

做这个项目的过程中踩了不少坑,挑几个有意思的分享:

人的备注:是 Hermes 踩的坑,我测试发现问题让它改的,它写到文章里了。

坑 1:IME 拼音输入被打断

最初的版本里,决策卡的备注框用oninput实时同步状态。但oninputsendEvent()→ 服务器广播 →renderAll()会销毁整个 DOM 重建,导致中文拼音输入法的组合状态被打断——你按 "zh" 想打"中",结果 "z" 刚上屏就被清空了。

修复:所有文本输入组件改为「本地编辑 + 显式保存按钮」模式。只在用户点"保存"时才同步状态,不再实时广播。

坑 2:Kanban 拖拽的 ondragover

HTML5 的拖拽 API 有个反直觉的坑:ondragover必须调preventDefault(),否则ondrop事件根本不会触发。这个 bug 让我调试了半小时。

坑 3:组件 ID 生成

一开始让客户端自选 ID,结果两个人同时添加组件时 ID 冲突。后来改成服务端生成{type}-{random_hex}格式,彻底解决。

8. 这件事靠不靠谱?

说实话,目前还是实验性项目。但有几个观察让我觉得方向是对的:

1. Agent 天生就擅长结构化输出。

让 Agent 生成一条ws add kanban --columns "待办,进行中,完成"指令,比让它生成一整段 Markdown 列表要简单得多。指令是结构化的,Agent 出错的概率更低。

2. 人类天生就擅长视觉操作。

拖拽一张卡片到"已确认"列,比打字说"把功能 B 从评估中移到已确认"更直观、更快、更不容易出错。

3. 状态不需要"理解",只需要"读取"。

Agent 不再需要做 NLU(自然语言理解)来解析人类的回复。状态就是 JSON,直接读,零歧义。

9. 如何开始

# 一行命令启动
npx ws-workspace start

# Agent 注入组件
ws init "我的工作台"
ws add kanban --columns "想法,评估中,已确认"
ws add decision-card --question "选哪个?" --options "A|B|C"
ws add todo-list --title "待办" --items "任务1,任务2"

# 读取人类操作结果
ws state

GitHub 仓库:comeonzhj/ws-workspace[1]

npm 包:npm install ws-workspace

如果你无法访问 GitHub,可以在知识星球下载压缩包,解压后让 Claude Code 或者其他 Agent 完成剩下的事情。

最后
打开网易新闻 查看精彩图片
最后

Thariq 说"我使用 HTML 的真正原因是我觉得与 Claude 的互动更加紧密了"。

我想在后面加一句:当 HTML 从输出格式升级为双向协作的介质,Agent 跟人类之间的互动就不只是"更紧密"了——而是第一次真正意义上的"协作"。

Agent 不再写给人看,而是跟人一起干。

最后的备注:

  1. 本项目全程由 Hermes 开发,我只提供了最开始的灵感和中间的测评建议
  2. 包括本文,也是在 Hermes 撰写的项目推荐稿,我做了少量细节优化
  3. Hermes 由MiMo-V2.5-Pro驱动