本地优先的用量报告同样需要生产级的失败边界。Claude Code 与 Codex 的会话日志在正常情况下是只追加的 JSONL,但它们可能含有内嵌的工具负载、粘贴进来的图片、写到一半的记录、重复事件,以及在扫描进行中还在变化的文件
Agent Island v1.7.1 处理了这条流水线上的两次事故:病态的行不再无上限地缓冲,而一次超过十分钟仍未完成的扫描,不再把余下整个进程生命周期的刷新都堵死
把内存和活性当成两条不同的边界
行长上限保护内存,扫描年龄上限保护活性。它们解决的是不同问题,不该共用一个包打天下的超时
for each session file:
stream lines with a platform-specific byte policy
parse only usage-bearing assistant events
deduplicate stable event identities
on refresh:
if scan is active and younger than wedge threshold:
keep the current scan
else:
start a fresh scan off the UI thread
行策略要从数据合同里推出来
macOS 的 Claude 读取器用 64 MiB 的兜底上限。那是一条内存天花板,不是常规过滤器。Claude 的用量可能就落在一条同时携带大工具负载的助手行上,所以把每一条超过某个小阈值的行都丢掉,会让本地总量与源日志背离
Windows 读取器用的是更严的规则:在 JSON 解析之前跳过超过一百万字符的行。这防止一个几 MB 的内嵌负载把峰值内存顶上去。这个差别是明确的、按平台区分的,不该被描述成同一个解析器
两种情况下,畸形与残缺的行对那一条记录都是失败闭合。一份正在被写的记录可能与读取器竞争,所以下一次指纹变化会让之后的轮询再解析一次
丢掉不可能含有用量的结构
一行通过了字节策略之后,读取器仍然不该把它反序列化成一个宽泛的应用模型。已发布的 Claude 读取器只接受满足以下条件的行:
- 顶层
type == "assistant" - 有一个
message.usage对象,且模型不是占位值 - 时间戳可解析
- 至少有一个非零的 token 字段
当两个标识都可用时,它用 messageId:requestId 给 Claude 事件去重。一个以路径、修改时间和大小为键的缓存,让每次刷新不必重读没变过的文件。这些规则减少的是常规开销,它们替代不了那条针对病态行的天花板
让过期的在途闸门自己失效
常见的刷新守卫是「如果在加载中就直接返回」。它防住了重叠扫描,但也把一个永远不提交的任务变成永久冻结。界面可以一直显示昨天的数据,而每一次计划中的刷新都停在同一个布尔值上
v1.7.1 的 macOS 存储分别跟踪 Claude 与 Codex 的扫描。如果某一家的扫描仍然活跃、但已经开始超过 600 秒,下一次刷新就被允许启动一个替代者。因此一个很慢的 Claude 扫描不会堵住一个健康的 Codex 扫描。Windows 对它共享的扫描闩锁套用同样的十分钟脱困
这个阈值不是在说十分钟是正常扫描时长。它是一条选在远高于预期路径之上的恢复边界。要收紧它,先把真实的扫描时长分布量出来
只提交已完成的汇总
替代扫描带来第二个问题:一个更早的任务可能在替代者之后才结束,把更新鲜的数据覆盖掉。稳健的存储应该给每次扫描配一个代际令牌,只提交最新的那一代。当前版本用年龄闸门恢复了活性;而对任何在恢复之后可能重叠的扫描器来说,按代际决定提交顺序是更强的通用设计
把解析与汇总放在 UI 线程之外,然后一次性提交发布状态。不要暴露一个更新到一半的服务商汇总
值得留着的失败测试
- 一个正常用量事件通过行策略,且只计入一次
- 重复事件不会让总量膨胀
- 一条不完整的末行不会让扫描崩溃
- 一条病态的行保持在预定的内存天花板之下
- 没变过的文件从解析缓存里返回
- 卡死的扫描在写明的阈值之后停止阻塞刷新
- 在有按服务商闸门的平台上,一家的慢扫描不会冻住另一家
- 一个更早的、恢复回来的任务无法覆盖更新的已提交代际
把测量的声称说窄一点
本地 token 台账是从用户机器上的文件里重建用量事件。它证明不了订阅扣了多少、用户省了多少,也证明不了有多少人在用这个产品。Agent Island 把 API 价值标成估算,并把扫描留在本地;没有任何会话数据被上传到 Agent Island 的服务
这里描述的行边界与卡死恢复都在 Agent Island v1.7.1 里。源码保持公开,方便需要审计具体平台行为的团队

