Cleanzo 0.5.0:本地模型、agent 会话和缓存,分开算清楚

给 Mac 清理空间这件事,过去几年一直挺简单:浏览器缓存、Xcode 的 DerivedData、npm 和 pnpm 的 store、废纸篓。这些东西删了会重建,最多下次慢一点。

现在不够用了。

本地跑模型的工具越来越多。Ollama 在 ~/.ollama/models 里存权重,HuggingFace 的下载缓存在 ~/.cache/huggingface,LM Studio 也有自己的目录。Claude Code 把会话写在 ~/.claude/projects,Codex 和 TRAE 各有各的文件夹。

这些东西有个共同点:它们不叫缓存,但长得跟缓存一样——自己变大,你不太知道里面有什么,也舍不得删。一个模型几百 MB 起,70B 这个量级的量化权重,一个就是几十 GB。删错了不是「下次重建」,是重新下载几个 GB。

Cleanzo 0.5.0 加的「AI 空间」,处理的就是这块。

之前为什么不碰

0.5.0 之前,Cleanzo 对这两条路径的处理是拉进硬保护名单:不显示,也不能清。

这个决定在早期版本里是对的。清理工具最怕不知道代价就去动用户的大文件,模型恰好属于「不知道代价」那一类。

但保护名单有个副作用:看不见,不等于没占用。磁盘上那几十 GB 不会因为界面上没有就消失,用户也没地方知道它们占了多少。

0.5.0 换了个做法:把模型目录放出来,让体积和每一个模型都能看到,让每一项都有安全等级,删除也只进废纸篓。

放出来不等于建议你删。模型和会话仍然不预选,只有缓存默认勾选。

一页三段,四个等级

AI 空间把占用分成三段:本地模型、agent 会话、缓存;下面四档安全等级分别是可安全清理、看你需要、敏感、保留

图 1:三段分开算,四档按「删了要付什么代价」排。保留档不进列表,只报体积。

AI 空间的第一屏是三个数:模型、会话、缓存各占多少。三个数下面才是列表。

安全等级沿用逐工具页那四档,但这回要跨来源用:缓存和旧版本是「可安全清理」,模型是「看你需要」,会话是「敏感」,设置、凭据、技能是「保留」。保留不出现在列表里,只在汇总的地方报一个体积。

模型按来源分组,展开到单个模型:名字、大小、最近改动时间。会话按项目展开,一个项目一行,最近 24 小时内写过的不能勾,那可能就是你此刻正在用的对话。缓存合并成两行,展开才按工具拆成子项。

分级不是按「能不能恢复」分的,是按恢复的代价分的。缓存能重建,所以敢默认勾;模型能下回来,但要花几个 GB 和一段时间,所以默认不勾;会话删了拿不回来,所以要么逐个项目确认,要么不碰。

取舍一:90 天,做成一次性按钮

三个时间窗决定哪些行能勾:90 天以前可以批量勾,24 小时到 90 天可以勾但要自己判断,最近 24 小时会话不可勾;没有可选项时按钮直接不出现

图 2:时间越近,越可能是你正在用的东西。页面不替你判断,只把边界画出来。

设计时考虑过给会话段做一个常驻的「只看 90 天前」开关。放弃了。

原因有点微妙:那个开关回答的不是「这个页面怎么显示」,而是「这次要批量勾哪些」。做成常驻开关,用户会以为视图变了,而且每次扫一眼都要重新确认它现在是开还是关。

最后做成段头的一次性动作:模型段的「选中 90 天没动过的模型」,会话段的「选中 90 天前的会话」。点一下,只勾这一次算出来的、可以勾的行。点完还能逐行取消,或者直接清空。它不会变成设置。

另一个细节:没有符合条件的东西时,按钮直接不出现,不置灰。置灰的按钮会让人以为还在加载,或者以为自己的点击没生效,然后反复点。隐藏是一个更明确的回答。

批量只勾能勾的行。正在写的会话、24 小时内的记录,永远进不了这一批。90 天也没做成滑杆,两个段头同一个数,看一次就记住。它不算最优值,只是一个足够长、长到不像在催你的数字。30 天对模型太短,一年又基本等于不选。

取舍二:不伪造「上次使用」

设计稿上每个模型旁边都该有一个「上次使用」。实现到一半发现拿不到。

文件被改过,不代表被用过。任何一个工具往旁边写点东西,mtime 就变了;HuggingFace 的 hub 缓存也没有留下「谁在什么时候加载过」的记录。能稳定拿到的只有大小、路径和最近改动时间。

要不要用最近改动时间顶上去?

否掉了。这两个数在模型场景里经常差很远:半年前下回来的模型,文件从此没动过,但它可能每天都在跑;昨天重新下载的模型,也可能下载完就没再打开过。显示一个错的数,比显示「—」更糟,因为用户会拿这个数去做删除决定。

所以这一列最后没有做。模型行给的是名字、大小、最近改动时间,三个数都从文件本身读出来。如果一定要一个「上次使用」,它只能写「—」,那不如空着。

连带地,批量按钮的措辞也变了:叫「90 天没动过的模型」,不叫「90 天没用过的模型」。实现是同一个判断,但对用户的承诺不一样,前者只说文件没变化,后者说你在用它。措辞是产品的一部分。

页面因此少了一个看起来很聪明的功能。用户会问「那到底哪个能删」,我们只能给大小和改动时间,判断留给你。

取舍三:缓存合并成两行

缓存一开始是按工具逐行列的:Claude Code 一行、Codex 一行、TRAE 一行。再加上模型的三个来源和会话的几行,总览页很快就十几行。

这一页要的是决定勾哪几个,不是浏览目录。行数一多,决定就变成翻页。

所以缓存合并成两行——「日志与缓存」和「旧版本」,各自展开才是按工具拆的子项。合完三段加起来大概 8 行,一屏能看完。

合并也有损失:名字从具体工具变成了「日志与缓存」「旧版本」这种类别,你不知道里面是 Claude 的 debug 日志,还是 TRAE 的旧版本。这个代价靠另一件事补:展开后的子项和逐工具页共用同一份扫描结果,体积、安全等级、运行态判断都是同一套。在总览页看到「缓存」有体积,再去工具页看具体是哪部分,不会出现两个地方数不一样。

最有意思的一段:删一个模型,空间为什么没回来

删除 Ollama 模型前后的对比:manifest A 和权重 A 进废纸篓,共享层仍被 manifest B 引用所以留下

图 3:manifest 是索引卡,blob 是箱子。删一个模型,删的是它的索引卡,加上不再被任何索引卡引用的箱子。

Ollama 的模型不是一个文件夹,是两层:manifest 和 blob。

manifest 像一张索引卡,里面按层记着这个模型由哪些块组成;blob 是仓库里的箱子,文件名就是内容的 hash。一个模型占地方最大的是权重那一层,通常是它独占的;但小的层——配置、模板、license——只要内容一样,就会指向同一个箱子。同一个模型的不同 tag 之间也会共用一部分。

这意味着删模型有两种错法。

只删 manifest:ollama list 里模型没了,blobs 一点没动,空间没释放。

按 manifest 里的 digest 全删:会删掉别的模型还在引用的共享 blob,那个模型下次加载就坏了。

我们的做法分两步:先扫一遍这台机器上所有 manifest,收集还被引用的 digest 集合;再对要删的模型,逐条比对它引用的 digest,不在集合里的才进删除队列。删完之后,manifest 进废纸篓,孤儿 blob 跟着走,共享的留下。

这里没有多聪明的算法,关键在顺序:先拿全量引用,再决定删谁。如果一边遍历模型一边删 blob,结果就取决于遍历顺序——同一台机器,换个顺序可能坏的是另一个模型。这种 bug 我不想修,所以一开始就不让它出现。

还有个副作用要说清楚:因为只进废纸篓,删完之后 ollama list 里模型没了、AI 空间重新统计也不计入它,但磁盘空间要等你清空废纸篓才真的回来。这是「只进废纸篓」的代价,也是它敢让你碰模型的原因。

安全模型:所有删除都通向废纸篓

这页里没有 rm。所有删除都走系统废纸篓,文件能放回原处。

路径在进删除队列之前要过一道白名单:先解析符号链接,再统一小写,只接受主目录和用户临时目录里的路径,越界一律拒绝。~/.ollama 下的 blob 也走同一道门,不会因为路径是 Cleanzo 自己算出来的就跳过校验。

规则层面还有几条:保留档不进可勾列表;模型不预选;会话最近 24 小时内写过的不可勾;批量只勾可选行。工具在运行、文件被打开着,都当在用,不清。

读完之后,你可以这样查自己的机器

下面这些命令和 Cleanzo 无关,什么时候都能跑:

# 先看总量
du -sh ~/.ollama/models ~/.cache/huggingface ~/.claude/projects 2>/dev/null

# Ollama 里有哪些模型、各多大
ollama list

# 正在跑哪些模型,删之前先看一眼
ollama ps

# HuggingFace 缓存里,每个模型各占多少
du -sh ~/.cache/huggingface/hub/* 2>/dev/null | sort -hr | head

几条我自己的判断原则,和页面里的分级是一回事:

  1. 能重建的放心清:缓存、日志、旧版本。
  2. 要重新下载的,先看代价:模型、Playwright 浏览器这类。
  3. 删了回不来的,别批量:会话、对话记录。
  4. 正在用的,别手动删。Ollama 的 blobs 不要直接 rm,用 ollama rm;HuggingFace 的 hub 目录里,快照是指向 blobs 的符号链接,手动删 blobs 会让还在的模型坏掉。

如果这四条你拿不准,那就先不删。清理工具的价值是让你少删错,不是让你多删点。

现在可以怎么用

打开 AI 空间,先展开模型那一段,看段头有没有「选中 90 天没动过的模型」。有,说明这台机器上确实有很久没改过的模型,点一下看看清单;没有,说明模型目录一直在变,这页暂时只是一张台账,看一眼总数就可以关掉。

会话段同理。点「选中 90 天前的会话」,把项目行扫一遍,今天还在写的项目是勾不上的。

下一步我想做的不是加更多可删项,而是让「最近改动时间」更接近真实使用,如果能找到可靠的数据来源的话。在那之前,模型旁边只有改动时间,没有「上次使用」。

参与讨论