我拆了一个 Agent 仓库,发现真正难的从来不是让 AI 会写代码 我拆了一个 Agent 仓库发现真正难的从来不是让 AI 会写代码我这两天翻一个 Agent 项目的源码看到一个很容易被忽略、但技术上特别关键的区分。run.completed不等于session.idle。前者只代表 Agent 的输出结束了后者还要等持久化、重试、上下文压缩和订阅处理都真正收束。就差这么一个状态背后却是两种完全不同的产品思路。一种思路是模型把字吐完了任务就算结束。另一种思路是模型只是完成了它的一段工作系统还要确认工具有没有收尾、事件有没有丢、文件有没有写进去、会话能不能恢复等这些都收束了才敢告诉你这件事做完了。Agent 真正难的地方这两年大家都在做 Agent。模型越来越强工具调用越来越自然写代码、改文件、做表格、生成 PPT演示视频里看起来都已经顺滑得不像话了。但是你真的把它放进一个真实工作流里很快就会发现最麻烦的地方往往不是模型不会回答。而是它回答完以后谁来管它。它能读哪些文件能不能碰工作区外的路径能不能直接执行一条 Shell 命令改文件之前有没有给你看 Diff模型请求的动作和真正获得的权限是不是一回事任务完成和数据落盘是不是一回事。再往后一点网络断了怎么办事件少了一条怎么办模型换了怎么办长会话超过上下文窗口怎么办刚刚生成的表格和 PPT 能不能继续在精确选区上修改。这些问题没有一个能靠多写几段 Prompt 解决。我以前也会下意识地把 Agent 项目理解成三块东西模型、工具再加一个聊天界面。这次翻完 Wordless 的代码以后我觉得这个理解得改一改。真正能拿来工作的 Agent长得更像一个有权限系统、有事件协议、有本地数据库、有恢复机制的工作台。聊天只是它最外面那层皮。从聊天窗口到工作台Wordless 这个名字翻译成产品口号就是少说废话把工作做完。这句话听起来像一句很轻的产品文案但顺着源码往下看会发现它其实在约束整个架构。它不是又做了一个只返回文本的聊天窗口而是做了一个运行在 macOS 和 Windows 上的本地优先 Agent 工作台。你选择工作区、模型和工作模式Agent 在明确的权限边界里读取上下文、调用工具然后把代码变更、电子表格、演示文稿、研究结果和媒体产物留在同一个工作界面里。本地优先这件事也不是把数据留在本地这么简单。Wordless 的工作区、会话和产物默认保存在设备上Google 登录和云同步都是可选能力。没有登录或者网络暂时不可用本地工作不会跟着一起瘫掉。这就带出了第一个很有意思的地方。它没有把整个 Agent 运行过程塞进一个远程后端里。在当前架构中Electron 是桌面宿主React Renderer 负责界面Preload Bridge 负责暴露受限能力Electron Main 负责可信的本地能力wordless/runtime负责会话、运行和编排。没有单独的本地 HTTP 后端也没有单独的 Agent 进程。链路大概是这样React Renderer - 受限 Preload Bridge - Electron Main - Wordless Runtime - Profile - Driver - Pi 派生的 Agent loop - Capability - 产物工作台。先把桌面边界立住这个设计听着没那么炫甚至有点朴素。但桌面 Agent 最容易出事的地方恰恰就是为了省事把 Renderer 直接和 Node.js、文件系统、任意 IPC channel 连在一起。一旦这样做界面里一段看起来只是用户输入的文本转眼就可能一路摸到本地文件、系统命令和凭据。Wordless 的做法是Renderer 通过DesktopBridge调用明确列出的能力Preload 里使用白名单化的wordless:*通道Renderer 不能自己拼任意 IPC channel也不能直接获得 Node.js 权限。权限不是弹窗是执行前的边界模型提出动作不等于拿到权限这个边界不是为了让架构图好看。它是在提醒你模型提出一个动作不等于这个动作已经拿到了执行许可。很多 Agent 产品讲工具调用时喜欢把重点放在模型能调用多少工具上。Wordless 反过来把一个更容易被忽略的问题放到了前面工具到底在什么条件下才能真的执行。这件事在packages/agent-workspace-policy/src/index.ts里写得很直白。工具执行前会经过preflightWorkspaceOperation。如果是bash系统会先拿命令去匹配 Shell 安全规则。当前会话不是完全放行而且命中了规则就需要用户确认。如果是write或edit系统会读取文件当前内容计算修改前后的预览判断目标路径是不是受保护路径然后把 Diff、匹配到的规则和文件基线一起放进审批定义里。也就是说模型说我要改这个文件真正发生的流程不是直接写入。更像是先读一下算出要改成什么样展示给你等你确认再产生副作用。这才是 Agent 和普通自动化脚本之间很大的区别。脚本一般默认你已经写好了所有判断。Agent 面对的是不确定的自然语言请求它会自己选择路径、工具和下一步动作所以它更需要一个会在关键位置踩刹车的系统。默认访问级别下工作区外的路径也不会悄悄放行。如果一个工具请求里出现了工作区之外的path、sourcePath、outputPath、filePath或cwd系统会把它识别出来生成一次性的外部访问审批告诉你哪些路径、什么操作、从哪个工作区发起。这类设计看起来有点麻烦。但你想想看一个 Agent 如果能替你写文件却不能让你知道它到底要写到哪里那它越聪明风险就越大。审批要能解释也要能追溯Wordless 甚至会为已批准的文件写入保留基线。这个细节很小但我挺喜欢。因为系统不只是记住用户点过同意还记住了当时文件是什么样。后面如果要生成变更、恢复状态或者解释一次写入至少有一份可追溯的依据。安全不是多弹一个确认框。安全是把动作、范围、理由和结果放在同一条链路里。真正好用的 Agent知道什么时候问说到这里可能有朋友会觉得既然每一步都要确认那 Agent 不就变得很慢了吗这个担心完全合理。真正的桌面工作也不可能每读一个文件就让人点一次确认否则人类很快只剩下无脑点允许。所以 Wordless 不是所有操作都一刀切地拦住而是把访问级别、工具类型、命令规则、文件规则和风险等级组合起来。普通的工作区内读取可以继续走越过工作区边界、命中安全规则或者产生高风险副作用的动作再回来找人。它在自动化和可控之间留了一条缝。这条缝很重要。因为一个真正好用的 Agent不是完全不问你也不是每一步都问你而是知道什么事情必须问。上下文不是越多越好上下文也是同一个问题。很多产品为了让模型看得更多转头就把整个项目、整个会话、整个文件夹一股脑塞进去然后再祈祷模型能自己找到重点。Wordless 的思路更接近精确引用。Composer 里的可以引用工作区文件和文件夹$可以选 SkillPPT 可以引用具体页面或对象Spreadsheet 可以引用当前选区产物引用里还会带上 revision、surfaceId 和 locator。这个设计对技术人来说特别熟悉。它不是让模型拥有一个模糊的「你自己看着办」而是尽可能把用户的意图编译成结构化上下文。比如一个表格用户说请把这个区域的公式改成按月汇总。系统真正传给 Agent 的不应该只是这句话还应该包含当前工作簿、工作表、精确选区和操作意图。否则模型很容易把「这个区域」理解成整张表然后顺手把旁边的数据也一起动了。上下文越精确模型需要猜的东西越少。猜得越少工具调用越稳定Token 也更容易花在真正的工作上。不同任务不该共用一个万能 Agent这也是为什么 Wordless 没有把所有场景都做成一个万能 Agent。它有 General、Coding、Presentation、Spreadsheet 和 Data Analysis 这些 Profile。Profile 不是欢迎语而是工作边界Profile 不是换一个欢迎语也不是在系统提示词里加几个标签。它会明确改变上下文、工具、权限声明、执行指导、产物类型和验证界面。Coding 需要索引搜索、文件编辑、Shell、测试和 Diff。Presentation 需要 OfficeCLI、质量扫描和可交互的 PPTX 预览。Spreadsheet 需要工作簿选区、公式、图表、质量检查和发布流程。Data Analysis 则需要按维度拆分研究、并行委派、追踪状态、保留来源和生成报告。这些任务当然都可以被一个大模型用文字回答。但如果你真的要把工作交付出去它们需要的工具和验收方式完全不一样。一个能写出代码的 Agent不一定知道一张 PPT 的对齐问题也不应该凭空拥有发布数据报告的权限。所以所谓多场景 Agent不是复制五份聊天窗口。而是在同一套运行内核上装配五套更贴近任务的工作边界。把底层模型和产品边界拆开这里又会回到 Wordless 的一个技术取舍。它没有重新发明一个通用模型循环而是使用 MIT License 的 Pi Agent Harness 作为 Agent 底座把自己的能力放在适配层上。wordless/ai隔离 Provider 协议和模型能力。wordless/agent负责 loop 和事件类型。agent-driver-sdk定义 Runtime 和具体内核之间的合同。Coding Driver 只需要声明自己支持 steer、follow-up、thinking、compact、branch、commands、artifacts、approval 和 user-request然后组装 Headless Coding Tools再接入工作区预检策略。这件事的价值在于底层内核可以替换桌面界面的会话、审批、存储和产物体验不必跟着一起重写。很多 Agent 项目早期都喜欢把所有东西揉成一个大文件。模型调用在里面文件操作在里面权限判断也在里面连 UI 都被绑到某个上游事件长什么样。一开始确实快。等到要加第二个工作模式或者换一个模型 Provider或者把长任务搬到 Worker整个人就会被历史包袱按在地上摩擦。Wordless 现在的模块化单体路线至少把这些变化留在了边界之外。模块化单体听起来不像当下流行的微服务但桌面软件里它是很务实的选择。只要依赖方向清楚运行时不依赖 Electron协议不依赖具体 UI能力服务不反向依赖 Profile单进程也可以保持相当好的可替换性。事件协议决定任务能不能恢复再往下看事件协议这个项目更像一个认真做过长任务的人写出来的。Runtime 发出的每个事件信封里会带上protocolVersion、runtimeInstanceId、全局唯一的eventId、sessionId、可选的runId、单会话递增的sequence、时间戳和具体事件内容。这几个字段不是为了给类型定义凑数。它们分别回答了当前事件来自哪一版协议来自哪一次 Runtime 启动属于哪个会话和运行前后顺序是什么以及界面该把它还原成什么状态。如果 Renderer 发现事件序列出现缺口它会重新请求一份新快照。这就比「来一条消息就 setState 一下」靠谱多了。因为真实世界里消息可能被合并进程可能重启窗口可能晚一点才订阅长任务也可能同时有工具、审批、压缩和子任务在跑。相邻的文本和思考增量可以合并到下一帧再刷工具完成、审批、错误和运行完成则要立即刷新。该合并的合并该立刻落地的立刻落地而且不能把生命周期边界打乱。这就是工程和 Demo 的差别。Demo 只要看起来在动。工程要保证它动完以后状态还说得清楚。输出结束不等于会话空闲前面提到的run.completed和session.idle就是一个很好的例子。模型已经停止输出只能说明 Agent 这一次调用结束了。但持久化、重试、上下文压缩和订阅者处理可能还没结束。界面如果把这两个状态混在一起就会出现很经典的假完成按钮已经恢复可用历史还没写好用户下一条消息却已经进来了。这种问题平时很难在演示里看出来。一到网络抖动、机器休眠、长会话或者连续追问就会开始冒出来。历史、索引和凭据要各归其位持久化层也没有把所有东西压成一个 JSON 文件。Wordless 用 JSONL 保存追加式的 Agent 历史用 SQLite 保存可查询的应用元数据。JSONL 是会话历史的权威来源记录完整消息、工具结果、模型和思考深度变化、上下文压缩、分支、Profile 身份和稳定的自定义条目。SQLite 则负责设置、项目、可搜索的会话元数据、模型选择、凭据引用、产物索引和权限决定。写入顺序也有明确约束先追加并 flush JSONL再更新 SQLite 索引。启动时如果发现索引缺失或过期还会根据会话头部和稳定条目进行重建。这套设计的好处是搜索很快历史又不会因为一次索引写入失败就丢掉。凭据不能混进会话凭据更不能混在会话里。API Key 和 OAuth token 在 AI 层之外由凭据抽象管理Electron 的safeStorage负责加密明文密钥不进入 JSONL、日志、协议事件、Renderer 持久化和 SQLite 元数据。这部分没有什么花哨的模型能力。但技术人应该知道真正让一个 Agent 能进入日常工作流的往往就是这些不适合放在发布会上的细节。图片别把路线图写成现成功能当然Wordless 也不是把所有问题都解决了更不是装上以后就可以对着模型说一句「帮我把公司流程全自动化」然后躺平。模型请求仍然会发送到用户选择的 Provider发送范围取决于当前任务和显式引用的上下文。本地优先不等于所有数据永远不离开设备。长任务仍然可能失败模型仍然可能误解工具仍然可能返回异常自动审批也不应该被当成无风险模式。当前产品是桌面端模块化单体未来长时间运行或不稳定的能力可以迁移到 Worker但这不是说今天已经有一个独立 Worker 集群在背后默默扛着。这个边界说清楚反而比把路线图写成现成功能更让人放心。还有一个很容易被忽略的许可边界Wordless 自己使用的是 Source-Available License而不是 OSI 定义的开源许可证。个人、教育、研究、评估和非商业内部使用可以免费进行商业使用、收费服务、SaaS、转售和商业托管需要事先获得书面授权。Pi、OfficeCLI 和其他第三方项目继续遵循各自的许可证。这些信息看着有点扫兴。但技术产品最怕的就是只展示能让人兴奋的那一半。真正能长期使用的项目一定也愿意把限制、成本和边界写在明面上。给正在做 Agent 的人几个检查问题所以如果你自己正在做 Agent我觉得不妨把问题往前挪一点。别只问模型能不能多调用几个工具先问它请求的动作和真正的执行权限是不是分开的。别只问上下文窗口有多大先问用户能不能精确指出当前任务需要哪几个文件、哪一页幻灯片、哪一块表格。别只问流式输出看起来顺不顺先问事件丢了一条以后界面能不能从快照恢复。别只问任务什么时候显示完成先把输出结束、持久化收束和会话空闲拆成不同状态。别只问数据库能不能存下聊天记录先问历史、索引、凭据和产物是不是各自有清楚的归属。这些问题才是 Agent 产品走出 Demo 的分水岭。把话说完不等于把工作做完过去的软件工程喜欢谈 API、状态机、事务和权限。现在我们把模型接进来以后这些词没有消失只是换了一个更容易失控的入口。模型可以帮你提出动作但它不应该自动继承动作的权力。模型可以帮你理解上下文但它不应该替用户决定上下文的边界。模型可以把一段文字生成出来但系统要负责确认这段文字有没有变成真正可编辑、可验证、可恢复的产物。从这个角度看Agent 工作台更像一个有审批、有 journal、有回滚依据的本地操作系统而不是一个更会聊天的输入框。我很喜欢 Wordless 的地方也就在这里。它没有把「少说废话」理解成让模型少输出几个字而是把过程播报交给界面把模型的输出留给决策、结果和异常。它也没有把「把工作做完」理解成模型回复了一句完成而是继续追问工具有没有执行文件有没有落盘事件有没有完整抵达结果能不能被人检查下一次还能不能从这里接着做。这才是我觉得 Agent 产品真正有意思的地方。模型能力的上限当然重要。但决定一个 Agent 能不能进入真实工作流的常常是那些模型之外的东西。权限边界事件存储恢复还有当系统不确定时愿不愿意停下来问你一句。以前我们担心软件不够聪明。现在更值得担心的是它已经足够聪明却还没有学会什么时候不能自作主张。所以下一次你再看到一个 Agent Demo不妨多问一句。它能不能写出代码只是第一关。写完以后谁批准它谁验证它谁保存它谁能在出错以后把它找回来这才是后面的世界。至少别让你的 Agent 只会把话说完。要让它把工作做完。以上既然看到这里了如果觉得这篇文章对你有点启发随手点个赞、在看、转发三连吧。如果你也在做 Agent欢迎从docs/architecture/overview.md、docs/architecture/runtime-protocol.md和packages/agent-workspace-policy/src/index.ts开始看很多真正有价值的答案都藏在这些不太起眼的边界里。谢谢你看我的文章我们下次再见。