你实际使用哪些 AI 代理命令?审视它们
自定义代理命令的堆积速度比您停用它们的速度更快。请从会话日志中统计实际使用情况,然后停用那些不再使用的命令,而不是删除它们。
你为编码代理编写的每个自定义命令,一开始都是个好主意。写了二十多个之后,那个文件夹就成了一个博物馆。我统计了数千份会话记录中的实际调用情况,发现我有一半的命令在两个月内一次都没有被调用过。清理工作很简单,但正确解读这些数字却并非易事。
命令不断累积,使用量却不然
代理命令是一套打包的指令集:一个包含定义文件的文件夹,当您键入斜杠名称时,代理会加载该文件。编写一个命令需要花费一个下午的时间。停用一个命令则毫无成本,所以没人会这么做。
这堆命令不仅仅是杂乱无章。在每个会话中,每个定义都会将其名称和描述添加到代理的上下文中,而拥挤的命名空间会导致模型选错工具。但真正的成本以另一种形式显现出来。我的 26 个命令并非 26 个相同的东西。它们是三种不同类型的对象,存放在一个扁平的文件夹中。
从会话日志而非内存中进行统计
智能体将对话记录写入磁盘,通常每个会话一个 JSON-per-line 文件。模型进行的每一次工具调用都以结构化块的形式记录在其中。该文件树就是一个使用情况数据库,只是没人想到要去查询。
命令的调用表现为一个工具使用块,其输入中包含命令名称。对整个文件树执行一次 grep,就能得出一个排名列表:
# adjust the JSON shape to match your agent's transcript format
rg -o -I '"name":"<ToolName>","input":\{"<field>":"([^"]+)"' -r '$1' \
--glob '*.jsonl' "$TRANSCRIPT_DIR" | sort | uniq -c | sort -rn
匹配代码块,而非裸露的名称。对命令字符串进行粗略的 grep 搜索,也会捕获到每次该名称仅在说明性文字中被提及的情况,这会最大程度地夸大不常用命令的使用频率。这与你想要的结果恰恰相反。
最重要的一个步骤是按时间来拆分计数。运行两次,一次针对所有内容,另一次针对过去 60 天内修改过的文件:
find "$TRANSCRIPT_DIR" -name '*.jsonl' -mtime -60 > recent.txt
rg -o -I '"name":"<ToolName>","input":\{"<field>":"([^"]+)"' -r '$1' $(cat recent.txt) \
| sort | uniq -c | sort -rn
累计总数会骗人。它们会将一个你在去年春天频繁使用、但在六月就弃用的命令,与一个你今天早上刚运行过的命令归为一类。在我的数据中,这两列在列表上大约有三分之一的内容不一致。有一个命令有数百次累计调用,但最近调用次数为零。另一个命令累计调用次数刚过十几次,却仍保持每周使用。
零调用并不总是意味着无用
我的三个命令显示调用次数接近于零,但它们却是整个集合中负载最重的几个文件。
它们是一个更庞大的管道命令的主体部分。该管道并不将它们作为工具来调用。它从磁盘读取它们的定义文件,并遵循其中的步骤。从日志来看,这三个命令看起来像是被废弃了。如果删除它们,管道就会在运行中中断。
这是一个普遍的陷阱。使用情况遥测只能看到你的埋点工具所知道的入口点。通过文件读取、shell 调用或文档引用进行的组合对它来说是不可见的。在你根据零调用采取行动之前,请在其余的工具中通过 grep 搜索文件路径,而不仅仅是命令名称:
# does anything else read the definition file directly?
rg -n '<commands-dir>/[a-z-]+/<definition-file>' .
那一个命令把一个删除列表变成了一个保留列表。对“去年有数百次直接调用,本季度为零”的正确解读并不是该命令已失效,而是有一个更新的流水线将其吸收为一个入口点,并且其底层步骤在每次调用父级时仍然会运行。
所以,那个扁平文件夹中的三类文件是:我直接调用的、流水线读取的,以及没人碰的。只有第三组是清理候选项。对于第二组,我更改了它们的描述,说明流水线是预期的入口点,并将文件保留在原位,因为流水线硬编码了这些路径。
通过深度停用,而非删除
对于那 13 个休眠的项,直接删除感觉不妥。它们功能正常,文档齐全。我可能六个月后又想用回其中一个。
大多数代理通过扫描一个深度恰好为一层的目录来发现命令:对每个子目录,检查是否存在定义文件。这里没有递归。与其相信文档,不如去阅读你自己代理的加载器 (loader)。在我检查的那个代理中,其行为是明确无误的。如果一个目录本身没有定义文件,那么它的子目录就绝不会被检查。
这就给了你一个现成的停用机制。将文件夹向深一层移动:
mkdir -p commands/_attic
git mv commands/old-command commands/_attic/old-command
现在,该定义位于两层深度之下。扫描器永远不会触及它。该命令会从菜单中消失,并停止占用上下文,而它的每一个字节都仍在磁盘上,仍在版本控制中。恢复只需一条 git mv 命令。
请注意此处真正起作用的是什么。不是文件夹名称中的下划线,也不是工具承诺会遵循的命名约定。而是深度的改变。在依赖此方法之前,请先验证你自己的代理的扫描行为,因为一个会递归地通配定义文件的加载器,会悄无声息地让每个归档的命令保持活动状态。有两位审阅者为我指出了这个确切的风险,只有在阅读了加载器的代码后,这个问题才得以解决。
阅读那段代码还有另一个发现。扫描器也接受符号链接作为命令。我曾用一个符号链接指向另一个命令文件夹作为语言别名,这意味着该命令在每个会话中都被注册了两次。别名已经由描述文本处理了。这个符号链接是纯粹的重复,在我阅读发现机制的工作原理之前,它一直是不可见的。
清理过程中出了什么问题
移动操作本身微不足道。问题出在文档上,同一个缺陷在不同文件中出现了三次。
行号会随着编辑而改变。 我的计划将编辑操作按从上到下的绝对行号范围列出。在第 496 行删除一行后,后面的每个范围都会偏移一位。在一个文件中,目标章节标题最终比计划开始的位置高了一行,因此删除操作只删除了正文,而将标题孤立地留在了水平分割线下方。然后,同样的错误在第二个文件中出现,接着又在第三个包含九个范围的文件中出现。修复方法很乏味但很绝对:从文件底部向上编辑,或者基于字符串而不是行号进行定位。
验证范围必须与编辑范围匹配。 我写了一个检查,用于确认整个仓库中没有过时的引用,但随后只列出了四个要编辑的文件。第五个文件里有一个引用。这个检查在我运行之前就注定会失败。如果你的验收标准涵盖了整个目录,那么该目录中的每个文件都在范围内,或者该标准需要一个明确的排除列表。
不要更新归档项内部的路径。 归档的定义包含它们自己的旧路径。将这些路径重写为新的归档位置看起来很整洁,但这是错误的:恢复文件夹时会将其移回原始路径,而重写后的引用将指向空无一物。归档内部看起来过时的路径,对于归档期望恢复到的状态来说,是正确的路径。
还有一个值得记录的决定。我曾计划从一个配置文件中删除整整两个部分,结果发现其中一个部分记录了仍在运行的基础设施。它提到的命令已经停用,但底层的钩子和脚本没有。删除这一部分会移除对正在运行的机器的唯一描述。我删掉了关于命令的两行,并保留了其余部分。
常见问题
需要多少次会话,这些数字才有意义? 足够多,以至于你最近的窗口包含了正常的工作组合。对我来说,每天使用两个月就足够了。如果你最近那一列的总调用次数少于几百次,应将较低的计数视为噪音而非证据。
我应该删除而不是归档吗? 如果仓库有历史记录,删除是可恢复的,并且能留下最干净的树。当你期望在不重新激活内容的情况下查阅它时,归档是更好的选择,这对于那些你可能仍会手动执行其步骤的命令来说是常见情况。两者都是可逆的。选择一种并保持一致。
如果一个命令已处于休眠状态,但文档仍然告诉人们使用它,该怎么办? 在同一次变更中修复文档,并检查整个仓库,而不仅仅是那个显而易见的文件。脚本、子项目中的 README 文件以及工作流指南都会累积引用。我的引用分散在五个文件中,其中一个文件告诉读者去调用一个我正要归档的命令。
精简列表真的能带来任何可衡量的改进吗? 上下文的节省是真实存在的,但很小。更大的影响在于选择的准确性:更少的近似重复的描述意味着更少的错误选择。我不会仅仅为了令牌数而这样做。我这样做是因为,在一个文件夹中,如果三种不同类型的对象看起来一模一样,那么这个文件夹最终会误导你。