最近我写了一个小项目:harness-cli。一开始它只是个很朴素的想法:我想要的不是一个“能聊天的命令行”,而是一个真的能帮我做事的CLI。它不只是接一下模型接口,把回答打印出来,而是要能理解任务、拆步骤、调工具、执行动作,最后把结果交回来。于是,这个项目慢慢就长成了现在这个样子:有
最近我写了一个小项目:harness-cli。
一开始它只是个很朴素的想法:我想要的不是一个“能聊天的命令行”,而是一个 真的能帮我做事的 CLI。
它不只是接一下模型接口,把回答打印出来,而是要能理解任务、拆步骤、调工具、执行动作,最后把结果交回来。
于是,这个项目慢慢就长成了现在这个样子:
有 Planner / Executor 双层结构,有 Tool Registry,有 Skill System,有 Session Persistence,也有一个基于 Bubble Tea 的终端交互界面。
整个系统虽然不大,但已经不是一个简单的“聊天壳”了。
今天这篇文章,不讲模型有多强,也不讲 API 怎么接,而是想聊聊:
如果从零开始做一个 Harness CLI,我是怎么把它一步一步搭起来的。
很多 AI CLI 项目,第一版都差不多:
这样当然能跑,但问题也很明显:它只能“说”,不能“做”。
我写 harness-cli 的时候,一开始就不想把它做成一个聊天机器人。我更在意的是,用户丢过来一个任务之后,它能不能自己先想清楚要怎么做,再调用工具把事情推进下去。
所以在架构上,我没有走单 Agent 路线,而是拆成了两个角色:
这个拆分看起来多了一层,其实非常值。因为一旦把“思考”和“执行”分开,整个系统就清楚了很多。
模型先负责想,再负责干。 计划先被结构化,工具调用才会更稳。 而且 UI 也终于能告诉你:它现在是在规划,还是已经开始执行了。
做这类项目,最容易陷进去的误区就是:总觉得能力强不强,取决于模型。
但写到后面我越来越确定,真正决定上限的,其实是工具系统。
如果模型只是回复一段文本,那它永远只是个会说话的助手。只有当它能读文件、写文件、跑命令、执行代码,它才开始像一个真正能工作的 CLI。
所以我在这个项目里专门抽了一层统一的 Tool 接口,把工具注册、工具发现、工具执行都收拢到一套机制里。这样做最大的好处是,后面不管加多少工具,Executor 都不用改逻辑,只需要通过注册表调度就行。
目前这个项目里,比较核心的几类工具包括:
做到这里之后,整个 CLI 才终于有了“手和脚”。
这套架构不是为了好看,而是因为它真的解决问题。
用户一句话抛过来,很多时候并不是一个直接可执行的动作,而是一个目标。比如:
这类请求,如果直接扔给一个 Agent 去自由发挥,很容易出现两个问题:
第一,它想得不够清楚。 第二,它工具用得很乱。
所以我单独让 Planner 先把任务变成计划。步骤是什么,要不要调工具,先做什么后做什么,都先定下来。这样后面的执行器就轻松很多。
有了计划之后,Executor 就很纯粹了:进入工具调用循环,执行动作,把结果回填上下文,继续下一步,直到整个任务结束。
这套东西听起来有点像“把 Agent 做重了”,但实际写完之后最大的感受是:整个系统终于稳定了。
你会发现,很多时候不是模型不行,而是没有一个清楚的执行框架去接住它。
这是我比较喜欢的一层。
单靠工具,其实只能解决“能做什么”;但很多任务不是缺动作,而是缺上下文、缺方法、缺套路。比如代码审查、调试、重构,这些事情并不是一个独立工具能解决的,它更像一套工作方式。
所以我在项目里加了 Skill System。
Skill 本质上是一层可插拔的“能力上下文”。它可以被发现、被激活、被注入系统提示中,让模型在执行某类任务时,自动带着那套工作习惯和判断方式去做事。
现在这套 skill 机制已经把发现、安装、激活、切换这些链路串起来了。它还在继续长,但方向已经比较明确了:
Tool 是动作,Skill 是方法。
这两层分开之后,整个 CLI 的味道就不一样了。
如果一个 CLI 每轮都失忆,那基本只能拿来玩。
所以我后来把两层东西补上了:
这一步做完以后,整个工具的感觉立刻从 demo 变成了“能连续使用的系统”。
你不用每次都重复说:
你也不用担心终端一关,前面的上下文全没了。
对命令行工具来说,这种连续性特别重要。因为很多真实任务本来就不是一轮完成的,而是反复试、反复修、逐步推进的。
很多人会觉得,终端 UI 不重要,能跑就行。
但 Agent CLI 不一样。 它不是一个单纯输出文本的程序,它是一个持续发生状态变化的系统。你得让用户知道:
所以我最后用 Bubble Tea 把整个交互层补了起来。不是为了好看,而是为了让这个系统更可理解。
做完之后最明显的感受就是: 同样一套 Agent 逻辑,有没有 UI 状态表达,使用体验完全是两回事。
做这种项目,最重要的从来不是“接了哪个模型”,而是:
你有没有把它做成一个真正能执行任务的系统。
模型当然重要,但它只是其中一层。 真正决定这个 CLI 有没有价值的,是这些更底层的东西:
harness-cli 对我来说,最有意义的地方不是“又做了一个 AI 工具”,而是我终于把这些东西在一个小项目里完整串起来了。
老实说,这次写完还有一个很强烈的感受:
我已经很久没认真写 Go 了。
这次相当于重新捡起来,完整做了一遍。但写着写着,还是会越来越明显地感觉到:很多地方不是不能写,而是总觉得不够顺,不够舒服。
尤其是当项目开始往更复杂的 Agent Runtime、事件流、插件系统这些方向走的时候,这种感觉会更明显。
所以后面我大概率会基于这次的实践,再用 Rust 重新设计一套。
不是因为 Go 做不到。 只是现在的我,真的越来越看它不顺眼了。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!