自制零依赖 REPL:当 CLI 也需要会话感知
大多数 CLI 工具是无状态的——你输入一条命令,它执行,结束,下次再来就是全新开始。这对脚本化任务完全够用,但一旦你想在命令行里和 LLM 做「持续的、有上下文的」对话,无状态就成了一道坎。
我自己搭了一个轻量 REPL(取名 NanoClaw),核心约束只有一个:零外部运行时依赖。这个约束本身就是一个设计决策,逼着我把每个功能想清楚再动手。
为什么「零依赖」是第一原则
外部依赖不是免费的午餐。一个依赖背后是版本管理、安全审计、更新维护、跨环境兼容。工具类脚本尤其如此——你可能在 CI、远程服务器、同事的机器上运行它,每多一个依赖就多一处「我以为装了但没装」的风险。
零依赖迫使你只用标准库和已有的运行时能力。这听起来是限制,但其实是在把复杂度前置:每加一个功能,你要先想清楚「用内置能力能不能实现」,不能才考虑引入外部包——而大多数时候,内置能力都够了。
NanoClaw 的持久化就是个典型例子:我用 Markdown 文件作为数据库。会话记录、分支、历史全部是 .md 文件,没有 SQLite,没有 JSON 数据库,没有任何附加依赖。Markdown 是人可读的、Git 友好的、跨环境的。这个选择让整个工具成为一个单文件脚本,任何装了 Node.js 的机器直接能跑。
会话管理的核心:分支与压缩
做持续对话的 CLI,最容易被忽视的两个设计点是「分支」和「压缩」。
分支是给高风险操作的保险丝。在执行某个可能把状态搞乱的动作之前,先 /branch 一个快照,出错了可以回到分支点重来。这和 Git 分支的逻辑是一样的——不是说你真的会频繁回滚,而是有了这个能力之后,你会更大胆地做实验。「可回退性」改变了你的操作心理。
压缩是上下文的主动管理。对话越长,模型能「记住」的有效内容比例越低,而且 token 成本也在线性上涨。/compact 不是清除历史,而是把过去的交互蒸馏成摘要,保留语义精华、丢弃冗余细节。我的经验是在每个「阶段里程碑」之后主动压缩,而不是等到快撑满了再亡羊补牢。
等上下文快满了才压缩,往往已经损失了中间某段关键推理链。在每个自然里程碑后主动 /compact,是把「上下文作为稀缺资源管理」的具体落地。
动态加载与模型切换
NanoClaw 支持 /load 动态载入外部 skill 文件、/model 切换底层模型。这两个设计背后是同一个判断:工具的能力不应该在启动时就固化。
对话的前半段可能需要快速廉价的模型做探索;某个子任务可能需要更强的推理能力;特定领域的 skill 只有在用到时才有必要加载。把这些能力做成运行时可切换的,而不是启动参数写死,让同一个 REPL 会话能灵活应对不同阶段的需求。
这也是「会话感知」的体现:REPL 不是工具调用的管道,而是一个有状态的工作空间,你可以在其中动态调整环境。
什么时候自制,什么时候用现成的
自制工具的代价是开发和维护成本。这条路值不值,取决于两个问题:
-
现有工具能不能满足你的核心约束? 如果零依赖、Markdown 持久化、本地运行是硬要求,大多数现成的 AI CLI 框架都做不到,因为它们优先考虑功能完整性而不是依赖极简。
-
这个工具会不会被频繁使用和迭代? 一次性脚本不值得自制;但如果它是你每天工作流的一部分,投入在设计上的时间会快速回收。
NanoClaw 对我来说是后者。它的核心不复杂,但每个设计决策背后都有明确的「为什么」——这本身就是一种工程训练:在约束条件下把事情做对。
一句话心法
给自己造工具时,先把约束写清楚——约束不是限制,是逼你想清楚的强制手段。