检查点与恢复
This content is not available in your language yet.
会话缺省只在内存里。要让它在进程重启后还能接着聊,你需要两件事:一个 SessionStore(存到哪),以及 createSession 的 store / resume 两个字段(什么时候存、什么时候取)。上下文压缩之后原文不再在历史里,所以又有了快照(checkpoint):某时刻完整上下文的自包含只读拷贝,可列举、可恢复、可删除。
SessionStore 与 resume
Section titled “SessionStore 与 resume”import { createFileSessionStore, createSession } from '@tansr/sdk';
const store = createFileSessionStore({ dir: app.getPath('userData') }); // 目录显式传,恒不隐式扫用户目录
// 落卷:store 在场即自动接管「会话登记 + 每轮落卷」const session = await createSession({ token, baseUrl, store });session.send('记住:发射码是 7-4-1');await session.idle();const savedId = session.sessionId; // 逻辑会话 id(UUID 形制),跨重启凭它取回
// 进程重启后:resume 一等恢复(取回历史 + 配对治理 + 预载)const resumed = await createSession({ token, baseUrl, resume: { sessionId: savedId, store }, // 只恢复;要继续落卷,store 字段同时传 store,});resumed.send('复述发射码'); // 前文事实已在上下文
// 枚举与删除经 store 接口面(list 只有元数据,没有内容)const sessions = await store.list({ limit: 20 }); // SessionRecordMeta[]await store.delete(savedId);语义要点:
- resume 恒结构化报错:目标不存在抛
session_not_found;存储损坏(中段坏行 / 哈希链断裂 / 坏 payload)抛session_store_corrupted。绝不悄悄起一个空会话。 - 配对治理内置:恢复时断尾的
tool_call补 isError 占位、孤儿tool_result截断到干净边界,恢复历史直送模型不会 400。initialMessages低阶重灌径不过治理。 - 崩溃自愈:
FileSessionStore继承内核 journal 原语(哈希链 / 崩溃截断修复 / 文件锁),断电半行自动截断,不算损坏。 - 平台影子(令牌档):每个会话铸一枚平台会话 ULID 随请求出线,映射落
SessionRecordMeta.platformSessionId;resume 时复用同一枚,缓存亲和跨重启延续。平台侧租约 = 30 天不活跃窗口,对话活动即隐式续租。
「先恢复,恢复不了才新建」的标准写法:
import { TansrSdkError, createFileSessionStore, createSession } from '@tansr/sdk';
async function openOrResume(lastId: string | undefined) { if (lastId === undefined) return createSession({ token, baseUrl, store }); try { return await createSession({ token, baseUrl, store, resume: { sessionId: lastId, store } }); } catch (e) { // 只有「确实不在 store」才新建;session_store_corrupted 等恒上抛 if (e instanceof TansrSdkError && e.code === 'session_not_found') return createSession({ token, baseUrl, store }); throw e; }}自建存储:五方法与两条纪律
Section titled “自建存储:五方法与两条纪律”实现 SessionStore 五方法 create / get / list / commit / delete 即可注入(SQLite、IndexedDB 主进程代理、你自己的服务端都同形)。两条纪律:
commit收到rewritten = true必须整卷轮换,不得按长度增量追加——压缩折叠或快照恢复会就地改写历史前缀。- 损坏恒结构化 throw,不静默。
两个可选成员:getHistoryPage?(sessionId, { offset, limit })(分页读,缺席回落全量切片)与 checkpoints?: CheckpointStore(与会话同生命周期的快照存储,缺席 = 快照未接线)。
文件布局与封段
Section titled “文件布局与封段”createFileSessionStore({ dir, segmentation? }) 在 <dir>/sessions/<sessionId>/ 下写 journal.jsonl(活段)+ meta.json + attachments/。长会话按阈值动态封段为 segments/000001.jsonl… + manifest.json(缺省 16 MiB / 5000 记录启用;segmentation: { enabled: false } 恒单卷)。旧目录(无 manifest)零迁移可读;整卷轮换恒回到单活段,压缩前消息恒不复活;轮换中途崩溃由下次访问自动补齐,恒不见半卷。
快照目录三选一,缺省一律显式:缺席 = 与会话同居 <sessionDir>/checkpoints/(随 store.delete 一并消失);checkpoints: { dir } = 独立目录、独立生命周期;checkpoints: { store } = 自建 CheckpointStore(put / get / list / delete 四方法)。
手动压缩与快照
Section titled “手动压缩与快照”const session = await createSession({ token, baseUrl, store, checkpoints: { dir: path.join(app.getPath('userData'), 'checkpoints'), max: 20 },});
// 手动快照(任何空闲时刻;运行中 throw turn_running)const mark = await session.checkpoint({ label: 'before compaction' });// → CheckpointMeta { checkpointId(ULID,时间有序), trigger: 'manual', label, messageCount, createdAt, cwd, model, … }
// 手动压缩:缺省先落 pre_compaction 快照,再把历史前缀折叠为摘要const result = await session.compact({ instructions: 'keep the launch code' });if (result.status === 'compacted') { console.log(result.compactionId, result.removedRange, result.checkpointId);} else if (result.status === 'rejected') { console.log(result.reason); // 'turn_running' | 'empty_history' | 'not_configured' | 'hook_blocked'} else { console.log(result.reason, result.checkpointId); // failed:已落的快照保留}
for (const cp of await session.listCheckpoints()) console.log(cp.checkpointId, cp.trigger, cp.label, cp.messageCount);
// 恢复:同会话就地换史;缺省先落 pre_restore 快照const restored = await session.restore(mark.checkpointId);if (restored.status === 'restored') console.log(restored.fromMessages, '→', restored.toMessages, restored.preRestoreCheckpointId);
await session.deleteCheckpoint(mark.checkpointId); // 幂等签名(逐字):
compact(options?: { instructions?: string; checkpoint?: boolean | { label?: string } }): Promise<CompactResult>;checkpoint(options?: { label?: string }): Promise<CheckpointMeta>;listCheckpoints(): Promise<CheckpointMeta[]>;restore(checkpointId: string, options?: { checkpoint?: boolean }): Promise<RestoreResult>;deleteCheckpoint(checkpointId: string): Promise<void>;要点:
- 空闲期语义:
compact/checkpoint/restore/deleteCheckpoint在运行中一律拒绝、不排队、零副作用——compact()/restore()以结果rejected: 'turn_running'表达,checkpoint()/deleteCheckpoint()结构化 throwturn_running;await session.idle()后重试。 - 快照开关:
compact({ checkpoint })缺席按checkpoints.autoBeforeCompact(缺省 true);轮内自动压缩同样按它落pre_compaction快照。未接线时compact()照常压缩但不落快照,restore()返回rejected: 'store_not_wired'。 - 失败语义:
not_configured= 压缩关闭或上下文窗口未知;empty_history= 历史为空;failed携内核原因(nothing_to_compact/summary_malformed/model_error/before_compact_failed…),经turn.error(scope='compaction', recoverable)可观测,已落的快照保留。 - 恢复:先做
sessionId对账(别的会话的快照拒session_mismatch),再整体替换历史并签发session.restored,store 走整卷轮换。同一快照连续恢复两次,第二次仍换卷。快照cwd≠ 会话cwd时不改 cwd,推cwd_mismatch_on_restore提示。 - 保留:缺省不限;
checkpoints.max到顶按时间序淘汰最旧,不分 trigger。快照文件恒 durable 写(temp + fsync + rename)。 - 事件顺序:
session.checkpointed → session.compacted;session.checkpointed(pre_restore) → session.restored。
长会话 UI 不必一次拿全量。history({ offset, limit }) 按消息索引分页,返回切片恒配对完整——起点若落在一轮工具调用中间会向前对齐并回填 offset,终点向后对齐(limit 是下限)。
let cursor = 0;const first = await session.history({ offset: 0, limit: 50 });while (cursor < first.total) { const p = await session.history({ offset: cursor, limit: 50 }); render(p.messages); cursor = p.nextOffset; // 恒以 nextOffset 推进,不要用 offset + messages.length}
const { total } = await session.history({ limit: 0 }); // 只取 totalconst last = await session.history({ offset: Math.max(0, total - 50), limit: 50 }); // 尾页休眠会话(未起 AgentSession)用 readHistoryPage(store, sessionId, { offset, limit });createFileSessionStore 的 getHistoryPage 按段清单跳读,成本与尾部体量相关、与卷总长无关。
fork 与快照导出 / 导入
Section titled “fork 与快照导出 / 导入”session.fork(checkpointId)从一份快照长出一条新会话,源会话零改动;需要会话store安放新记录,否则抛session_store_not_wired。exportCheckpoint(checkpointId)产出自包含字节(格式tansr-checkpoint/1,图像附件内联);importCheckpoint(bytes)校验 format / schema / 附件 sha256 / 消息形,任一不过抛checkpoint_import_invalid,篡改即拒、零落盘。导入件归目标会话,可直接 restore 或 fork。
与会话服务的关系
Section titled “与会话服务的关系”@tansr/serve 的 /v2 面有同一组能力的 HTTP 形态(compact / checkpoints / restore / history 分页 / fork / export / import),语义与本文一致;服务端的会话保留与物理删除策略见保留期与治理。
Was this page helpful?
Thanks for your feedback.