AI 编码 Agent 工作流总崩溃?kassette 实战:用 JSONL 日志实现零成本持久化重试
AI 编码 Agent 执行复杂任务时,经常遇到这样的场景:调了 30 次 API、执行了 5 个工具、生成了几百行代码——然后进程崩溃了。重试时,LLM 重新生成所有内容,花费翻倍、时间翻倍,还可能产生不一致的结果。
kassette 是一个将「持久化执行」嵌入到 Agent 工作流中的 TypeScript 库。它的核心思路很简单:用一个只追加(append-only)的 JSONL 日志记录已经完成的工作步骤,崩溃重试时直接从上次中断的地方继续,而不是从头再来。
安装
npm install @usekassette/kassette
零运行时依赖,安装后即可使用。核心包以外的可选包:
@usekassette/core— 底层持久化原语,适合直接嵌入自定义 Agent 循环@usekassette/s3— S3 存储后端@usekassette/cli— CLI 工具,用于检查和 fork 日志
核心概念
kassette 提供一个 ctx.step() 方法,包裹需要持久化的异步工作单元。正常运行时代码照常执行,崩溃后重放(replay)时则会跳过已完成步骤,直接从断点继续。
第一步:一个带人工审批的 Agent 工作流
import { kassette, LocalStorage } from '@usekassette/kassette';
const storage = new LocalStorage('.kassette');
const agent = kassette(async (ctx, ticket) => {
const analysis = await ctx.step('analyze', () =>
llm.chat('分析这个问题并推荐修复方案', { ticket })
);
if (analysis.destructive) {
const approval = await ctx.suspend('human-approval');
if (!approval.approved) return { outcome: 'skipped' };
}
const result = await ctx.step('apply-fix', () =>
executeTool(analysis.suggestedAction)
);
return { outcome: 'resolved', result };
}, { storage });
这段代码中,ctx.step() 包裹了 LLM 调用和工具执行,ctx.suspend() 在需要人工审批时挂起工作流。
第二步:启动与恢复
// 启动工作流
let result = await agent.start(ticket);
// → { status: 'suspended', event: 'human-approval', runId }
// 进程退出... 外部 Webhook 或审批回调触发恢复
result = await agent.resume(result.runId, {
eventName: 'human-approval',
value: { approved: true },
});
// LLM 调用不会重复执行——直接重放已记录的分析结果
// → { status: 'success', result: { outcome: 'resolved', ... }, runId }
关键点:agent.resume() 重新执行工作流时,ctx.step('analyze', ...) 不会再次调用 LLM,而是直接返回日志中记录的上次结果。这意味着即使 LLM API 成本高昂的任务,重试也不会产生额外费用。
第三步:并行分支和超时控制
const agent = kassette(async (ctx, ticket) => {
const { creditCheck, propertyAppraisal } = await ctx.parallel({
'credit-check': () => checkCredit(ticket.userId),
'appraisal': () => appraiseProperty(ticket.propertyId),
});
const approval = await ctx.suspend('underwriter-review', {
timeout: 86_400_000, // 24 小时超时
});
const decision = await ctx.step('finalize', () =>
llm.chat('根据信用报告和评估结果做出最终决策', { creditCheck, propertyAppraisal })
);
return decision;
}, { storage });
ctx.parallel() 并发执行多个分支,任一分支挂起时整个并行块一同挂起。ctx.suspend() 支持可选超时,超时后工作流自动终止,释放资源。
日志即真相
kassette 的日志设计非常适合 AI Agent 场景调试:
npx @usekassette/cli list --storage file:.kassette npx @usekassette/cli dump--storage file:.kassette npx @usekassette/cli fork --from-offset 3 --storage file:.kassette
每次运行对应一个 JSONL 文件,可以用 jq 直接查询:
cat .kassette/.jsonl | jq '.event' | sort | uniq -c
版本兼容性
kassette 的日志记录了步骤名称。当工作流代码不兼容地变更时(如新增/删除/重排步骤),只需重命名步骤:
// 兼容变更:重命名步骤,旧日志无法匹配
const analysis = await ctx.step('analyze-v2', () =>
llm.chat(newPrompt)
);
或通过 version 选项做安全校验:
const agent = kassette(fn, {
storage,
version: 'workflow-v2',
});
版本不匹配时抛出 VersionMismatchError,避免用新代码读旧日志。
存储选择
| 后端 | 适用场景 | 延迟 |
|---|---|---|
LocalStorage | 文件系统持久化,单机重试 | ~100µs/次写入 |
@usekassette/s3 | 跨机器/跨区域恢复,Serverless | O(N²) 上传量 |
对于大多数 Agent 工作流(10-100 个步骤,KB-MB 级别的日志),LocalStorage 就足够了。如果有跨机器恢复需求,S3 后端让任何有存储访问权限的进程都能接管运行。
总结
kassette 解决了 AI Agent 工作流中一个很实际的问题:崩溃重试的成本。传统的重试策略会重新执行所有步骤(包括昂贵的 LLM 调用),而 kassette 通过只追加日志 + 确定性重放,让重试几乎零成本。
适合使用 kassette 的场景:
- CI/CD 流水线中 Agent 自动修复代码
- 需要人工审批的代码审查辅助工具
- 跨多个工具调用的自动化部署流程
- 任何”跑了几分钟崩溃了就要重新付钱”的 Agent 任务
相关链接