claw-code 源码分析:Transcript / Session Store——智能体「运行史」数据结构怎样才算可运维?

张开发
2026/5/4 1:04:42 15 分钟阅读
claw-code 源码分析:Transcript / Session Store——智能体「运行史」数据结构怎样才算可运维?
涉及源码src/transcript.py、src/session_store.py、src/query_engine.py、src/main.py与result/03.md、result/05.md中的会话/拒绝语义相连。1. 「运行史」在运维眼里要解决什么运维含 SRE、平台、合规、客服排障关心的不是「有一段聊天」而是能否在不重现当时模型的前提下回答谁在什么时间、以什么身份、在什么环境触发了会话每一轮用户输入、助手输出、工具调用请求/结果、拒绝原因是否可顺序重放用量与计费是否与某次请求可对账失败/越权能否按session_id/trace_id跨服务关联数据保留、脱敏、导出、删除是否有稳定 schema 与生命周期下面先还原 claw-code当前的 Transcript 与 Session Store 设计再对照上述问题给出可运维数据结构应具备的要素。2. 当前实现TranscriptStore进程内# 6:23:src/transcript.pydataclassclassTranscriptStore:entries:list[str]field(default_factorylist)flushed:boolFalsedefappend(self,entry:str)-None:self.entries.append(entry)self.flushedFalsedefcompact(self,keep_last:int10)-None:iflen(self.entries)keep_last:self.entries[:]self.entries[-keep_last:]defreplay(self)-tuple[str,...]:returntuple(self.entries)defflush(self)-None:self.flushedTrue语义entries仅追加字符串在本仓库中与QueryEnginePort.mutable_messages在成功路径上同步追加同一prompt见下文submit_message。flushed布尔标记append置假flush置真不表示「已 fsync 到对象存储」只表示调用过flush_transcript()。compact尾部截断不是语义摘要与mutable_messages共用compact_after_turns阈值。学习点移植期合理极小 API便于单测与教学。运维缺口无角色user/assistant/tool、无时间戳、无id、无版本无法区分「用户原话」与「系统注入」截断后不可恢复早期轮次。3. 当前实现StoredSession与磁盘 JSON# 8:35:src/session_store.pydataclass(frozenTrue)classStoredSession:session_id:strmessages:tuple[str,...]input_tokens:intoutput_tokens:intDEFAULT_SESSION_DIRPath(.port_sessions)defsave_session(session:StoredSession,directory:Path|NoneNone)-Path:target_dirdirectoryorDEFAULT_SESSION_DIR target_dir.mkdir(parentsTrue,exist_okTrue)pathtarget_dir/f{session.session_id}.jsonpath.write_text(json.dumps(asdict(session),indent2))returnpathdefload_session(session_id:str,directory:Path|NoneNone)-StoredSession:target_dirdirectoryorDEFAULT_SESSION_DIR datajson.loads((target_dir/f{session_id}.json).read_text())returnStoredSession(session_iddata[session_id],messagestuple(data[messages]),input_tokensdata[input_tokens],output_tokensdata[output_tokens],)持久化触发链QueryEnginePort.persist_session()→flush_transcript()→save_session(...)。140:150:src/query_engine.pydefpersist_session(self)-str:self.flush_transcript()pathsave_session(StoredSession(session_idself.session_id,messagestuple(self.mutable_messages),input_tokensself.total_usage.input_tokens,output_tokensself.total_usage.output_tokens,))returnstr(path)hydratefrom_saved_session把messages填回mutable_messages与TranscriptStore.entries并标记flushedTrue。运维相关隐患DEFAULT_SESSION_DIR为相对路径.port_sessions依赖进程当前工作目录不同服务启动方式会导致会话文件散落或「找不到」生产应显式传入绝对路径或配置save_session已支持directory但persist_session/load_session未暴露给 CLI 以外的配置层。整文件覆盖写无 WAL、无并发锁多进程写同一session_id会互相覆盖。无 schema 版本字段演进字段时难以做迁移。敏感内容messages若为真实用户 prompt落盘即PII/密钥风险需加密、脱敏或存引用 ID 而非原文——当前无任何封装。4. 与QueryEnginePort的状态关系双缓冲与一致性成功处理一轮时# 91:95:src/query_engine.pyself.mutable_messages.append(prompt)self.transcript_store.append(prompt)self.permission_denials.extend(denied_tools)self.total_usageprojected_usage self.compact_messages_if_needed()129:132:src/query_engine.pydefcompact_messages_if_needed(self)-None:iflen(self.mutable_messages)self.config.compact_after_turns:self.mutable_messages[:]self.mutable_messages[-self.config.compact_after_turns:]self.transcript_store.compact(self.config.compact_after_turns)现状mutable_messages与transcript_store.entries内容同步截断持久化只写mutable_messages。设计上两者在内存中应始终一致否则replay_user_messages与落盘会分叉——当前实现靠同一套append/compact维持。未进入运行史的数据对运维很重要每轮TurnResult.output助手侧摘要/JSONmatched_commands/matched_tools/permission_denials明细stop_reason工具调用的请求体与返回成功或失败。因此磁盘上的 JSON不是完整对话录而是「用户轮次文本 伪 token 累计」的瘦身快照适合移植演示不足以单独支撑事故复盘。5. 流式事件与运行史协议 vs 持久化stream_submit_message在内存中 yield 结构化事件含transcript_size但这些事件默认不落盘# 122:127:src/query_engine.pyyield{type:message_stop,usage:{input_tokens:result.usage.input_tokens,output_tokens:result.usage.output_tokens},stop_reason:result.stop_reason,transcript_size:len(self.transcript_store.entries),}可运维做法若产品前端消费 SSE运维侧应把同一事件流或等价日志追加写入只增不改的event log文件/队列/OLAP而不是仅依赖最终StoredSession快照。6. 怎样才算「可运维」的数据结构——分层建议6.1 事件日志append-only真相源每条记录建议至少包含字段作用event_id/ 单调序号有序、可去重session_id会话聚合trace_id跨服务ts(RFC3339)时间线、与指标对齐typeuser_message/assistant_message/tool_call/tool_result/permission_denial/error…payload结构化 JSON可引用 blob 存大体裁schema_version迁移claw-code 的TurnResult与流式 dict 是向该形态过渡的中间层。6.2 会话快照derived可丢可重建StoredSession类角色适合作为从 event log 物化的缓存用于快速 hydrate而不是唯一数据源字段可含last_event_id、checksum、updated_at。6.3 角色与多说话者智能体运行史至少user / assistant / tool三类当前仅存 user 侧prompt字符串。可运维模型常用统一Message或Turn块内含role与content或tool_calls[]。6.4 工具与审计每次 tool 调用应有call_id与PermissionDenial、计费、下游日志同键参见result/05.md。拒绝、超时、部分成功都应是独立事件而非挤在一条字符串里。6.5 用量与对账当前input_tokens/output_tokens为词数近似UsageSummary.add_turn运维对账需API 返回的真实 usage或自研 tokenizer并记录model_id、pricing_version。6.6 生命周期与合规Retention按租户策略 TTL 或法务要求删除。PII字段级加密、密钥轮转、访问审计。导出GDPR 等「可携带」需稳定 schema。6.7 运维可操作性可观测性transcript_size、落盘路径、flush 失败率、JSON 写延迟指标。配置session 目录、是否同步 fsync、单文件大小上限、轮转策略。调试session_id一键拉全链 event当前仅一条 json 文件。7. CLI 与测试提供的「最小闭环」160:169:src/main.pyifargs.commandflush-transcript:engineQueryEnginePort.from_workspace()engine.submit_message(args.prompt)pathengine.persist_session()print(path)print(fflushed{engine.transcript_store.flushed})return0ifargs.commandload-session:sessionload_session(args.session_id)print(f{session.session_id}\n{len(session.messages)}messages\nin{session.input_tokens}out{session.output_tokens})return0test_flush_transcript_cli_runs断言flushedTruetest_load_session_cli_runs经bootstrap_session生成文件再load-session。这是可回归的薄层但断言内容未覆盖消息语义完整性或并发。8. 小结维度claw-code 现状可运维目标Transcript字符串列表 flushed 截断带角色/时间/id 的事件序列或 append-only logSession 落盘单 JSON用户消息 伪用量版本化快照或 log 物化含助手/工具/拒绝路径相对.port_sessions配置化绝对路径、隔离租户并发覆盖写单写者或乐观锁 / 外部 store合规无内建保留、加密、脱敏、导出一句话当前TranscriptStore StoredSession把「会话能续上、能演示 persist」跑通了要可运维需把运行史从「几条字符串」升级为「可关联、可重放、可审计、可生命周期管理的事件模型」并让磁盘格式成为该模型的投影而非全部真相。

更多文章