把「清理 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 里的铁律第一条写得很直白:
1. 先扫描后执行,禁止跳步:必须 init → scan → plan → apply,
apply 之前没有 plan 段直接被脚本拦下(退出码 1)。
2. 默认预演,落地必须显式确认:apply 不加 --yes 只预演。
3. 删除一律进隔离区:磁盘垃圾禁止直接 Remove-Item。
二、三个不变量
整个工具只围绕三句话设计:
能不动就不动 → 要动就搬走不删 → 搬走也要能搬回来。
对应到实现上是三个机制:
- 只读优先:scan 阶段全部只读,一次取回回收站、磁盘垃圾、桌面、进程窗口、性能基线五类数据,不碰任何文件和进程。
- 默认预演:apply 不带 --yes 只打印「将要做什么」,preview 与 execute 走的是同一份动作清单。
- 可还原:所有磁盘垃圾移入隔离区并写 manifest,桌面文件逐条记录原路径,性能优化记录原电源计划 GUID / 原 VisualFXSetting。
顺便明确了不可逆动作的清单,只有两个:
可逆性是分级告知的,不是笼统说一句「放心,可以恢复」。用户看到「回收站不可恢复」和「垃圾可还原」,才会做出正确的勾选决策。
三、架构:一个编排器 + 五个阶段 + 一份计划文件
目录结构很克制,没有框架、没有第三方依赖(Python 标准库 + PowerShell 5.1):
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/ 本轮计划文件
阶段机不是流程图,是硬校验
init → scanned → planned → applied → finished
每个阶段只做一件事,中间产物全部落盘到 .tmp-tune/tuneup-plan.json。下一个阶段进来第一件事是校验上一阶段:
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 前置校验失败 / 参数非法
- 2 argparse 参数解析错误
为什么用单文件计划而不是数据库
计划文件承载了一轮体检的全部上下文:环境基线、扫描结果、动作清单、执行结果、还原值。它有三个好处:
- 断线可恢复:pc_flow.py status 随时告诉你当前阶段和下一步命令。
- 可审计:一轮跑完,文件里就是完整的决策链路。
- 可重跑:init --force 覆盖旧计划即可开始新一轮。
对应代价是「并发不安全」,但本机单人使用场景下这个取舍很划算。
四、真正花时间的工程细节
4.1 一次 PowerShell 调用取回全部基线
这是性能上最关键的一条。PowerShell 冷启动在部分 Windows 环境里要 30~40 秒,如果按模块各拉一次(回收站一次、进程一次、桌面一次……),光进程启动就要三分钟。
所以 pc_scan.py 的设计是:把所有 block 拼成一个脚本,输出单个 JSON。
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:
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,一次性解决三件事:
_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 全在清理半径内。
所以保护路径在运行期动态追加工作区本身及其父目录:
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,并且目标已存在时自动改名,绝不覆盖。
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 的两个参数:
"recycle": { "chunkSize": 200, "pauseMs": 200 }
PowerShell 侧用切片循环 + Start-Sleep:
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 天以上未修改的条目,占用中或近期写入的跳过。
{ "key": "pnpm-store", "path": "%LOCALAPPDATA%\\pnpm\\store",
"risk": "medium", "enabled": true, "cache": true }
配套两个设计细节:
- 缓存类移动失败计入「跳过」,不算失败。因为「被占用」是预期内的正常现象,不该污染失败率。
- 非缓存类(崩溃转储、蓝屏转储、CBS 日志)不套门槛,按整目录清理——它们本身就是排障产物,留 180 天没意义。
扫描时还要回传 keptBytes(未达门槛的保留量),让用户知道「这一项看着大,但大部分不能动」。
enabled: false 的条目连扫都不扫。prefetch 和 cbs-logs 默认就是关的:前者删了短期拖慢常用程序启动,后者排查系统更新问题要用。默认值就是产品决策,把「不推荐」做成默认关闭,比在文档里写十行警告更有效。
4.7 下载目录:同名归一才是有价值的那一半
清理下载目录如果只按「多少天没动」来,收获很有限。真正占地方的是同一份文件下载了五六次:
季度报表.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:
pc_flow.py apply --module process --ids process:12345 --yes
第二道:先优雅后强制。 默认只调 CloseMainWindow()(等同于用户点窗口右上角 ×),有未保存内容时进程会自己弹窗拒绝。结果分四种状态:
看到 waiting 时不允许自动升级为 --force,必须回报用户确认。这条规则写在 rules/safety.md 里,因为模型最容易在这里「自作聪明」。
候选筛选也刻意保守:必须有主窗口,或内存 ≥ 200MB,且不在保护名单。无主窗口且内存 < 200MB 的一律视为后台服务,只计数不参与候选——否则清单里会冒出几百个 RuntimeBroker 之类的系统组件,用户根本没法判断。
保护名单除了系统关键进程,还包含当前工具链:
powershell / pwsh / python / cmd / windowsterminal
理由很简单:Agent 自己是跑在 Python 进程里的,把父进程杀了,整个流程当场断掉。
4.10 性能优化:所有动作都要记原值
三个动作,都只动当前用户级设置:
两个细节:
- 已是高性能时不生成该动作,清单里直接没有 perf:power-plan,避免制造「零收益动作」。
- visual-effects 默认不勾选,因为它会牺牲动画与字体平滑的观感——这是审美偏好,不该由工具替用户决定。
五、无人值守:给自动化任务写「授权清单」
Skill 落地后,我把它挂到了每日自动化任务上。这里的关键是把交互式确认转化成书面的、逐模块的预授权。
下面是我实际使用的任务提示词(维护在自动化任务里,不是 Skill 文档):
使用 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 跑多了会发现同一个坑反复踩:某个目录总是占用失败、某类进程总是关不掉。这类信息如果只存在我的脑子里,换台机器、换个时间就丢了。
所以内置了一个轻量经验台账,四个命令:
# 记录(同模块相似条目自动合并,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 条,本次执行按经验走。这条很关键——经验如果不主动推到眼前,就等于没记。
更重要的是一条规定:规则层能解决的,必须回写规则,不能只记台账。
二次以上重复出现 + 规则层能解决
→ 白名单类:改 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 现在每天自动跑一次,流程是固定的:
init → scan → plan → apply(分模块授权)→ receipt → clean
用户拿到的是三段式结果:这轮做了什么、释放了多少、怎么反悔。
我越来越觉得,给 Agent 做工具的核心不是「让它能做更多」,而是让它做错的时候代价足够小。可预览
本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。
What's really hard is to leave the high-risk "cleaning C" operation to Agent, not to delete the file, but to put it back after it was deleted.
It's in the front.
I use AI Agent to process duplicated work: writing codes, running tests, sorting documents, making presentations. The common denominator of this kind of mission is failure to roll back -- the wrong code changed git checkout and came back.
But "cleaning up the computer" is different. It is a natural operation that is irreversible, highly influential, and the user's expectations are extremely vague: the user simply says "C discs are full" and that behind it may be a wastebin, possibly cache, maybe a desktop pile of 800 files, or a process full of memory.
So I made a pc-tuneup, this Skill, to tear down the computer medical examination into five types of controlled action, and to bind it with a set of stages plus quarantine. This is a piece that makes it clear that if you're also making "side effects" for Agent, you should be able to copy it directly.
First, think clearly: Why not let Agent directly implement Remove-Item
The most intuitive approach is to give Agent a prompt: "Help me clean up temporary file." In fact, there are four blast points:
- Models make up results. No-yes rehearsals, it says "94 GB cleared" in summary. User mail. The actual byte hasn't moved.
- Models will bypass. A file that fails to move (in the process) will automatically be replaced by Remove-Item-Force, "Try it again in another way." This step directly translates "reversible" into "non-recoverable".
- Models will be scaled up. Let it clear the desktop, it may also group the desktop folder; let it clear cache, it may include node modules in cache.
- The model has no stable word. Whether it's three files or three GBs, without script output, it can only be summed up.
Conclusion: prompt cannot restrain destructive operations, only code can.
So the first design principle of this Skyll is that Agent is only responsible for organizing orders and not for carrying out actions. All processes of deletion, movement and closure must pass through the Python script; within the script, the area of separation is then unified and lists are drawn up.
The first article of the Iron Law in SKILL.md is plain:
1. Scanning and execution, no jumping: must be done
Apply no plan section was previously stopped directly by script (exit code 1).
2. Default previews, landings must be clearly confirmed: apply not-yes only.
3. Delete all entries into the area of separation: Disk trash is prohibited directly Remove-Item.
II. Three non-variables
The whole tool is designed around three words:
You can't move, you can move, you can move, you can move, you can move back.
This corresponds to three mechanisms:
- Read-only priority: read-only at all stages of scan, take back the trash, disk, desktop, process window, performance baseline five categories at once, without touching any documents and processes.
- Default preview: Apply does not-yes only print "what to do," preview and execute go with the same list of actions.
- Revertable: All disk trash is moved into the isolation zone and write manual, desktop files record the original path by article, and optimize the recording of the original power supply plan GUID / the former VisaFXSetting.
By the way, the list of irreversible actions is only two:
Reversibility is provided by a hierarchy, not by a general phrase, “freedom, can be restored”. It is only when users see that the wastebin is not restored and the waste is restored that they make the right decision.
III. Structure: one organizer + five phases + one plan document
The catalogue structure is very restrained and has no framework and no third-party dependence (Python Standard Library + PowerShell 5.1):
pc-tuneup/
Ideas - SKILL.md Position / Iron/ Action caliber/ Process / Reply
Ideas -comands/protocol.md 0-5 step command protocol (one order per stage)
rules/never.md Force Scriptization Ban
Ideas-rules/safety.md secure border (protect path / protect process / reversibility)
Ideas-rules/violence.md Experienced sedimentation and isolation life cycle
Ideas-references/targets.md Clear the target white list and calibration
Ideas -config/targets.json Clear Target / Protect List / Desktop Classification (sole factual source)
├── scripts/
│ - pc flow.py organiser (stage command, only entrance)
Pc scan.py read-only scan
Pc plan.py plans to read and write + list generation
│ - pc apply.py Executive Level (Isolation / Desktop / Process / Performance / Restore)
Pc lessons.py Experiences
│ - pc common.py path constant / PowerShell encapsulation / mass formatting
├── state/ runs.jsonl / lessons.jsonl / manifests/
.tmp-tune/ Current round of plan documents
Stage machine is not a flowchart. It's a hard check.
init → scanned → planned → applied → finished
At each stage, only one thing is done, and all intermediates are set to.tmp-tune/tuneup-plan.json. The first step in the next phase is to verify the previous phase:
def cmd_plan(args):
data = open_plan(args)
if data.get('stage') not in ('scanned', 'planned', 'applied'):
Fail ( 'STAGE ORDER', 'Current stage is %s, please start with %s scan' % (data.get('stage'), CLI))
Fail() does not print a "scan first" line, it will write to the last line of STderr and end with exit code 1.
Why is the machine readable error code: Agent judge that "can this move continue" cannot be exported by reading natural languages, but by exit codes and code. So "jumping" is blocked at the protocol level instead of relying on model self-awareness.
Full exit code only has three values:
-0 Normal (includes no operational hint such as "no batch does not exist" for "no executable")
-1 prevalidation failed / invalid
- 2 aggparse parameter parsing error
Why do you plan to use single files instead of data? Library
The plan document contains all of the context: environmental baseline, scanning results, action lists, execution results, reduction values. It has three advantages:
- Breakline can be restored: pc Flow.py status always tells you at this stage and the next step command.
- Auditable: After a round, the document is a complete decision-making link.
- Rerun: init-force covers old plans to start a new round.
The price would be "co-insecurity," but it would be a cost-effective option to use this scenario.
Details of the project that really takes time
4.1 One PowerShell call back to all baselines
It's the most critical thing about performance. The PowerShell cold start takes 30 to 40 seconds in some Windows environments and three minutes for the light process to start once in each module (trash, process, desktop...).
So pc scan.py is designed to spell all blocks into a script and output individual JSON.
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())
...
The protocol document contains a reminder to prevent subsequent calls from being "optimised":
PowerShell is slow to start (for the first time up to 40 seconds) and single calls are deliberately designed to prohibit multiple calls.
4.2 - EncoedCommand: Chinese Path and Quote Hell
Windows clears a Chinese-language path (desktop, download, scheme - copy.docx). If the path is inserted directly into PowerShell command line, two pits will be stepped on at the same time: the citation transposition, and the uncoded cp936/UTF-8 code.
The solution is not a spell string.
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,
)
And one paragraph before the other, prelude, solves three things:
_PS_PRELUDE = (
"[Console]: :OutputEncoding=[System.text.Encoding]: UTF8; # Chinese
"$ErrorActionPreference='SilentlyContinue';"
"$Process='SilentlyContinue';\n"
)
There is also an unusual but real pit: do not call Powershell.exe by name. Part of the environment PATH contains an illegal prefix and calls directly fail. So solve it by absolute path, priority System32\WindowsPowerShell\v1.0\powershell.exe, under SysWOW64.
4.3. Protect the path by including "myself."
Static protection lists are good: documents, desktops, downloads, pictures, system directories, Program Files.
But the easiest scene is the code library. The user lets Agent clean the C disc, and Agent happens to work in a 30GB Monorepo, node modules, dist,.cache is all within the cleanup radius.
So the protected path is dynamically adding the workspace itself and its parent directory during the run-off:
def protect_paths(config):
"""Protected Path: Configure Item + This repository and its father's directory"
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)
Determines to match "equal to or below it" instead of a blurted string to avoid C:\Users\me\Desktop-old being accidentally injured by Desktop (here by its + ossep).
This is the run-time rule, not write in the configuration file -- because it depends where Agent runs, and it's dead.
4.4 Zone of separation + substance: how to use the regret medicine
The quarantine zone is not as simple as "a different directory of trash." It needs to be restored in both directions.
- Position priority D: $PC-Tuneup-Quarantine, no D drive-back C: Root directory - deliberately placed outside the skills directory, otherwise the clean can easily be deleted together.
- Apply each time to create a batch number b-YYYYMMDD-HHMMMSS, which shares a directory of multiple modules in the same batch.
- Write in each entry.json: mode / kind / source (original path) / target.
Revert is the reverse of Move-Item and automatically changes the name when the target exists, which is never covered.
def unique_path(path):
# Conflict with the same name: xx.txt→xx(1).txt→xx(2).txt
...
Here's an easy-to-neglect detail: remove only the contents of the directory while cleaning up, and keep the directory itself. Since the system may be holding the directory handle (%TEMP%, C:\Windows\Temp especially common), the deletion of the directory will result in the failure of the subsequent entire block.
4.5 The wastebin must be phased out and not emptied.
It's a real pit, and it's on the board:
Tens of thousands of files were deleted at the one-time Clear-Recyclebin, and the disk IO was filled and the resource manager was unable to respond for a long time. Root: The wastebin entry is over 10,000 and the one-time deletion of the system causes IO peaks. Practice: Batch deletion, default 200 files/entries, stopover 200ms.
Two parameters in script corresponding to config/targets.json:
"recycle": { "chunkSize": 200, "pauseMs": 200 }
PowerShell side cycle with slices + Start-Sleep:
for ($i = 0; $i -lt $total; $i += $chunkSize) {
$last = [Math]::Min($i + $chunkSize - 1, $total - 1)
# Delete $files
$chunks++
Start-Sleep -Milliseconds $pauseMs
}
Note that floor protections for max (10, chunkSize) have been added to prevent the configuration from being mismodified to 1 leading to the deletion of 10,000 files for hours.
The only action that allows "real deletion" is because it is itself a "deleted" double confirmation, and it makes no sense to keep it; but because it cannot be restored, it must be clearly stated in the reply.
4.6 cache Target: 180-day threshold and "jumping"
The greatest risk of cleaning up cache is not to delete it, but to delete what is in use: pnpm store deletes the need to reload dependency, Chrome cache deletes the need to reload resources and cache deletes the need to recompile.
So the rule for cache-type targets (cache: true) is to deal only with unmodified entries for more than 180 days, to take in or to skip in the near future.
{ "key": "pnpm-store", "path": "%LOCALAPPDATA%\\pnpm\\store",
"risk": "medium", "enabled": true, "cache": true }
A combination of two design details:
- cache moves fail to count "jumping" and not fail. Because "occupancy" is the normal thing to expect, failure rates should not be contaminated.
- The non-cache category (Crash Repossession, Blue Screen Repository, CBS Log) does not set the threshold and cleans up the whole list - they are themselves disabling products, leaving 180 days meaningless.
The scan is also sent back to keptBytes (reserves below the threshold) so that users know that "this one looks big, but most of it is not moving."
Can't even sweep the entries. Prefetch and cbs-logs are closed by default: the former deletes the short-term slow start of the common application, and the latter is used for checking system updates. The default value is the product decision, and it is more effective to turn "not recommended " down by default than to write a 10-line warning in the document.
4.7 Download directories: the half worth the same name.
Cleaning up the download directories is limited if you press "no movement for days". The real site was downloaded five or six times from the same document:
Quarterly statements. xlsx
Quarterly statements (1). xlsx
Quarterly reports (2). xlsx
Program - Copy. Docx
Program - Copy (2).docx
Program - Copy.docx
So there are two kinds of actions:
-download: new: change earlier than statusDays (default 30 days)
- Download: duplicate: all but the last of the more than 1
A suffix automatically generated by a stripping system and superseding:
- Report (1). xlsx / Report (2). xlsx → Report. xlsx
- Programs - Copy.docx / Schemes - Copy (2).docx Schemes.docx
- Copy.docx Program.docx
The key is not to overcompose: only the number in brackets is removed from the copy mark, and report2026.xlsx and report2027.xx are not named. Keep the priority "the one with the shortest name at the latest change time".
This is also included in the bill because the original logic was to follow only the white list and the download catalogue was completely out of range.
4.8 Desktop preparation: only files, and only categories
The desktop is the most sensitive area for users, so the rules are tight:
- Sweep only the files under the desktop root directory, not recurred, not moved Jack.
- Skip desktop.ini, hide files, system files
- Skip.lnk /.url /.ini /.webloc - Fast is a high-frequency entrance and can be abused when moving
- classified as document by extension / picture / video / audio / compression / installation / other
- Automatic suffix of conflict with the same name, without covering
- A desktop: group action covers all categories, only one appears on the list
There is a concept here to clarify that the desktop directory itself is on the protection list, but that "desktop organizing" allows action by moving a file into a subdirectories within the desktop, without a disc, without deleting it. Desktop files can only be touched by desktop modules, and no other modules cross borders.
4.9 Closure process: two-stage safety valves
The process module has the highest risk (possibly discarding unsaved data) and therefore has two valves:
First: Default does not tick. All processes in the list
pc_flow.py apply --module process --ids process:12345 --yes
The second is: grace and coercion. The default is to switch to CloseMainWindow (equivalent to the top right corner of the user point window) and the process will reject the bullet window itself if there is no saved content. The results are divided into four states:
Seeing wait does not allow automatic upgrade to --force and must be returned to user confirmation. This rule is written in Rules/safety.md, because models are the easiest place to be "smart".
Candidate screening is also deliberately conservative: there must be a main window, or memory 200MB, and not on a protected list. The ownerless window, with < 200MB memory, is considered a back-office service and only counts no candidates -- otherwise hundreds of system components such as Runtime Broker will appear on the list and the user has no way of judging.
In addition to system-critical processes, the Protection List contains the current tool chain:
powershell / pwsh / python / cmd / windowsterminal
The simple reason is: Agent is running in the Python process, killing the father process, and the whole process is off the scene.
4.10 Optimization of performance: all actions shall bear original values
All three actions, only current user-level settings:
Two details:
- The action is not generated when it is high-performance and there is no perf: power-plan directly on the list to avoid creating a "zero-gain movement".
- The visual-effects default not to tick because they sacrifice animation and font smoothing -- aesthetic preference that should not be determined by the tool for the user.
V. Unmanned: Write "authorization list" for automated missions
When Skill landed, I hung it on a daily automation mission. The key here is to translate interactive confirmation into written, modular pre-authorization.
Here is the task prompt that I actually used (maintenance in automated missions, not Skill files):
Use pc-tuneup skills to perform a Windows computer check-up upgrade, strictly press
Init scan plan apply recapt clean stride and no jumping.
The current round is unserviceable and the following authorizations are considered to have been confirmed in advance by the user:
1. Disk trash: apply-module junk-yes
Desktop collation: application-module desktop-yes
3. Performance optimization: application-module perf-yes
4. Wastebin: This round does not process and does not perform any recycle actions
5. Processes and windows: Scan list only and no process is closed
Implementation requirements:
- Start with pc flow.py init, and proceed with the echo of high frequency experience.
- All deletions and movements must take place in isolation and are prohibited from direct Remove-Item, del, rd.
- Recall must be rendered in pc flow.py receipt, in the form of a list, prohibiting forms, prohibiting manual assembly.
- Any failure to keep PowerShell in its original language and label it as a ban on forgery of "cleaned".
- If the C disc is available in less than 10%, mark separately at the beginning of the reply.
- End the implementation of the clean, preserve the area of separation and historical accounts and do not automatically empty the area of separation.
Output content: Full reply + batch directory of the area of separation and restoration command.
This prompt has four layers of structure, which is worth noting separately:
- Sequence: six phases are visible, along with the check-up phase of the script, to form a dual insurance policy.
- Modular authorization: not "you can clean up" in general, but "module junk-yes". The wastebin is explicitly excluded, the process is clearly prohibited, and authorized boundaries are more important than the authorization itself.
- Restatement of the prohibition: removal from the area of separation, prohibition of hand-mutilation, prohibition of forgery of results. The three were also found in rules/never.md, but wrote again in prompt - because they are the most easily "reasonablely bypassed" by models.
- Output contract: Reply + Batch Directory + Restore command. Users get more than "cleaned 6.3GB," but "where and how to get it back."
One more detail: the C disc is marked separately at the beginning of the reply when the space available is less than 10%. This is not a clean-up capability, but rather a risk hint — even the area of separation itself may not be able to be written when space is extremely tense, and users must be warned first.
VI. RESPONSE: WHY THE STRENGTHENING OF LISTS, PROHIBITION OF FORMS
An anti-intuitive act was done in the design of the response: prohibited forms, all lists.
There are three reasons for this:
- Terminal width is uncontrollable: tables are folded into unreadable pieces at narrow terminals.
- Markdown tables often fail to render in chat windows, especially when Agent relays the receipt to the user.
- The list is more "failed article by article": a failure takes the PowerShell original key sentence and the table cells cannot be plugged down.
The returns fixed these paragraphs:
- Environment: System version / Whether to administrate / Memory totals and availability
- Disk: totals available per disk (percentage)
- Scan: Wastebin / Disk / Number of Desktop Files / Number of Processes Closed
- Implementation: preview or implemented, total number of items, number of successes/ failures, list actions and results by article
- Total releases: actual release space during current round
- Zone of separation: batch catalogue + restore command
- Time-consuming: full process time (including start time)
Recall also has a hidden function: to automatically write state/runs.jsonl (time / time-consuming / release / number of successful failures / batches) on first rendering and to move the phase to finished. Duplicate rendering does not duplicate entries.
So "how much has been cleared this time" always comes from scripts, not model memories. Once the account is saved, the trend can also be seen: the sudden decrease in releases at a certain point in time indicates that the clean-up has been completed; the sudden increase in the number of accounts, usually in the case of some application that is writing cache.
Add a fixed sentence at the end of the reply:
Do you want me to clear the old backlog of quarantine permanently?
Then wait for the user to respond. The area of separation is reserved by default for seven days and is not automatically deleted when due, only when init is indicated.
Why not clean up automatically: the isolation zone is the only medicine of regret. Automatic clean-up amounts to reducing the validity of regrets, and one day the user tries to retrieve a log that was wrongly deleted six months ago, and finds that it is long gone – a tool that is less trusted than a few more GBs.
VII. Empirical: letting Skill run faster
Skill runs more and more and finds the same pit repeatedly: a catalogue is always a failure to occupy, and some kinds of processes are never shut down. If this kind of information only exists in my head, change the machine, change the time.
So there's a lightweight experience account, four orders:
# Records (auto-merger with module-like entries, hits add)
pc_flow.py lessons add --module junk \
--symptom "C:\Windows\Temp Partial File Move failed"
--cause "by other processes"
"Fix, I'm good to go back.
# View (the default is only active, sort by number of hits)
pc_flow.py lessons list [--module junk] [--limit 20] [--all]
# Similar entries are compressed
pc_flow.py lessons merge
# Decided marking (replaced)
pc_flow.py lessons retire --id l-20261031-102024
The design has three constraints:
- Just remember the pits that will be retouched. One-time occasional occupancy failed, or the accounts were flooded with noise. The field agreement is "the phenomenon (retain PowerShell original key sentence) + the root cause + what next".
- Similarity 0.75 Automatic consolidation, hits cumulative, fix based on latest writing; more than 200 pieces cut by number of hits and nearest time. The account cannot expand indefinitely.
-Init automatically echoes the highest three hits, and this execution follows experience. That is the key — experience that is not remembered if it is not pushed forward.
More importantly, there is a provision that if the hierarchy of rules can be resolved, the rules must be written back and not just billed.
Double or more repeats + rule layer solves
• White List category: change to config/targets.json
• Decision logic class: change scripts/pc scan.py
• Process caliber class: change of SKILL.md iron law/rules/safety.md
We have to run again.
The bill is a patch and the rules are the cure. If a directory has failed to clean up for three months, it is correct to remove it from the white list (or downgrade it by default not ticking), rather than adding hits to each round of accounts.
In addition, no site was reported throughout the journey and no external network requests were made. This is not a brevity: a tool that can delete documents and close processes, and no security review can pass if data are still being sent externally.
VIII. List of pedestals
A few of them, as they are, are real:
1. One-time clean-up of the wastebin resulting in the system Cut. Die.
Symptoms: Tens of thousands of files are deleted simultaneously, disk IO is filled and the resource manager is not responding for a long time. Root: The wastebin entry is over 10,000 and the one-time deletion of the system causes IO peaks. Practice: Deletion by batch, 200 per batch, stopover by batch, 200 ms.
2. The cache catalogue is either complete or not.
Symptoms: The cache catalogue piled up six months ago, and the old logic did not distinguish between data that could have the value of reconstructing cache and sewage.
Practice: cache by MinAgeDays is only 180 days away, is in use or is recently written; dumps and logs are exempt from thresholds.
3. Long-term accumulation of duplicate copies of downloaded directories
Symptoms: The same document was downloaded several times and named by the system as xxx (1). xlsx/ xxx - copy.docx.
Roots: The clean-up logic follows only the white list, and the downloading directory is completely out of range and does not have the ability to merge under the same name.
Practice: Add a new downloads rule, 30 days or more + retain only the latest copy after the same name has been added, leaving the area of separation.
4. Chinese Path and Quote Cause PowerShell Error
Organisation
Practice: Replace -EncodedCommand + UTF-16LE with a uniform output code in prelude.
Process list flooded by system components
Symptoms: The early version listed all the components of RunteBroker, SeachHost, etc., as candidates and users could not judge.
Practice: Add a "no-owner window and memory < 200MB directly excluded ", with the candidate standing in the order of memory backwards.
IX. Six Principles for Reuse to Other Agent Tools
If you're also going to give Agent a "side effect" capability (deployment, distribution, batch modification data, kinetic configuration), I think the six are common:
1. prompt does not control destructive operations. "Whether or not to do it" and "what to do" in the script. The model can only select which sub-command to call and cannot spell the deletion order itself.
2. Default values are security strategies.
Preview is default, -- yes before landing; process default is not ticked; high risk cache is defaulted to close. Users should not be expected to make the right judgment on the list every time.
3. Each irreversible act must have a corresponding "reversible substitution". If you can move it, don't delete it. If you can record the original value, don't overwrite it. Unreversible (recycling stations, off-site processes) must be clearly informed of non-recoverability before implementation.
4. Status machine + machine readable error code.
Phase check, exit code. Let Jump fail at the protocol level, not by Agent himself.
5. The result must be a script output, and models are prohibited from summarizing figures. Reactions, releases, success stories, all from scripts. The role of the model is transposition, not statistics.
Experience needs to be able to sink into rules. Each step in the pit is marked and the configuration or code is returned twice. Otherwise, the same pit will be retried in every new session.
Concluding remarks
This Skill is running automatically every day. The process is fixed:
Init scan plan apply
Users get three-part results: what was done, how much was released, how was it reversed?
I'm getting the feeling that the core of the tool for Agent is not "let it do more," but that it's a small enough cost to make mistakes. Previewable
Auto-fetched and translated by AI Atlas. All rights belong to the original author.