07. Command

一个「/」背后:命令、产出契约与 Flow Design

上一篇留下两个预告:一是「斜杠命令的触发有被吞掉的风险」,二是「命令背后要想清楚的产出契约」。这一篇,就把这两个要点逐一拆开细说

👆你在这里
👆你在这里

🪧 基础概念:那个「/」,其实也没有那么玄乎

先祛个魅。刚开始接触 Claude Code 那一堆斜杠命令时,觉得还挺唬人的,数量众多且每个看起来都很专业的样子,用惯了各种工具,好像每一个 /xxx 背后是一套精密的命令解析引擎

但仔细研究后,才发现它一点也不复杂。一个斜杠命令,本质上就是「一句预先写好的话」。 你敲 /polish,跟你手动打一长串「请帮我润色这段,精炼用词、调整节奏、删掉冗余」,在系统眼里没有区别,都是发给模型的一段 prompt,只是提前替你写好、存成了一个快捷入口,省得每次重打

That's it。不过也正因为它「就是一段话」,那很自然也要考虑,这段话被谁接住、怎么接。看似简单,但实现的过程中也有一些小坑。我们先看 Claude Code 和 SDK 是怎么处理这段话的,然后再把定制时踩坑的经验慢慢聊

🔩 Claude Code:内置命令开箱,而「自定义命令」其实已经是 Skill

Claude Code 把命令分了两类

一类是内置命令,比如 /compact(压缩上下文)、/clear(清空)、/context(看当前上下文)、/usage(看用量)。这些开箱即用,CLI 自己能认得

另一类是自定义命令。它最早的格式是一个 markdown 文件,放在 .claude/commands/ 下,文件头上可以写些配置(比如允许用哪些工具、用哪个模型、参数提示),正文就是那段预置的话

💡 这套 .claude/commands/ 旧格式,官方已经标为过时,推荐统一改用 SKILL.md。 因为 SKILL.md 一份文件,就同时支持了「用户敲 /名字 显式唤起」和「模型按意图自主调用」两条路

这意味着在 SmartWriter 里,「命令」和「Skill」根本是一回事,我为写作场景自定义的那些 /polish/research,底层都是一个个 Skill。所以这一篇讲的命令,也完全可以理解成「上一篇那些 Skill 的另面:用户显式唤起它的那个入口」

🛠 SDK:命令就是发一个字符串,外加一个反直觉的事实

到了 SDK 这层,用命令的方式也很简单:你就把 /polish 这个字符串,当成一次普通的用户输入发过去。 官方文档有说明,斜杠命令就当普通文本,塞进 prompt 字符串里发出去即可。所以我在前端做了「Slash Command」菜单,选中命令点一下就往后端发一次 /polish,不需要再调用其它的命令 API

配置上,一个命令(Skill)的文件头里,可以指定它允许用哪些工具、用哪个模型、参数怎么填。这里有个挺实用的小设计:不同命令可以挂不同的模型。譬如「调研」这种要动脑子的挂个强模型,「润色」这种相对轻的则给个轻模型,兼顾成本和体验

命令还能带参数。文件头里能写个参数提示,正文里用占位符接住你跟在命令后面输入的内容,甚至能用 @ 直接引用某个文件。比如翻译命令,你敲 /translate 英译中,后面那个「英译中」就作为参数被接进去、填进那段预置的话里。所以一个命令不是「一句写死的话」,它可以是一句带了填空槽位、按你当次输入现填的话

以上听起来很完整,当我以为万事俱备、可以开干时,就碰到了第一个问题: 

SDK 的 Python 版本,根本不解析斜杠命令(这是审计 SDK 源码后的发现,官方文档没有显式说明)。发过去的 /polish,它不管三七二十一,原样透传给底层真正干活的 CLI,由 CLI 去决定这串东西是个命令、还是段普通文本。也就是说,「/polish 到底触没触发到我那本润色指南」,决定权在 CLI 手里,我的 Python 后端看不见、也管不着

那万一,CLI 没接住呢?

🧪 SmartWriter 踩的坑:/polish 被 CLI 悄悄「吞」掉了

还真就没接住。这也是前面提过的问题,自定义命令发出去,却被吞掉

🔧 避坑 · /polish 命令发出去了,然后什么都没发生

Debug 期间,写过一个 spike 测试:只发一个 /polish 过去,先看它到底能触发什么。结果是:token 花费为 0、内部一个 turn 都没跑、工具审计记录也是 0。也就是说,这条命令石沉大海,压根没进到模型的干活循环

然后继续深挖才发现,CLI 收到 /polish,把它当成一个内置斜杠命令去匹配。可「polish」并不在它的内置命令清单里(那份清单只有 /compact/clear 这些),匹配不上,CLI 就默默返回了空,既不报错、也不把它当普通文本丢给模型。它就这么被「吞」没了

再看另外一个测试对照:另一个命令 /co-author(带连字符的)反而没被吞。因为 CLI 一看这名字带连字符,压根不认为它是个合法的斜杠命令,于是干脆当普通文本,老老实实丢给了模型的干活循环。这有点黑色幽默了,长得「更像命令」的 /polish 被吞没,长得「不像命令」的 /co-author 反而活了下来

正解是不再把自定义命令交给 CLI 去判定。我在后端加了一道拦截:发给 CLI 之前,先把这几个模式命令的前缀(/polish 这类)自己剥掉,然后确定性地做两件事,加载对应的那本 Skill、把用户的意图作为普通文本送进去。这样,触没触发,应用侧说了算,不看 CLI 脸色

针对这个坑的解法,跟上一篇题材 Skill 的「确定性注入」其实很像:模型自主行为存在不确定性,对于关键路径/环节,应用侧要把主动权拿回自己手里。 这是本专栏里第三次出现类似的处理手法(前面是删 Bash、题材确定性注入),这也是做垂直产品的必经之路,始终对「模型不可控的因素」保持高度警惕,针对业务特性做确定性的兜底托举。不过话说回来,也不是所有命令都乖乖走「发字符串赌 CLI」这条路,/plan 就是个例外

📐 深挖 · 并非所有命令都走「透传给 CLI」这条路

上面讲的 /polish 被吞,容易给人一个印象:所有命令都是发个字符串给 CLI、看它接不接。/plan 命令就是个反例

/plan 我在后端做了应用层拦截:后端一检测到 /plan 前缀,直接把它拦下来,剥离前缀取出写作意图,然后单独跑一套「只探索、不动手」的计划流程。它压根没被透传给 CLI,自然也不存在「被吞」的问题

为什么独独 /plan 要这样特殊处理呢?因为计划这个动作,需要一种「只看不动」的权限约束(只准读不准写),而 SDK 通用的透传通路给不了这种约束。所以与其发出去赌 CLI 行为,不如应用侧先拦下来、自己控。这和前面关于确定性的解题思路是一脉相承的,不过具体的计划流程怎么设计、那套「只看不动」的权限怎么落地,是另一篇的大话题了,后面会单独展开

第一个坑有点偏技术实现,一顿折腾好歹填完了。但你会发现,上面反复在说一件事:命令触发之后,「加载对应 Skill、送进用户意图」。可送进去之后呢?模型一通操作,它到底会不会乱改我辛辛苦苦写的那篇稿子? 这就是第二个要关注的坑,事关用户体验

🧭 SmartWriter 踩的坑:敲一个命令,它到底动不动我的作品

 事故现场是这样的,我在一篇写作时,本想发起research命令让它帮忙去搜集一些素材,它却过分积极,搜索完后自作主张地把新的素材给融合到文章里了。可这个时候我都还没想清楚文章该怎么改呢,它这么一动,我手上的草稿也被改乱了

这里其实涉及大 Flow Design的概念(这一篇只讲跟命令有关的那一小块,完整的留到第 13 篇),它的内核可以用一句话总结:一次写作任务,自始至终只围绕「一个作品」在打磨。 你的底稿、参考附件、slash 命令、随口的指令,全是输入;它们汇进来,最终都指向同一件事,把这一个作品建得更好、改得更顺

这个「只有一个作品」的设定,让一个问题变得无比关键:每一次交互,到底动不动这个作品? 对一个写作的人来说,这可能是他最在意、也最没安全感的一件事:我随口问一下,它会不会把我改了半天的稿子给覆盖了?

因此要给每一类命令,都制定出明确的产出契约:这次交互,产出是什么、动不动你的作品,事先都要说清楚。我把所有命令按这个契约分了 4 组:

分组 命令 产出契约(动不动你的作品)
工作流 /plan(先出大纲)、/co-author(从零协作成文)、自定义工作流 推进计划 / 流程,不直接改作品
写作模式 /polish /expand /restructure /reformat /translate 直接更新作品(润色、扩写、重组、调格式、翻译)
调研 / 质检 /research(补论据)、/fact_check(查证)、/final_check(终检) 产出报告 + 修改建议,不直接改作品;你点头了才动
上下文管理 /compact(整理对话) 只压缩历史,完全不碰作品

要给用户清晰的预期,这一次互动会不会改变他的作品。 「写作模式」这一组,动作意图明确(你说润色就是要改),那就痛快地直接改;「调研质检」这一组,产出的是判断和建议,不应该擅自落到稿子上,先给你看、你说改我才改。把这个边界做得足够稳定,用户用起来才会踏实

举例说明会更直观。同样丢给它一句「帮我看看这段」,落到不同分组,产出会有明显差异:想让它顺一顺文字,它按「写作模式」的默认,直接把顺过的版本落到作品上,你看到的是稿子变好了;可要是问的是「帮我查查这段里那个数据对不对」,它就该走「事实查证」,回你一份核查报告 + 建议,一个字都不动你的稿子,等你点头再改。同一句模糊的话,靠命令背后的产出契约,被导向至两种完全不同、却又合理的结果

Flow Design 还配了几个默认值来兜底,省得动不动就跳出来问用户:默认就是在打磨这一个作品、默认覆盖到最新版、非作品类的命令默认不碰稿子。只有当你的意图跟这些默认明显冲突时(比如你说「别写进作品,就在对话里给我看看」),它才停下来跟你确认。这套「默认先兜住、必要才追问」的思路,其实就是第 5 篇讲过的「Default-then-Clarify」思路,在命令层面的落地

⚖️ 垂直落点:通用的命令是「功能开关」,写作的命令是「产出契约」

两个坑都填完,回到那个贯穿全专栏的对照:

⚖️ 取舍现场 · 同样是斜杠命令,通用和垂直在想什么

通用 Agent 的命令 SmartWriter 的命令 为什么这么设计
命令是什么 一个功能开关:触发某个能力 一份产出契约:说清这次动不动你的作品 写作者最怕稿子被擅自改动,得给确定感
触发怎么保证 发出去,模型接住后判断 后端剥离前缀 + 确定性加载 Skill 实测「指望 CLI 接住」会被悄悄吞掉
组织逻辑 按功能罗列 按「产出契约」分 4 组 让用户按「会不会改我作品」来理解,而非按技术功能

如果按通用 Agent 的思路,命令是「我能干什么」,是一份功能清单。写作 Agent 的命令,我理解它是一份契约,说清楚我这么做会对你的作品产生什么后果。通用命令只是功能清单,这份契约却关乎用户的心理安全

斜杠命令这么个不起眼的小东西,往下挖居然有这么多讲究,一头连着「CLI 到底怎么解析这串字符」这种很底层的机制,一头连着「用户敢不敢放心用」这种很人性的东西。做独立产品好玩也累人的地方,大概就在这,你得不停在技术和产品的连接处进行探索与融合

驾驭这个板块理到这儿,Agent 的人设(System Prompt)、方法论(Skills)、带产出契约的命令(Command)算是都配齐了,它现在知道自己该怎么想、怎么做

但你注意到没有,这三样都还停留在「模型脑子里」的层面。真正要把想法落到一个真实的文件上、甚至发布到外部平台,靠的是另一套东西:工具。从下一篇起,我们进入新板块「与世界交互」,先聊聊我怎么给这位副驾配工具,以及关于确定性和模型思考之间的博弈。下一篇咱们继续聊