thread-payload-guard 是一个 Codex skill,专门解决"会话越聊越大、最后被接口 413 拒收、界面一直转圈"的问题:它约束 agent 的截图方式、随时给会话称重,并且能把已经卡死的会话救回来。适合任何需要大量截图的工作——UI 调试、视觉验证、性能测试、游戏跑分。
- 省流截图:
shot.py只截需要的区域、缩小、转 JPEG。整屏 Retina PNG 2.5 MB 实测变成 13 KB,省 99%;进对话后按 base64 计费,降的是字节也顺带降 token。 - 会话称重:
thread_weight.py把会话历史拆成「图片字节 + 文本字节」,给出 ok / watch / tight / over 判定,并打印上次请求的 input token 与上下文窗口——用来说明为什么 token 侧不会提前预警。 - 卡死修复:
slim_thread.py把历史里的图片载荷换成[image payload removed NKB]占位符。49.2 MB 的会话实测降到 1.3 MB,文字、推理、命令输出、文件路径全部保留。 - 判定线来自实测:同一个会话在 44.8 MB 时请求正常,涨到 50.2 MB 就被
413 Payload Too Large拒收,所以阈值按字节定,不按 token 定。 - 写入安全:修复默认干跑,加
--apply才写;写前打时间戳备份,写临时文件后逐行json.loads校验,任一行不合法就放弃、原文件不动;会话仍处于打开状态(检测~/.codex/thread-writer-locks/)时直接拒绝执行。 - 约束 agent 的截图习惯:能用文字信号(窗口坐标、文件 mtime、
lsof、图像哈希 diff)就不看图;每张图只看一次;单会话图片预算 10 MB;超 40 MB 必须交接;并明确禁止用 fork 逃——fork 会把图片一起复制过去。 - 诊断线索:附只读 SQL,在
~/.codex/logs_2.sqlite里查codex_core::responses_retry的重试记录,确认卡住确实是 413 而不是别的原因。
1 把仓库链接发给 agent,说“下载并安装这个 skill”,agent 会自动完成下载、安装。
2 从 Releases 页面 下载压缩包,解压后把其中的 thread-payload-guard 目录放到 skill 目录:
- Codex:
~/.codex/skills/thread-payload-guard/
3 也可以从源码装:
git clone https://github.com/reF0o0/thread-payload-guard.git
cp -R thread-payload-guard/thread-payload-guard ~/.codex/skills/更新重做一遍即可:重新下压缩包覆盖,或 git pull 后重新 cp -R。skill 在会话启动时加载,装完要新开一个会话才生效。
1 平时不用管它怎么触发。遇到"这次要大量截图""某个对话卡住不出字""这个会话占了多少磁盘"这三类情况时它会自动被加载,也可以显式 $thread-payload-guard 调用。
2 截图走 shot.py,只截区域、自动缩小:
python3 ~/.codex/skills/thread-payload-guard/scripts/shot.py -R 0,90,1440,80 -o /tmp/tabrow.jpg --max-width 700实测输出:
输出 /tmp/tabrow.jpg
尺寸 700x39 文件 7 KB 进对话后约 10 KB base64
对比 原始截取的 PNG 0.12 MB -> 现在 0.01 MB(省 94%)
对比一下不压缩的路子:同样内容的整屏 Retina 截图是 2.5 MB,进对话后按 base64 算就是 3.3 MB。一张图差 300 多倍,一个会话差的就是"能正常聊"和"被 413 拒收"。
3 干活中途给会话称重:
python3 ~/.codex/skills/thread-payload-guard/scripts/thread_weight.py # 当前会话
python3 ~/.codex/skills/thread-payload-guard/scripts/thread_weight.py --all # 最重的会话实测输出(就是被 413 拒收的那类会话):
会话 01a0aa5b-...
历史 49.8 MB = 图片 48.8 MB(33 张,最大 4.0 MB) + 文本 0.9 MB
tokens 上次请求输入 122,064 / 窗口 996,147 —— token 远没满,压缩不会救你
判定 OVER —— 已到危险区(实测 50MB 会被 413 拒收),立刻换新会话
4 会话已经卡死时修复它。先把那个会话关掉,否则脚本会拒绝执行:
python3 ~/.codex/skills/thread-payload-guard/scripts/slim_thread.py --thread <thread-id> # 干跑
python3 ~/.codex/skills/thread-payload-guard/scripts/slim_thread.py --thread <thread-id> --apply # 备份后写入实测输出:
体积 49.2 MB -> 1.3 MB(去掉 20 张图,47.9 MB base64)
已写入:1.3 MB,图片条目 20 -> 0
备份:rollout-....jsonl.bak-20260917-120012
写入后重新打开这个会话就能继续对话,历史里的截图显示为占位文字。
阈值不是拍的,是用同一套环境实测出来的:
| 会话历史 | 判定 | 该做什么 |
|---|---|---|
| < 10 MB | ok |
继续干活 |
| < 25 MB | watch |
开始收尾,别再开新的截图循环 |
| < 40 MB | tight |
这一轮做完就交接,不要再贴图 |
| ≥ 40 MB | over |
已到危险区(实测 50 MB 被 413),立刻换新会话 |
触发 413 的真正变量是请求体字节数:Codex 每次请求都会把整段历史重发一遍,图片以 base64 存在历史里。同一批会话在 44.8 MB 时还能正常响应,50.2 MB 时请求被拒,客户端会静默重试 5 次后判负,界面上只表现为一个一直转的圈——这就是用户感受到的“卡死”。重试不可能成功,因为超大的历史本身就是请求内容。
token 侧不会先报警:那些请求只报了 12–15 万 input token,而上下文窗口是 996,147,自动压缩根本不会触发。先撞墙的是字节,不是 token。
截图会同时存在于两个地方,行为完全不同:
| 位置 | 内容 | 会自动清理吗 |
|---|---|---|
/private/tmp/*.png 等文件 |
截图原图 | 会。 macOS 的 com.apple.bsd.dirhelper 每天 03:35 删除 /private/tmp 中超过 3 天的文件,开机时也跑一次(com.apple.tmp_cleaner 每天 0 点) |
~/.codex/sessions/**/rollout-*.jsonl、~/.codex/archived_sessions/ |
对话历史里的 base64 副本 | 不会。 归档只是把文件挪个目录,字节一个不少 |
所以删 /tmp 里的截图能省磁盘,但不会让会话变轻——拖垮请求的是历史里那份副本。要回收这部分空间,要么删掉不用的会话,要么用 slim_thread.py 瘦身。
- macOS(脚本使用
screencapture、sips、osascript) - Codex App 或 CLI
- Python 3(macOS 自带的
/usr/bin/python3即可,无第三方依赖) shot.py截图需要「屏幕录制」权限(系统设置 → 隐私与安全性 → 屏幕录制)
- 阈值按 deepseek 供应商 +
wire_api = "responses"这套配置实测标定。换 provider 或换模型后上限可能不同,需要重新标。 - 瘦身不可逆:历史里被替换的截图只能变成占位文字,对话会失去当初的图片内容。备份文件
rollout-*.jsonl.bak-<时间戳>留在原目录,清理前可以回滚。 --force只在确认无风险时使用,它的作用是跳过"会话仍处于打开状态"的拦截。- 这个 skill 只负责让会话不被撑爆,不减少 token 消耗本身;不过图少了,token 自然也会少。
- 仓库里不含任何 API key、本机用户名或真实会话数据。