恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

自己动手写Agent Harness【cmd-builtin】:实现cmd 与内置命令

  • 首页
  • 资讯中心
  • /
  • 自己动手写Agent Harness【cmd-builtin】:实现cmd 与内置命令

相关资讯

代码报错怎么办?正确使用 AI 排查错误 2026/8/24 15:02:48
自动驾驶人才能力画像评估|全网独家复现 复合型落地人才适配量产交付、破解行业人员优化困境、实现团队人效精准量化、助力智驾项目高效迭代 2026/8/24 15:02:48
音频处理实战|EQ均衡器调整为什么会失真?频段划分、Q值与增益结构的四个常见误区 2026/8/24 15:02:48

最新资讯

从单一AI编程助手到系统化工作流:构建高效开发自动化流程
向量分析与张量入门:从梯度、散度到应力张量的工程实践
恶霸鲁尼崩溃修复:3 步装完,启动闪退、卡死、内存暴涨一次说清
数据结构--栈和队列
本地部署AI图生视频工具:从静态图片生成动态短视频的完整实践指南
ESP32局域网实时音频流硬件链路搭建与四大经典坑位解析

今日推荐

OpenModScan:免费跨平台 Modbus 主站调试工具,让现场通讯验证一键搞定
WechatHook 终极指南:5大核心能力详解,3分钟看懂微信自动化
如何在ThinkPad X390上安装macOS:OpenCore EFI完整指南

本周热门

Nextcloud 桌面客户端:把同步交给它,你只管改文件
如何将 HTML 转成 Word 文档且格式不丢失?html-to-docx 使用教程
Anki 批量操作卡片完整指南:一次搞定上千张,不再逐张修改

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

自己动手写Agent Harness【cmd-builtin】:实现cmd 与内置命令

发布时间:2026/8/24 15:07:48
自己动手写Agent Harness【cmd-builtin】:实现cmd 与内置命令 写在前面系列是为了帮助大家更好的去理解Agent Harness基础设施并不是想重复造轮子真实开发建议选择一个成熟的SDK或Harness框架才是最合适的选择~1. loop 里缺一个方向盘之前写的《自己动手实现一个 agent生命周期钩子》给 harness 装上了强制底线。到目前你的 harness 会派活子代理、会积累技能和规则、有强制底线钩子能拦该拦的最后缺的是人的指挥入口。loop 转起来之后你怎么指挥它现在的答案只有一个发消息。想换模型只能重启改环境变量想清掉刚才的对话只能重新开进程想看看它现在能做什么没有入口。这些动作不是「跟模型对话」是「指挥 harness 本身」。cmd 是人在 loop 内的干预入口。loop 不是封闭的黑盒。人除了喂消息给模型还要能指挥 harness 本身——把「常问的」「常改的」「常控的」做成命令常问的我现在能用什么/help、现在用的哪个模型/model常改的换个模型/model、重置记忆/clear常控的停/exit四个命令撑起一个可用的命令层。这就是本篇要写的给 harness 装一个方向盘。2. 先看 Claude Code 怎么做点到为止命令以 / 开头在发给模型之前被本地拦下处理不进模型上下文。slash 命令在 REPL 里以 / 开头输入官方把它定位成「在会话内控制 CLI」的快捷方式——它拦截在本地不是发给模型的对话内容。我们自建 harness 的 / 前缀分界线出处就在这。官方命令按用途分几类点到为止用途命令举例干的事帮助/help列出所有可用命令、快捷键与提示模型/model /effort换模型、调推理力度上下文/context /compact /clear /rewind看占用、压缩、清空、回退运行/loop /goal /batch定时循环、可测终态、批量并行配置/config /permissions /hooks /memory配置面板、权限、钩子、记忆这一长串对应的是产品在真实场景里的各种需求——并行工作、定时任务、云端同步。自建 harness 用不上那么多。命令系统这一层要搭的是「命令怎么注册、怎么路由」至于命令本身够用即止。所以我只做四个对应人操作 harness 的最高频四件事看帮助、换模型、清记忆、退出。贵精不贵多——命令表是「人在 loop 内的干预入口」每多一个命令就多一份入口面够用即止。3. Demo 先行命令注册表 路由分发代码来自配套工程examples/first-agent/step8-cmd/index.js零依赖、无 API key。先给代码再跑通看真实输出。3.1 命令注册表命令系统的第一块是注册表——登记「有哪些命令、怎么调」。核心就一句话注册即得命令。// 命令注册表 // 核心就一句话注册即得命令。要加新命令register 一个// { name, description, handler } 进去路由自动认它。// 真实工程命令表还缺别名与分类。真实工程应支持命令别名/quit /exit、// 分类分组对话 / 文件 / 上下文 / 运行帮助信息按分类展示// Claude Code 的命令全录就是带分组的。classCommandRegistry{constructor(){this.commandsnewMap()}register(cmd){this.commands.set(cmd.name,cmd);returncmd}get(name){returnthis.commands.get(name)}list(){return[...this.commands.values()]}}一个命令就三个字段name叫什么、description说明/help 靠它展示、handler干活的函数。注册进 Map路由就能认它。3.2 内置命令贵精不贵多四个handler(ctx, args)返回要展示的文本由路由统一加[cmd]前缀打印。ctx是会话上下文命令通过它读/改 harness 内部状态。// 内置命令贵精不贵多4 个// handler(ctx, args) - string返回要展示的文本由路由统一加 [cmd] 前缀打印。// ctx 是 Session会话上下文命令通过它读/改 harness 内部状态。// /help —— 第一个该做的命令它把剩下所有命令教给用户。// 新手不用背命令表/help 就是命令表在运行时的投影。constcmdHelp(ctx){constlistctx.commands.list()return可用命令list.length\nlist.map((c)${c.name.padEnd(7)}${c.description}).join(\n)}// /model —— 显示当前模型可切换mock/real。// 它是 harness 级操作直接改 loop 用的 provider。constcmdModel(ctx,args){consttargetargs[0]if(!target)return当前模型${ctx.model.describe()}if(targetmock||targetreal){if(ctx.model.set(target))return已切换模型 →${target}return无法切换${target}未设置 OPENAI_API_KEY维持${ctx.model.name}}return未知模型${target}。可用mock | real}// /clear —— 清空当前会话日志。// 直接操作 SessionLog换一个新日志loop 同步指向它。等于「重置记忆」。// 这证明 cmd 是 harness 级操作——普通对话做不到让 agent 失忆。constcmdClear(ctx){constoldCountctx.clearLog()return会话日志已清空原${oldCount}条事件 → 新建空日志}// /exit —— 退出程序。// 不直接调 process.exit把 running 置 false由主循环停。// 好处是资源能正常收尾日志落盘、连接关闭之类而且可测试。constcmdExit(ctx){ctx.runningfalsereturn退出。再见}// 真实工程内置命令写死在代码里。真实工程应支持自定义命令从配置文件挂载//Claude Code 的自定义 slash command 就是从配置文件 / skill 注册进来的——命令// 注册表天然支持外部 register把「读配置 → 逐个 register」接进 Session 即可。constBUILTIN_COMMANDS[{name:/help,description:列出所有可用命令与说明,handler:cmdHelp},{name:/model,description:显示当前模型可切换mock/real,handler:cmdModel},{name:/clear,description:清空当前会话日志,handler:cmdClear},{name:/exit,description:退出程序,handler:cmdExit},]3.3 路由分发/ 开头是命令不是对话注册表有了剩下是路由——决定一条输入走命令还是走 loop。// 路由分发cmd 与 agent loop 的分界线 // / 开头 → 命令表命中 → 执行 handler不进 loop// / 开头 → 命令表 miss → 提示 列出可用命令// 其它 → 进 loopasyncfunctionroute(input,ctx){consttextinput.trim()if(!text)return{kind:empty}console.log(\n[user]${text})// 所有输入统一由路由打印 [user]if(text.startsWith(/)){// 真实工程这里用空白切分取词/model real 只取 args[0] 够用但复杂参数会错//带引号的值、多段子参数、可选标志。真实工程应做完整参数解析// /model name [args] 的词法拆分 参数个数 / 取值校验 未知参数报错提示。const[name,...args]text.split(/\s/)constcmdctx.commands.get(name)if(!cmd){console.log([cmd] 未知命令${name}。可用命令${ctx.commands.list().map((c)c.name).join( )}输入 /help 查看说明。)return{kind:unknown,name}}constreplyawaitcmd.handler(ctx,args)if(reply)console.log([cmd] String(reply).split(\n).join(\n[cmd] ))return{kind:cmd,name}}// 不以 / 开头 → 这不是命令是给模型的对话 → 进 step4 的 turn 循环awaitctx.loop.turn(text)return{kind:turn}}3.4 跑通它配套工程克隆下来直接跑。下面输出逐字取自本机真实运行记录step8-cmd/PRACTICE.mdWindows 11 / Node v22.12.0 / mock 模型我落稿前又复跑核对过一遍$ node step8-cmd/index.js step8-cmdcmd 与内置命令 预置输入序列模拟一次交互式会话便于取证/ 开头进命令路由否则进 agent loop。 [user] /help [cmd] 可用命令4 [cmd] /help 列出所有可用命令与说明 [cmd] /model 显示当前模型可切换mock/real [cmd] /clear 清空当前会话日志 [cmd] /exit 退出程序 [user] /nope [cmd] 未知命令 /nope。可用命令/help /model /clear /exit输入 /help 查看说明。 [user] 你好我是来学 agent 的 [assistant] mock收到你好我是来学 agent 的 [user] /model [cmd] 当前模型mock确定性假模型无 key 可跑 [user] /model real [cmd] 无法切换 real未设置 OPENAI_API_KEY维持 mock [user] /model mock [cmd] 已切换模型 → mock [user] /clear [cmd] 会话日志已清空原 3 条事件 → 新建空日志 [user] /exit [cmd] 退出。再见 —— demo 结束 ——一条一条对。/help把四个命令列全——命令表在运行时的投影。/nope是未知命令提示 列出可用命令不崩。你好我是来学 agent 的没以 / 开头进了 loop回[assistant]——命令和对话在展示上分得清清楚楚。/model显示当前模型/model real没 key被优雅拒绝不抛异常、不碰网络/model mock走成功路径。/clear清空日志/exit退出。这里有个我在 Windows 上踩过的真实细节值得单独说。demo 要区分「直接运行」还是「被测试 import」我最初用process.argv[1] import.meta.url判断。Windows 上这俩永远不相等——argv[1]是相对路径step8-cmd/index.jsimport.meta.url是file:///D:/...绝对 URL。改成resolve(process.argv[1]) fileURLToPath(import.meta.url)才正确resolve按平台分隔符归一化成绝对路径fileURLToPath把 file:// URL 转成 Windows 盘符路径。这一坑和我在《动手开发你的第一个 agent 让它有记忆》里踩过的盘符重复坑同源——Windows 上 ESM 路径一律认fileURLToPath。4. 两个核心设计代码跑通了两个设计值得单独拆开讲。4.1 命令注册表注册即得命令命令系统的第一个设计是注册表。核心就一句话注册即得命令。要加新命令register 一个{ name, description, handler }进去路由自动认它/help 自动列出它——不需要改路由不需要改主循环。为什么三个字段就够name让路由能查description让用户能看/help 的每一行说明都来自它handler让命令能干活。handler(ctx, args)的签名里ctx是关键——命令不是孤立的字符串处理它拿到的是整个会话上下文命令注册表、模型、工具、日志、loop。所以命令能读/改 harness 内部状态这是「干预入口」的物理前提。这里单独立一条/help 是第一个该做的命令。它不干任何实事但它教会用户剩下的所有命令——新手不用背命令表/help 就是命令表在运行时的投影。有了它你加新命令不用写文档/help 自动展示。没有它命令再多用户也不知道有。4.2 路由分发/ 开头即命令不进 loop第二个设计是路由它是 cmd 与 agent loop 的分界线。规则就两条以 / 开头 → 查命令表命中执行 handler不进 loop不以 / 开头 → 进《动手开发你的第一个 agent最小的 agent loop》里那个ReactLoop.turn()正常对话为什么 / 开头就不进 loop因为「换模型」「清记忆」这类话如果被当成用户消息喂给模型模型会当作聊天话题来「回答」而不是当作 harness 控制指令来「执行」。/前缀是路由的分界线把「控制 harness」和「跟模型对话」两类输入从语法上分开。这是我在第 2 节核过的 Claude Code 行为——slash 命令被本地拦截模型看不到。复用的 loop 从哪来就是第一步那个 turn 循环它不关心输入怎么来只关心「boundary → 写用户消息 → step 请求模型 → 直到出文本」。唯一改动turn()里不再自己打印[user]统一由路由打印——命令和普通输入在展示上完全一致。测试能证明「命令不进 loop」。工程里step8-cmd/test.js用了一个「间谍 loop」替换真实 loop只计数turn被调用几次。// ① 以 / 开头的输入被路由到命令、不进 loop// 用一个「间谍 loop」替换真实 loop只计数 turn 被调用了几次。// 如果命令输入也触发了 turn计数就会 1测试就失败。asyncfunctiontestCommandRoutedNotToLoop(){constsnewSession()letturnCalls0s.loop{turn:async(){turnCalls},// 间谍 loop不真跑模型只计数}awaitcaptureLog(()route(/help,s))assert.equal(turnCalls,0,/help 是命令不应进 loop)awaitcaptureLog(()route(你好这是普通对话,s))assert.equal(turnCalls,1,不以 / 开头的输入应进 loop)awaitcaptureLog(()route(/clear,s))assert.equal(turnCalls,1,/clear 是命令不应进 loop)console.log(✓ ① 以 / 开头的输入被路由到命令、不进 loop)}/help、/clear触发 0 次 turn普通输入触发 1 次——命令确实没进 loop。真跑一遍五条全绿输出逐字取自 PRACTICE$ node step8-cmd/test.js ✓ ① 以 / 开头的输入被路由到命令、不进 loop ✓ ② /help 列出所有已注册命令 ✓ ③ 未知命令提示并列出可用命令 ✓ ④ /clear 清空会话日志原 6 条事件 → 0 条 ✓ ⑤ /model 显示当前模型mock 全部通过 ✅node step8-cmd/test.js 零依赖跑通把分界线画出来就是这张图空非空是命中未命中否用户输入trim 后空?empty 什么都不做以 / 开头?命令表命中?执行 handler 打印 [cmd] 结果不进 loop提示未知命令 列出可用命令进 step4 的 turn 循环[assistant] 正常对话/clear和/model是 harness 级操作的例子值得单独说。/clear直接操作会话日志换一个全新的空日志loop 同步指向它。clearLog()就几行// 清日志换新 SessionLog并让 loop 同步指向它否则 loop 还在用旧日志// 真实工程这里直接换新日志旧日志等同丢弃。真实工程清空策略应可配置// 归档旧日志文件加时间戳留存或确认后才清避免误清重要历史。clearLog(){constoldCountthis.log.events.lengththis.lognewSessionLog()this.loop.logthis.logreturnoldCount}两个引用必须同步换。只换session.log不改loop.logloop 继续往旧日志写清空形同虚设。这证明 cmd 是 harness 级操作——普通对话做不到让 agent 失忆命令可以。/model同理它直接改 loop 用的 provider把「启动时读一次环境变量」升级成「运行期可变状态」——换模型不用重启。/exit为什么不是process.exit直接调process.exit会立刻终止进程日志来不及落盘、连接来不及关也没法测试。改成把ctx.running置 false主循环if (!ctx.running) break停——资源能正常收尾而且route()返回后测试还能断言。demo 末尾能打出「—— demo 结束 ——」就是这条路走通了。命令机制这五处demo 都做了最省事的简化我逐个交代怎么省事、真实怎么补。配置持久化/model 切换是内存态、重启即还原真实工程落盘写配置、启动时读取恢复密钥走配置/环境变量统一管理。参数解析demo 空白切分取 args[0]带引号的值、多段子参数会错真实工程做词法拆分、参数个数/取值校验、未知参数报错。自定义命令demo 内置写死真实工程从配置文件挂载Claude Code 的自定义 slash command 就从配置/skill 注册注册表支持外部 register。清空策略/clear 直接换新日志丢弃真实工程可配置归档旧日志加时间戳留存或确认后才清。命令组织demo 命令表无分类无别名真实工程命令带别名/quit/exit、按对话/文件/上下文/运行分组帮助按分类展示Claude Code 命令全录就带分组。5. 对照 dsh命令层是 harness 的门面写到这里命令系统的两层都搭完了。对照 dsh我想把「命令层是 harness 的门面」这句话讲透。dsh 的「门面」长在启动期。我在《给 DeepSeek Harness 加一个自定义工具》里拆过两层boot profile--profile web只有web/headless决定「启动哪一棵插件树」和agent-presetstandard/code/minimal/cordis四份 YAML是「人格 工具组合」挂进 boot profile 的插件树。这两层都是启动时定下来的——选好壳、选好人格跑起来就不能改换 preset 得重启。我们的命令层是「运行期」的门面。loop 转起来之后你靠 / 命令指挥它/model 换模型/clear 清记忆/exit 停。一个是启动前选一个是运行中改。对照看运行期 · 我的命令层启动期 · dsh 的门面boot profileweb / headlessagent-presetstandard / code / minimal / cordis人格 工具组合启动即固定换 preset 要重启/model 运行中换模型/clear 运行中清记忆/exit 运行中停不用重启loop 内直接改这个对应关系落到最小实现上就是/model 是「换 preset 的人格」的运行期最小形态。dsh 换 preset 要重启启动期配置我们的 /model 一条命令运行中切运行期命令。能力上差着数量级——dsh 的 preset 决定一整套人格和工具组合我们只切 provider——但「门面」这件事是同一件都是「人怎么跟 harness 打交道」的界面。诚实边界交代一句dsh 部分我只引用自己拆解文里的结论boot profile 只有 web/headless、preset 是人格工具组合本地没有 dsh 源码不展开任何源码细节。顺带补一刀。我在《DeepSeek Harness 架构拆解》那篇点过它一个短板缺一个驻留终端的持续对话入口。headless 是一次性任务web 是浏览器 UI没有一个「你坐进去、敲命令、它回话」的终端界面。这一条回头看正好反衬命令层的分量——面向开发者的 harness上手手感很大程度取决于指挥它的入口长什么样。dsh 缺的那格你刚用几十行代码补上了。6. 结论给 harness 装方向盘cmd 就是那个方向盘。方向盘贵精不贵多四个旋钮够用/help 看路/model 换挡/clear 回空挡/exit 熄火。命令层不是 harness 的功能是 harness 的门面——用户不看源码看的就是命令。把「常问的、常改的、常控的」做成命令你就不必在启动前想清楚一切loop 跑起来随时能指挥。记住内置命令贵精不贵多但 /help 一定要第一个做。它教会用户剩下的所有命令。把这个系列的产出连起来看你的 harness 已经不再是「一个循环」——它能派活、能积累、有底线、能被指挥。还差的部分更完整的插件系统、多 Agent 编排、持久化加固后面空了再继续写~

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号