把「清理 C 盘」这种高危操作交给 Agent 执行,真正难的不是删文件,而是让它在删错之后还能把文件放回去。
写在前面
我日常用 AI Agent 处理重复工作:写代码、跑测试、整理文档、做演示。这类任务的共同点是失败可回滚 ------代码改错了 git checkout 就回来了。
但「清理电脑」不一样。它天然是不可逆、影响面极大、且用户预期极模糊的操作:用户只说一句「C 盘满了」,背后可能是回收站、可能是缓存、可能是桌面堆了 800 个文件、也可能是某个进程吃满了内存。
所以我做了 pc-tuneup 这个 Skill,把「电脑体检调优」拆成五类可控动作,用一套阶段机 + 隔离区把它约束住。这篇把设计取舍和踩过的坑写清楚,如果你也在给 Agent 做「有副作用」的工具,应该能直接抄。
一、先想清楚:为什么不让 Agent 直接执行 Remove-Item
最直觉的做法是给 Agent 一段提示词:「帮我清理临时文件」。实际跑下来有四个必炸的点:
- 模型会编造结果。 没加
--yes的预演轮次,它能在总结里写「已清理 9.4 GB」。用户信了,实际一个字节没动。 - 模型会绕路。 某个文件移动失败(被进程占用),它会自动改成
Remove-Item -Force「换个方式再试一次」。这一步直接把「可还原」变成「不可还原」。 - 模型会顺手扩大范围。 让它清桌面,它可能把桌面文件夹也一起归类了;让它清缓存,它可能把
node_modules也算进「缓存」。 - 模型没有稳定的量词。 「清理成功」到底是 3 个文件还是 3GB,没有脚本产出,就只能靠它自己总结。
结论:提示词约束不住破坏性操作,只有代码能。
所以这个 Skill 的第一条设计原则是------Agent 只负责编排命令,不负责执行动作。所有删除、移动、关进程,必须经过 Python 脚本;脚本内部再统一走隔离区并写清单。
SKILL.md 里的铁律第一条写得很直白:
markdown
1. 先扫描后执行,禁止跳步:必须 init → scan → plan → apply,
apply 之前没有 plan 段直接被脚本拦下(退出码 1)。
2. 默认预演,落地必须显式确认:apply 不加 --yes 只预演。
3. 删除一律进隔离区:磁盘垃圾禁止直接 Remove-Item。
二、三个不变量
整个工具只围绕三句话设计:
能不动就不动 → 要动就搬走不删 → 搬走也要能搬回来。
对应到实现上是三个机制:
- 只读优先 :
scan阶段全部只读,一次取回回收站、磁盘垃圾、桌面、进程窗口、性能基线五类数据,不碰任何文件和进程。 - 默认预演 :
apply不带--yes只打印「将要做什么」,preview 与 execute 走的是同一份动作清单。 - 可还原 :所有磁盘垃圾移入隔离区并写
manifest,桌面文件逐条记录原路径,性能优化记录原电源计划 GUID / 原VisualFXSetting。
顺便明确了不可逆动作的清单,只有两个:
| 动作 | 可逆 | 恢复方式 |
|---|---|---|
| 磁盘垃圾清理 | 是 | restore --batch <批次> --yes |
| 桌面整理 | 是 | restore --batch <批次> --module desktop --yes |
| 性能优化 | 是 | 记录原值,回执中给出 |
| 关闭进程 | 否 | 用户自行重开 |
| 清空回收站 | 否 | 无,执行前必须提示 |
可逆性是分级告知的,不是笼统说一句「放心,可以恢复」。用户看到「回收站不可恢复」和「垃圾可还原」,才会做出正确的勾选决策。
三、架构:一个编排器 + 五个阶段 + 一份计划文件
目录结构很克制,没有框架、没有第三方依赖(Python 标准库 + PowerShell 5.1):
bash
pc-tuneup/
├── SKILL.md 定位 / 铁律 / 动作口径 / 流程 / 回执
├── commands/protocol.md ⓪~⑤ 分步命令协议(每阶段一条命令)
├── rules/never.md 强制脚本化禁止项
├── rules/safety.md 安全边界(保护路径 / 保护进程 / 可逆性)
├── rules/evolution.md 经验沉淀与隔离区生命周期
├── references/targets.md 清理目标白名单与判定口径
├── config/targets.json 清理目标 / 保护名单 / 桌面分类(唯一事实来源)
├── scripts/
│ ├── pc_flow.py 编排器(阶段子命令,唯一入口)
│ ├── pc_scan.py 只读扫描
│ ├── pc_plan.py 计划读写 + 清单生成
│ ├── pc_apply.py 执行层(隔离区 / 桌面 / 进程 / 性能 / 还原)
│ ├── pc_lessons.py 经验台账
│ └── pc_common.py 路径常量 / PowerShell 封装 / 体量格式化
├── state/ runs.jsonl / lessons.jsonl / manifests/
└── .tmp-tune/ 本轮计划文件
阶段机不是流程图,是硬校验
csharp
init → scanned → planned → applied → finished
每个阶段只做一件事,中间产物全部落盘到 .tmp-tune/tuneup-plan.json。下一个阶段进来第一件事是校验上一阶段:
python
def cmd_plan(args):
data = open_plan(args)
if data.get('stage') not in ('scanned', 'planned', 'applied'):
fail('STAGE_ORDER', '当前阶段为 %s,请先执行 %s scan' % (data.get('stage'), CLI))
fail() 不是打印一句「请先扫描」就完事,它会把 {"ok": false, "code": "STAGE_ORDER"} 写到 stderr 最后一行,并以退出码 1 结束。
为什么要机器可读的错误码:Agent 判断「这步能不能继续」不能靠读自然语言输出,得靠退出码和 code。这样「跳步」在协议层就被堵死,而不是依赖模型自觉。
完整退出码只有三个值:
0正常(包含「没有可执行项」「批次不存在」这类无操作提示)1前置校验失败 / 参数非法2argparse 参数解析错误
为什么用单文件计划而不是数据库
计划文件承载了一轮体检的全部上下文:环境基线、扫描结果、动作清单、执行结果、还原值。它有三个好处:
- 断线可恢复 :
pc_flow.py status随时告诉你当前阶段和下一步命令。 - 可审计:一轮跑完,文件里就是完整的决策链路。
- 可重跑 :
init --force覆盖旧计划即可开始新一轮。
对应代价是「并发不安全」,但本机单人使用场景下这个取舍很划算。
四、真正花时间的工程细节
4.1 一次 PowerShell 调用取回全部基线
这是性能上最关键的一条。PowerShell 冷启动在部分 Windows 环境里要 30~40 秒,如果按模块各拉一次(回收站一次、进程一次、桌面一次......),光进程启动就要三分钟。
所以 pc_scan.py 的设计是:把所有 block 拼成一个脚本,输出单个 JSON。
python
def build_script(config, modules):
blocks = []
if 'recycle' in modules: blocks.append(_recycle_block())
if 'junk' in modules: blocks.append(_junk_block(config))
if 'desktop' in modules: blocks.append(_desktop_block())
if 'process' in modules: blocks.append(_process_block())
if 'perf' in modules: blocks.append(_perf_block())
...
协议文档里专门写了一句提醒,防止后来人「优化」成多次调用:
本环境 PowerShell 启动较慢(首次可达 40 秒),单次调用是刻意设计,禁止拆成多次调用。
4.2 -EncodedCommand:中文路径与引号的地狱
Windows 清理绕不开中文路径(桌面、下载、方案 - 副本.docx)。如果把路径直接拼进 PowerShell 命令行,会同时踩两个坑:引号转义,以及 cp936 / UTF-8 编码不一致导致的乱码。
解法是不拼字符串,直接传 Base64 的 UTF-16LE:
python
def run_ps(script, timeout=300):
encoded = base64.b64encode((_PS_PRELUDE + script).encode('utf-16-le')).decode('ascii')
proc = subprocess.run(
[powershell_exe(), '-NoProfile', '-NonInteractive',
'-ExecutionPolicy', 'Bypass', '-EncodedCommand', encoded],
capture_output=True, timeout=timeout, creationflags=flags,
)
同时统一前置一段 prelude,一次性解决三件事:
python
_PS_PRELUDE = (
"[Console]::OutputEncoding=[System.Text.Encoding]::UTF8;" # 中文不乱码
"$ErrorActionPreference='SilentlyContinue';" # 不让单条错误中断整块
"$ProgressPreference='SilentlyContinue';\n" # 不输出进度流污染 stdout
)
还有一个不常见但真实存在的坑:不要按名字调用 powershell.exe 。部分环境 PATH 里带了非法前缀,直接调用会失败。所以按绝对路径解析,优先 System32\WindowsPowerShell\v1.0\powershell.exe,兜底 SysWOW64。
4.3 保护路径要包含「我自己」
静态保护名单好写:文档、桌面、下载、图片、系统目录、Program Files。
但最容易出事的场景是代码库 。用户让 Agent 清理 C 盘,Agent 恰好在一个 30GB 的 monorepo 里工作,node_modules、dist、.cache 全在清理半径内。
所以保护路径在运行期动态追加工作区本身及其父目录:
python
def protect_paths(config):
"""保护路径:配置项 + 本仓库及其父目录(避免把代码库当垃圾扫掉)"""
raw = list(config.get('protect', {}).get('paths', []))
raw.extend([PROJECT_ROOT, os.path.dirname(PROJECT_ROOT)])
return [os.path.normcase(os.path.expandvars(item)) for item in raw]
def is_protected(path, protected):
target = os.path.normcase(os.path.abspath(path))
return any(target == item or target.startswith(item + os.sep) for item in protected)
判定用「等于或位于其下」,而不是字符串模糊匹配,避免 C:\Users\me\Desktop-old 被 Desktop 误伤(这里靠 item + os.sep 保证边界)。
这条是运行期规则,不写进配置文件------因为它取决于 Agent 在哪跑,写死反而会失效。
4.4 隔离区 + manifest:后悔药怎么做的
隔离区不是「换个目录放垃圾」这么简单,它需要能双向还原。
- 位置优先
D:\$PC-Tuneup-Quarantine,无 D 盘回落C:根目录------刻意放在技能目录之外 ,否则clean时容易连隔离区一起删掉。 - 每次
apply落地生成一个批次号b-YYYYMMDD-HHMMSS,同批多个模块共用目录。 - 每个条目写进
state/manifests/<批次>.json:module/kind/source(原路径)/target(隔离区路径)。
还原就是反向 Move-Item,并且目标已存在时自动改名,绝不覆盖。
python
def unique_path(path):
# 同名冲突:xxx.txt → xxx(1).txt → xxx(2).txt
...
这里有个容易忽略的细节:清理时只移走目录内容,保留目录本身 。因为系统可能正持有该目录句柄(%TEMP%、C:\Windows\Temp 尤其常见),删目录会导致后续整块失败。
4.5 回收站必须分批删,不能一把清空
这是真实踩出来的坑,已经进了经验台账:
一次性清空回收站(Clear-RecycleBin)时上万文件同时删除,磁盘 IO 打满、资源管理器长时间无响应。 根因:回收站条目过万,系统一次性删除造成 IO 峰值。 做法:按批删除,默认 200 个文件/批、批间停顿 200ms。
脚本里对应 config/targets.json 的两个参数:
json
"recycle": { "chunkSize": 200, "pauseMs": 200 }
PowerShell 侧用切片循环 + Start-Sleep:
powershell
for ($i = 0; $i -lt $total; $i += $chunkSize) {
$last = [Math]::Min($i + $chunkSize - 1, $total - 1)
# 删除 $files[$i..$last]
$chunks++
Start-Sleep -Milliseconds $pauseMs
}
注意这里还加了下限保护 max(10, chunkSize),防止配置被误改成 1 导致删一万个文件要跑几小时。
回收站之所以是唯一允许「真删」的动作,是因为它本身就是「已删除」的二次确认,保留它没有意义;但正因不可恢复,回执里必须明确写「不可恢复」。
4.6 缓存类目标:180 天门槛与「跳过」语义
清理缓存最大的风险不是删不掉,而是删掉正在用的东西:pnpm store 删了要重装依赖,Chrome 缓存删了要重新加载资源,着色器缓存删了要重新编译。
所以缓存类目标(cache: true)的规则是:只处理 180 天以上未修改的条目,占用中或近期写入的跳过。
json
{ "key": "pnpm-store", "path": "%LOCALAPPDATA%\\pnpm\\store",
"risk": "medium", "enabled": true, "cache": true }
配套两个设计细节:
- 缓存类移动失败计入「跳过」,不算失败。因为「被占用」是预期内的正常现象,不该污染失败率。
- 非缓存类(崩溃转储、蓝屏转储、CBS 日志)不套门槛,按整目录清理------它们本身就是排障产物,留 180 天没意义。
扫描时还要回传 keptBytes(未达门槛的保留量),让用户知道「这一项看着大,但大部分不能动」。
enabled: false 的条目连扫都不扫。prefetch 和 cbs-logs 默认就是关的:前者删了短期拖慢常用程序启动,后者排查系统更新问题要用。默认值就是产品决策,把「不推荐」做成默认关闭,比在文档里写十行警告更有效。
4.7 下载目录:同名归一才是有价值的那一半
清理下载目录如果只按「多少天没动」来,收获很有限。真正占地方的是同一份文件下载了五六次:
scss
季度报表.xlsx
季度报表 (1).xlsx
季度报表(2).xlsx
方案 - 副本.docx
方案 - 副本 (2).docx
方案 - Copy.docx
所以做了两类动作:
download:stale:修改时间早于staleDays(默认 30 天)download:duplicate:归一化后同名多于 1 份时,除最新一份外全部处理
归一只剥离系统自动生成的后缀,且可叠加:
报表 (1).xlsx/报表(2).xlsx→报表.xlsx方案 - 副本.docx/方案 - 副本 (2).docx→方案.docx方案 - Copy.docx→方案.docx
关键是不做过度归并 :只剥括号数字与副本标记,report2026.xlsx 和 report2027.xlsx 不算同名。保留优先级是「修改时间最新 → 时间相同取名字最短的那份」。
这条也进了台账,因为最初的逻辑只按目录白名单走,下载目录完全在范围外。
4.8 桌面整理:只动文件,且只动这几类
桌面是用户心智最敏感的区域,所以规则收得很紧:
- 只扫桌面根目录下的文件,不递归、不动文件夹
- 跳过
desktop.ini、隐藏文件、系统文件 - 跳过
.lnk/.url/.ini/.webloc------快捷方式是高频入口,动了会挨骂 - 按扩展名归入文档 / 图片 / 视频 / 音频 / 压缩包 / 安装包 / 其他
- 同名冲突自动加后缀,不覆盖
- 一个
desktop:group动作覆盖全部分类,清单里只出现一条
这里有个概念要澄清:桌面目录本身在保护名单里,但「桌面整理」是在桌面内部 把文件移入子目录,不跨盘、不删除,所以是允许动作。桌面文件只有 desktop 模块能碰,其他模块一律不越界。
4.9 关闭进程:两级安全阀
进程模块风险最高(可能丢掉未保存的数据),所以设了两道阀:
第一道:默认不勾选。 清单里进程类一律 enabled=false,必须显式指定 PID:
bash
pc_flow.py apply --module process --ids process:12345 --yes
第二道:先优雅后强制。 默认只调 CloseMainWindow()(等同于用户点窗口右上角 ×),有未保存内容时进程会自己弹窗拒绝。结果分四种状态:
| 状态 | 含义 |
|---|---|
closed |
已正常关闭 |
gone |
进程已不存在 |
waiting |
窗口没关掉,可能在等保存确认 |
nowindow |
该进程实际没有可关窗口 |
看到 waiting 时不允许自动升级为 --force ,必须回报用户确认。这条规则写在 rules/safety.md 里,因为模型最容易在这里「自作聪明」。
候选筛选也刻意保守:必须有主窗口,或内存 ≥ 200MB,且不在保护名单。无主窗口且内存 < 200MB 的一律视为后台服务,只计数不参与候选------否则清单里会冒出几百个 RuntimeBroker 之类的系统组件,用户根本没法判断。
保护名单除了系统关键进程,还包含当前工具链:
powershell / pwsh / python / cmd / windowsterminal
理由很简单:Agent 自己是跑在 Python 进程里的,把父进程杀了,整个流程当场断掉。
4.10 性能优化:所有动作都要记原值
三个动作,都只动当前用户级设置:
| 动作 | 实现 | 记录原值 |
|---|---|---|
| 电源计划切高性能 | powercfg /setactive <GUID> |
当前方案 GUID |
| 刷新 DNS 缓存 | ipconfig /flushdns |
不需要 |
| 视觉特效调最佳性能 | VisualFXSetting = 2 |
原值 |
两个细节:
- 已是高性能时不生成该动作 ,清单里直接没有
perf:power-plan,避免制造「零收益动作」。 visual-effects默认不勾选,因为它会牺牲动画与字体平滑的观感------这是审美偏好,不该由工具替用户决定。
五、无人值守:给自动化任务写「授权清单」
Skill 落地后,我把它挂到了每日自动化任务上。这里的关键是把交互式确认转化成书面的、逐模块的预授权。
下面是我实际使用的任务提示词(维护在自动化任务里,不是 Skill 文档):
text
使用 pc-tuneup 技能执行一次 Windows 电脑体检调优,严格按
init → scan → plan → apply → receipt → clean 顺序推进,禁止跳步。
本轮为无人值守执行,以下授权视为用户已提前确认:
1. 磁盘垃圾:apply --module junk --yes
2. 桌面整理:apply --module desktop --yes
3. 性能优化:apply --module perf --yes
4. 回收站:本轮不处理,不执行任何 recycle 动作
5. 进程与窗口:只扫描出清单,禁止关闭任何进程
执行要求:
- 开始先跑 pc_flow.py init,并按回显的高频经验执行。
- 所有删除和移动必须走隔离区,禁止直接 Remove-Item、del、rd。
- 回执必须用 pc_flow.py receipt 渲染,列表形式,禁止表格、禁止手工拼装。
- 任一步失败保留 PowerShell 原文并标 ⚠️,禁止伪造「已清理」。
- 若 C 盘可用空间低于 10%,在回执开头单独标出。
- 结束执行 clean,保留隔离区与历史台账,不要自动清空隔离区。
输出内容:完整回执 + 隔离区批次目录及还原命令。
这份提示词有四层结构,值得单独说明:
- 顺序:把六个阶段显式列出,配合脚本的阶段校验,形成双保险。
- 逐模块授权 :不是笼统说「你可以清理」,而是精确到
--module junk --yes。回收站显式排除,进程显式禁止,授权边界比授权本身更重要。 - 禁止项复述 :走隔离区、禁止手工拼回执、禁止伪造结果。这三条在
rules/never.md里也有,但提示词里再写一遍------因为它们是最容易被模型「合理绕过」的地方。 - 输出契约:回执 + 批次目录 + 还原命令。用户拿到的不只是「已清理 6.3GB」,而是「这 6.3GB 在哪、怎么拿回来」。
还有一个细节:C 盘可用空间低于 10% 时在回执开头单独标出。这不是清理能力,而是风险提示------空间极度紧张时,连隔离区本身都可能写不下,得先提醒用户。
六、回执:为什么强制列表,禁止表格
回执设计上做了一件反直觉的事:禁止表格,一律列表。
原因有三个:
- 终端宽度不可控:表格在窄终端会被折行成不可读的碎片。
- Markdown 表格在聊天窗口经常渲染失败,尤其是 Agent 把回执转述给用户时。
- 列表对「逐条失败」更友好:一项失败要带 PowerShell 原文关键句,表格单元格塞不下。
回执固定包含这些段:
- 环境:系统版本 / 是否管理员 / 内存总量与可用量
- 磁盘:各盘可用 / 总量(百分比)
- 扫描:回收站 / 磁盘垃圾 / 桌面文件数 / 可关闭进程数
- 执行:预演或已执行、总项数、成功 / 失败数,逐条列出动作与结果
- 合计释放:本轮实际释放空间
- 隔离区:批次目录 + 还原命令
- 耗时:全流程耗时(含开始时间)
回执还有一个隐藏职责:首次渲染时自动写 state/runs.jsonl (时间 / 耗时 / 释放量 / 成功失败数 / 批次),并把阶段推进到 finished。重复渲染不会重复记账。
这样「这次到底清了多少」永远来自脚本,而不是模型的记忆。台账攒起来之后,还能看趋势:某段时间释放量突然变少,说明该清的已经清完了;突然变多,通常是某个应用在疯狂写缓存。
回执末尾固定追加一句:
是否需要我把隔离区积压的旧批次永久清掉
然后等用户答复 。隔离区默认保留 7 天,到期也不自动删,只在 init 时提示。
为什么不自动清理:隔离区是唯一的后悔药。自动清理等于把后悔药的有效期偷偷缩短,用户哪天想找回半年前误删的日志,发现早就没了------这种「工具自作主张」比多占几 GB 更伤信任。
七、经验台账:让 Skill 越跑越快
Skill 跑多了会发现同一个坑反复踩:某个目录总是占用失败、某类进程总是关不掉。这类信息如果只存在我的脑子里,换台机器、换个时间就丢了。
所以内置了一个轻量经验台账,四个命令:
bash
# 记录(同模块相似条目自动合并,hits 累加)
pc_flow.py lessons add --module junk \
--symptom "C:\Windows\Temp 部分文件移动失败" \
--cause "被其他进程占用" \
--fix "回执标注失败即可,不重试不改用删除"
# 查看(默认只看活跃,按命中次数排序)
pc_flow.py lessons list [--module junk] [--limit 20] [--all]
# 相似条目归一压缩
pc_flow.py lessons merge
# 标记失效(做法被取代)
pc_flow.py lessons retire --id l-20261031-102024
设计上有三个约束:
- 只记会重复踩的坑。一次性的偶发占用失败不入库,否则台账会被噪声淹没。字段约定是「现象(保留 PowerShell 原文关键句)+ 根因 + 下次怎么做」。
- 相似度 ≥ 0.75 自动合并 ,
hits累加,fix以最新写入为准;超过 200 条按命中次数与最近时间截断。台账不能无限膨胀。 init自动回显命中次数最多的 3 条,本次执行按经验走。这条很关键------经验如果不主动推到眼前,就等于没记。
更重要的是一条规定:规则层能解决的,必须回写规则,不能只记台账。
bash
二次以上重复出现 + 规则层能解决
→ 白名单类:改 config/targets.json
→ 判定逻辑类:改 scripts/pc_scan.py
→ 流程口径类:改 SKILL.md 铁律 / rules/safety.md
改完必须重跑 init → scan → plan 确认不报错
台账是补丁,规则才是根治。如果某个目录三个月来一直清理失败,正确的做法是把它从白名单里去掉(或降级为默认不勾选),而不是每轮在台账里累加一次 hits。
另外,全程不做任何埋点上报、无外发网络请求。这不是洁癖:一个能删文件、能关进程的工具,如果还在往外部发数据,任何安全评审都过不了。
八、踩坑清单
按台账原样摘几条,都是真实跑出来的:
1. 一次性清空回收站导致系统卡死
现象:上万文件同时删除,磁盘 IO 打满、资源管理器长时间无响应。 根因:回收站条目过万,系统一次性删除造成 IO 峰值。 做法:按批删除,200 个/批,批间停顿 200ms。
2. 缓存目录要么全清要么不碰
现象:缓存目录里堆积了半年前的文件,旧逻辑无法区分可重建缓存与有排障价值的数据。 做法:缓存类按
minAgeDays只清 180 天以上,占用中或近期写入的跳过;转储与日志类不套门槛。
3. 下载目录的重复副本长期堆积
现象:同一份文件下载多次,被系统命名为
xxx (1).xlsx/xxx - 副本.docx。 根因:清理逻辑只按目录白名单走,下载目录完全不在范围内,也没有同名归并能力。 做法:新增 downloads 规则,30 天以上 + 同名归一后只保留最新一份,全部走隔离区。
4. 中文路径与引号导致 PowerShell 报错
现象:路径含中文或空格时,拼接式命令随机失败或输出乱码。 做法:改用
-EncodedCommand+ UTF-16LE,并在 prelude 里统一设置输出编码。
5. 进程清单被系统组件淹没
现象:早期版本把
RuntimeBroker、SearchHost之类的后台组件全列进候选,用户无法判断。 做法:加「无主窗口且内存 < 200MB 直接排除」的兜底规则,候选按内存倒序取前 80。
九、复用到其他 Agent 工具的六条原则
如果你也要给 Agent 做「有副作用」的能力(部署、发版、批量改数据、动生产配置),这六条我觉得是通用的:
1. 提示词管不了破坏性操作,代码才能。 把「该不该做」交给模型,把「怎么做」锁进脚本。模型只能选择调用哪个子命令,不能自己拼删除命令。
2. 默认值就是安全策略。 预演是默认、--yes 才落地;进程默认不勾选;高风险缓存默认关闭。不要指望用户每次都在清单上做正确判断。
3. 每个不可逆动作必须有对应的「可逆替代」。 能搬走就别删,能记录原值就别覆盖。做不到可逆的(回收站、关进程),必须在执行前明确告知不可恢复。
4. 状态机 + 机器可读错误码。 阶段校验、退出码、{"ok": false, "code": "..."}。让「跳步」在协议层失败,而不是靠 Agent 自觉。
5. 结果必须由脚本产出,禁止模型总结数字。 回执、释放量、成功失败数,全部来自脚本。模型的角色是转述,不是统计。
6. 经验要能沉淀成规则。 每次踩坑记一条,重复两次就回写配置或代码。否则同一个坑会在每个新会话里重新踩一遍。
结语
这个 Skill 现在每天自动跑一次,流程是固定的:
csharp
init → scan → plan → apply(分模块授权)→ receipt → clean
用户拿到的是三段式结果:这轮做了什么、释放了多少、怎么反悔。
我越来越觉得,给 Agent 做工具的核心不是「让它能做更多」,而是让它做错的时候代价足够小。可预览、可回滚、可审计、可解释------这四点做到,才敢把「清理电脑」这种活真正交出去。
如果你的项目里也有那种「不敢让 AI 碰」的操作,不妨先别想怎么让它更聪明,先想清楚:如果它删错了,你怎么把东西拿回来。