transcript 四层模型

L1 存储 / L2 幂等操作 / L3 订阅粒度 / L4 视图注册,序列号契约,以及从 wire.jsonl 冷重建的两级折叠。

作者 David更新于 第 27 篇(共 33 篇)

packages/transcript(约 3696 行)是转录渲染数据层。包描述:

Isomorphic transcript rendering data layer: agent-granular L1 store, idempotent L2 operations, granularity-gated L3 subscriptions, framework-free L4 view registry.

根 AGENTS.md 对它的定位没变:纯 TypeScript、浏览器安全、不 import 引擎,并且是 transcript 契约类型(src/contract/)与 op 批次序列号契约的唯一所有者。运行时依赖只有 zod。它和 agent-core-v2、kap-server 一样属于「零注释区」,源码里的注释已全部清掉。

为什么要独立成包

同一份对话历史,服务端要投影、浏览器要渲染:

  • 浏览器端不能 import 引擎(Node 依赖、体积)。
  • 两边若各写一套数据模型与协议类型,必然分叉。

所以把「转录的数据模型 + 归约逻辑 + 线协议 schema」抽成纯 TS 包。仓库里的运行时消费者是 kap-server;Web UI 已移到另一个仓库,以预构建产物 apps/kimi-code/dist-web 随 CLI 发布,产物里能搜到 subscribe_v2、transcript_since、transcript.ops 这些协议字面量。apps/kimi-inspect 则已改用 kap-server 的 message protocol v3,不再依赖本包(见 kap-server)。

四层

packages/transcript/src/
├── model/        L1 数据模型
├── store/        L1 存储(agentTranscript / transcriptStore)
├── ops/          L2 幂等操作(operation / apply)
├── granularity/  L3 订阅粒度(grade / filterOps)
├── view/         L4 视图注册(registry)
├── pagination/   分页
├── history/      冷重建(groupTurns / foldFacts)
└── contract/     协议契约(schema / events / mediaRef / origin)

L1:存储

model/ 下的实体:ids、turn、frame、interaction、attachment、todo、item、task、meta、prompt。

agent 粒度——主代理与各子代理各有一条转录流,store/agentTranscript.ts 管单个,store/transcriptStore.ts 管全体(按需 ensureAgent,并广播 agent 名册变化)。

L2:幂等操作

export { EMPTY_AGENT_STATE, applyOperation, appendAtOffset } from './ops/apply';
export type { AgentState, ApplyResult } from './ops/apply';

幂等是关键:WS 可能重连、重放、乱序,同一个 op 应用两次结果必须不变。AgentState 不可变,applyOperation 是纯归约函数——标准的 event sourcing 形态。

L3:订阅粒度

四档 TranscriptGrade:off / turn / block / delta。granularity/filterOps.ts 的判定很短:

档放行什么
off什么都不发
turn除下面两档专属 op 以外的全部 op;快照里 turn 的 steps 置空
block再加 step.upsert / frame.upsert(整状态帧)
delta再加逐 token 的 append

同一份数据,不同消费者按需订阅:只关心结构的订 turn,要完整帧但不要逐 token 动画的订 block,打字机效果才需要 delta。gradeFor() 支持按 agent 指定、以 '*' 兜底;needsResetOnTransition() 规定升档必须重发基线——低档时没发的帧,增量补不回来。

L4:视图注册

view/registry.ts 的 ViewRegistry<C> 不依赖任何 UI 框架,只按键分派:工具帧按 frame.view ?? frame.name(小写),输入按 origin.kind,时间线标记按 marker 名,另有 fallbackTool 兜底。C 是宿主框架自己的组件类型。

序列号契约

contract/schema.ts 第 434 行:

export const transcriptSeqSchema = z.number().int().nonnegative();
  • 每个 (session, agent) 有单调递增的批次 seq。
  • 订阅帧 subscribe_v2 的载荷里,transcript_since 是按 agent 的续传游标。
  • 追赶响应 transcriptOpsCatchupResponseSchema:batches(每批 seq + ops)、latest_seq、complete。

订阅游标 transcript_since 和 REST 转录响应里的 seq 都是 .optional()——不认识 seq 的老对端照样能用,只是没有增量追赶,只能全量刷新。服务端 GET /sessions/{session_id}/transcript/ops?since_seq= 做点对点追赶;complete: false 表示会话不在内存或 journal 已回溯不到,调用方必须回退全量刷新。

冷重建:两级折叠

会话不在内存里(冷会话)时,要从每个 agent 的 wire.jsonl 重建转录。kap-server 的 services/transcript/transcriptService.ts 读出记录后分两步:

wire.jsonl(唯一真相)
   ├── groupMessagesIntoSnapshot()  history/groupTurns.ts:上下文消息 → 回合树
   └── foldWireRecordFacts()        history/foldFacts.ts:非上下文记录 → 任务 / 交互 / 待办 / 标记……

文件名没变,两个入口的函数名比 v0.29.1 时更直白。foldFacts.ts 第 579 行起:

  for (const [id, entity] of interactions) {
    if (entity.state === 'pending') {
      interactions.set(id, { ...entity, state: 'cancelled' });
    }
  }

关机时仍 pending 的交互,折叠成 cancelled——否则重建出来会有永远转圈的「等待审批」。折叠也处理 context.undo 记录:被撤销的回合在冷重建里同样隐藏(v2 撤销时会顺带写一条兼容的 context.undo,见记录与回放)。

旧版根 AGENTS.md 曾注明「实时投影才有的字段冷重建不回填」,这句已从现行文档删去。对照代码:model/ 里 step 的 usage / finishReason / timing / retry、task 的 resultSummary 仍在,history/ 下却找不到它们的回填逻辑——这些字段依旧只来自实时投影。

计划的引用式存储

Plan 章节提到的机制换了落点。引擎侧 features/plan/planService.ts 的 recordRevision():

    const scope = this.agentCtx.scope();
    const key = `plan/${id}/v${version}.md`;
    await this.blobs.put(scope, key, bytes);

文档写进 agent 作用域的 blob 存储,plan.revision 记录只带 {agentId, id, version, key, sha256, bytes}。foldFacts.ts 优先用 key(可由调用方传入的 resolvePlanRevisionKey 解析成路径),老记录里的 path 仍兼容。大文档卸载,记录里只存引用 + 校验和——事件流保持轻量,同时可校验完整性。

与 kap-server 的分工

transcript 是纯数据层,kap-server 负责:

  • 引擎事件 → transcript ops(services/transcript/coreEventMap.ts、coreBinding.ts)
  • REST 路由:/sessions/{session_id}/transcript 及其 /ops、/user-messages、/plan
  • 批次 seq 分配与有界 journal(TRANSCRIPT_OPS_JOURNAL_CAPACITY = 2000)
  • 事件抑制:订阅了 transcript 的连接,不再重复收到已被投影的旧式 session_event(suppressedByTranscript())

下一篇:记录与回放。

本页目录