把 HTML 当作接口
构想一种适合 AI Agent 访问的网页:内容和操作都由基础 HTML 完整表达,CSS 与 JavaScript 只做增强。人保留直接、可修正的基础交互,BrowserMCP 等工具驱动的 Agent 也不必在视觉线索中猜测。不是另做一个 Agent 版,而是让同一个页面成为双方共用的接口。
我最近常让 AI Agent 替我操作网页。查一条记录、填一张表、从列表里找到某个项目,事情本身都不复杂,真正消耗时间的却常常是另一件事:按钮没有名字,内容只画在画布上,链接其实不是链接,提交结果只在屏幕角落闪现一下。人看一眼界面就知道该怎么办,Agent 却要在大量视觉线索和 DOM 节点之间猜测。
于是我开始构想一种更克制的网页:只使用基础的 HTML 特性,把内容和操作直接写在文档里;CSS 让页面保持易读,JavaScript 只做可选的增强。人仍然可以正常浏览、点击和填写,只是没有复杂的动效、拖拽和层层弹窗。对使用 BrowserMCP 这类工具的 Agent 来说,它也不需要先理解一套为视觉效果设计的界面,才能完成任务。
这不是要再造一个「Agent 版互联网」。我的想法恰好相反:不增加一套专用协议,而是把 HTML 本身当作接口;不为 Agent 另做一个页面,而是让同一个页面对人和 Agent 都保持直接。
Agent 需要的不是更聪明地猜
用浏览器完成一件事,本质上是一个循环:观察当前状态,决定下一步,执行动作,再检查结果。网页越依赖位置、颜色、悬停和临时出现的控件,这个循环里就越充满猜测。一个只有图标的按钮,人可以根据经验认出它;一个没有文本、没有可访问名称的按钮,给 Agent 留下的却只有一种暗示:去猜这张图通常代表什么。
当然可以让模型更强,让它看截图、理解布局、模拟鼠标轨迹。但用更强的模型弥补含混的界面,是把设计成本转移给每一次访问。每次都要重新识别,每次都多一次点错的机会。更省事的办法是在页面上直接写明:这是什么,它能做什么,操作之后发生了什么。
所以,Agent 友好并不等于给网页塞满供机器读取的特殊标记。最基本的要求反而很普通:让观察、动作和结果都能从文档中确定地读出来。
内容应该是一份文档,不是一幅画面
这样的网页从一个朴素的结构开始:页面有明确的标题,正文放在main 里,章节使用有层级的标题,导航使用链接,列表就是ul 或 ol,表格数据放在table 里。重要信息以文本存在于 HTML 中,而不是只出现在图片、画布或一段运行后才生成的脚本状态里。
这些标签不是为了通过某种规范检查。它们是在减少歧义。标题告诉访问者这一页是什么,列表告诉访问者这些项目属于同一组,表头说明每一列的含义。人看到的是排版后的结果,Agent 读到的是同一层结构。双方不必维护两份关于页面的理解。
链接也应该是真的链接。一个资源有稳定的 URL,打开、刷新、收藏和转交时都指向同一件事;下一页是a href,而不是只在点击时改变内部状态的div。无限滚动可以提供便利,但至少应该留一条可以逐页访问的路径。否则,Agent 很难知道列表是否读完,也很难在中断之后回到原来的位置。
操作应该回到链接和表单
只读页面还容易,真正拉开差距的是操作。很多现代界面把动作拆散在一串脚本事件里:单击某个区域、拖动一个卡片、等待菜单出现,再从几个没有说明的选项中选择。对人来说,这些动作可能很顺滑;对 Agent 来说,每一步都是额外的状态和失败点。
我想象中的主要流程,用最普通的表单就能表达:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>新建任务</title>
</head>
<body>
<main>
<h1>新建任务</h1>
<form method="post" action="/tasks">
<p>
<label for="title">标题</label><br>
<input id="title" name="title" required>
</p>
<p>
<label for="description">说明</label><br>
<textarea id="description" name="description"></textarea>
</p>
<button type="submit">创建任务</button>
</form>
<p><a href="/tasks">返回任务列表</a></p>
</main>
</body>
</html>这段代码没有任何视觉上的野心,却已经把接口说清楚了。label 说明要填什么,name 说明提交什么,按钮说明会发生什么,action 和method 说明动作发往哪里。人可以在浏览器里使用它,Agent 也可以。即使 JavaScript 完全没有加载,主要任务仍然能够完成。
查询可以尽量使用普通的 GET 表单,让搜索条件留在 URL 中;创建和修改使用明确的表单与提交按钮;危险操作不要伪装成一次含义模糊的点击。这里没有什么新技术,只有一条约束:不要用一个看似灵活的事件系统,重新发明浏览器本来就会做的事情。
结果必须留下来
操作完成只是流程的一半。Agent 还需要知道动作是否成功,失败又失败在哪里。一个两秒后消失的 toast,一个只改变颜色的边框,一个永远旋转的加载图标,都没有把结果说清楚。
更可靠的方式是让结果成为页面状态的一部分。创建成功后跳转到一个有稳定 URL 的详情页,标题和正文明确显示新建的对象;失败时重新渲染表单,保留已经填写的内容,并用文本列出错误。动态页面可以在现有文档中更新状态,但状态不能只存在于某段脚本的内存里,更不能要求访问者恰好在那一秒盯着屏幕。
这条要求对人同样有用。网络中断后可以刷新,填写出错后不必重来,完成一项操作后可以复制链接交给别人确认。为 Agent 减少的不确定性,通常也是为人减少的不确定性。
克制不等于简陋
我说只保留基础特性,并不是主张回到白底蓝字、完全不考虑阅读体验的年代。合适的行宽、清楚的层级、足够的对比度、可见的键盘焦点、在手机上也能点中的控件,都应该保留。基础交互也不只意味着「能提交」:清楚的返回路径、可修正的输入、可理解的分页和错误提示,都是交互的一部分。
真正要去掉的是不必要的间接层。能用链接完成的导航,不必做成手势;能用按钮完成的动作,不必只支持拖拽;能直接显示的说明,不必藏在悬停之后;能用普通页面完成的确认,不必嵌进三层对话框。CSS 和 JavaScript 仍然可以改善这些流程,但移除它们之后,页面不应只剩一堆无法理解的容器。
换句话说,这不是降低网页的能力,而是给能力安排一个可靠的底座:先让 HTML 完整,再让增强有地方发生。
不需要另开一个 Agent 版
一个很容易想到的方案,是保留原来的网页,再提供/agent 或 ?format=agent之类的简化入口。这在迁移期或许有用,但我不希望它成为最终形态。两个版本会分别变化,简化版迟早会缺少字段、落后一步,或者只剩下只读内容。最后,Agent 能访问的又变成了一种次等功能。
更好的关系是同一份语义完整的 HTML,加上面向人的视觉呈现和可选增强。人看到的是设计过的页面,Agent 读到的是同一个资源、同一组操作、同一个结果。底层不分裂,才不会要求站长永远维护两种真相。
这也不排斥 API。高频调用、批量处理和长期稳定的系统集成,仍然应该使用有明确契约的 API。网页适合的是另一种场景:任务不固定,调用量不大,Agent 需要利用现有账号和权限,在人的监督下完成一次具体操作。BrowserMCP 这类工具的价值,就在于不必等待每个网站都为每种任务提供专用接口。语义清楚的 HTML,则让这条通用路径少一点猜测。
可访问不等于不设防
让 Agent 容易理解页面,不等于让 Agent 可以执行页面上的所有动作。读取和写入应该有不同的权限,敏感操作需要明确的确认,服务端仍然必须检查身份、权限和请求来源。一个表单再简单,也不能替代安全边界。
同样,减少脚本和装饰并不会自动解决提示注入,也不会让网页内容变得可信。页面里的文字仍然是外部输入,Agent 仍然需要区分「页面上写了什么」和「自己应该服从什么」。克制的 HTML 提供的是清楚,不是豁免。
少设计一点接口
这个构想里最吸引我的地方,是它几乎不要求发明什么。标题、段落、链接、列表、表格、表单、稳定的 URL,这些 Web 很早就拥有的东西,已经能描述大多数阅读和操作。后来增加的层层抽象当然有其价值,但它们也经常把最简单的任务包进最复杂的表达方式。
如果网页的主要流程能重新落成一份可操作的文档,那么人不必学习一套多余的动作,Agent 也不必模仿人的每一次视觉判断。对 Agent 时代来说,一种很激进的网页设计,也许恰恰是少设计一点:让内容直接可读,让动作直接可做,让结果直接可见。剩下的,交给浏览器就够了。