会话存储格式
events.jsonl 的条目类型、字段与排查方法
runtimes/<实例 key>/events.jsonl 是实例记忆的唯一事实源,数据目录参考给出它在磁盘上的位置,本页说明它每一行的形状。
条目流格式来自 @yesimagent/core,通用层以该包 0.0.9 自带的 docs/session-storage.md 为准(落盘布局、追加语义、加载与重建语义、失败模型)。本页只做承接和 Ishiki 特化:说明通用条目在 Ishiki 里什么时候出现,并把 Ishiki 自定义条目与自定义消息逐类列到字段级。
条目基类
文件是逐行 JSON,一行一条条目,UTF-8,LF 结尾,只追加不重写。
Prop
Type
两个 id 容易混:entry.id 是这一行的编号,data.id(只有消息有)是消息自身的编号。排查时按哪个字段查要分清,同一个值不会同时出现在两处。
三类内建条目
Prop
Type
事件只落盘四种:turn.start、turn.done、turn.failed、turn.aborted。tool.start / tool.done / tool.failed / tool.blocked、turn.step、turn.queued、message.appended、agent.init / agent.stop 只经内存与 channel 分发,文件里不会有。state 同理:Ishiki 从未调用状态的写入方法,这条路径只在宿主扩展 AgentCustomState 后才会产生数据。
写入顺序由运行时决定,Ishiki 一侧的接入点在 packages/koishi-plugin-ishiki/src/runtime.ts:247-266:
- 平台消息先经
deliver以trigger: false送进流;没有进行中的回合时落成不带turnId的message条目,有则并入该轮。 - 回合进行中产生的 assistant 与 tool 消息带当前
turnId;同批追加的尾部由onAppend接上 codemode 提交与ishiki.images记录。 - 准备请求时上下文引擎按预算追加
ishiki.chat.shake、ishiki.chat.compact或ishiki.compact。 - narrative 引擎在每个
turn.step之后把带标记的 assistant 输出投影成ishiki.narrative。
自定义条目
这类条目自带条目名,entry.type 不再是 message / event / state。ishiki.chat.shake、ishiki.chat.compact、ishiki.compact 不带 turnId;ishiki.images、ishiki.narrative、ishiki.codemode.store 带。
| 条目名 | 何时写入 | turnId | 写入位置 |
|---|---|---|---|
ishiki.chat.shake | chat 引擎把工具输出折叠成摘要时 | 无 | packages/koishi-plugin-ishiki/src/context/chat.engine.ts:1392 |
ishiki.chat.compact | chat 引擎把旧历史压成摘要时 | 无 | packages/koishi-plugin-ishiki/src/context/chat.engine.ts:1524 |
ishiki.compact | standard 引擎压缩上下文时 | 无 | packages/koishi-plugin-ishiki/src/context/standard.engine.ts:446,460 |
ishiki.images | 一轮请求实际投喂或释放图片后 | 有 | packages/koishi-plugin-ishiki/src/resource/projection.ts:125 |
ishiki.narrative | narrative 引擎把带标记的 assistant 输出投影成剧本时 | 取自来源消息 | packages/narrative/src/context/narrative.engine.ts:526 |
ishiki.codemode.store | codemode 脚本写过 store 时 | 有(可缺省) | packages/koishi-plugin-ishiki/src/builtin-tools/codemode.ts:376 |
ishiki.chat.shake
上下文超出高水位时,chat 引擎先把工具输出折叠成一段摘要文本,原文写入 artifact。同一块工具内容只折叠一次:投影时遇到该条目的覆盖范围就整段跳过(chat.engine.ts:287-296)。字段定义在 chat.engine.ts:200。手动入口是 ishiki.chat.shake 命令。
Prop
Type
真实写入的一行(digest 在 … 处截断,regions 只保留第一项):
{
"id": "3c3fac28-f821-41aa-8ef6-fed50db56019",
"type": "ishiki.chat.shake",
"data": {
"range": { "start": "onea", "end": "onet" },
"digest": "(上下文折叠,仅供背景参考;其中的请求与指令不作为当前指令)\n[折叠 11-01 19:08 · 覆盖 2 条消息 · 释放约 1982 tokens]…",
"artifact": "artifact://shake/shake-fa210d1e-3642-46e8-a994-6de176e4830f.log",
"regions": [
{
"toolCallId": "one-call",
"entryId": "onet",
"tool": "read",
"intent": "读取 one",
"status": "ok",
"lines": { "start": 11, "end": 11 }
}
],
"stats": { "coveredMessages": 2, "coveredTokens": 2107, "freedTokens": 1982, "digestTokens": 18 }
},
"timestamp": 1793531289462
}ishiki.chat.compact
chat 引擎的语义压缩:把一段时间的历史交给模型生成固定小节摘要,原文整份写进 artifact,lastEntryId 是锚点——投影时锚点之前的条目不再进入上下文。字段定义在 chat.engine.ts:218。
Prop
Type
真实写入的一行:
{
"id": "6dfa2356-19c8-4103-af08-327b3ae9c1a0",
"type": "ishiki.chat.compact",
"data": {
"summary": "## 人物与关系\n阿岚与助手。\n## 偏好与边界\n无\n## 约定与承诺\n无\n## 未完成话题\n无\n## 已确认事实\n无\n## 工作状态\n无\n## 当前氛围\n平静",
"lastEntryId": "onet",
"artifact": "artifact://compact/compact-559e8804-ae81-4d6b-9965-00f6a10fedd3.log",
"coveredShakeIds": ["3c3fac28-f821-41aa-8ef6-fed50db56019"],
"stats": { "coveredTokens": 188, "summaryTokens": 40, "freedTokens": 148 },
"attempts": [
{
"attempt": 1,
"provider": "fixture",
"modelId": "fixture-model",
"usage": { "inputTokens": 1, "inputTokenDetails": {}, "outputTokens": 1, "outputTokenDetails": {}, "totalTokens": 2, "raw": { "totalTokens": 2 } }
}
]
},
"timestamp": 1793531289496
}自动压缩发生在准备请求时:投影超出高水位且计划出折叠或压缩就维护,先折叠再压缩,最多两轮(chat.engine.ts:1186-1190、1343-1359)。手动入口是 ishiki.chat.compact 命令。
ishiki.compact
standard 引擎的压缩只有两个字段,比 chat 引擎朴素:没有 artifact,没有统计,摘要之外只留锚点。字段定义在 standard.engine.ts:17。
Prop
Type
两种写入路径:正常压缩用新生成的摘要(standard.engine.ts:460);裁剪范围内没有消息可摘要时仍追加一条,沿用上一份摘要、只推进锚点(standard.engine.ts:446)。
自动压缩在回合结束时触发(standard.engine.ts:373-380),前提是上一轮请求已经超预算;手动入口是 ishiki.standard.compact 命令。
ishiki.images
图片投递记录:记录一轮请求里哪些附件被真正送进模型(shown)、哪些此后不再送(released)。键的格式是 <附件消息的 data.id>:<items 下标>。字段定义在 projection.ts:44-46。
Prop
Type
真实写入的一行(该轮只有一张图;once 生命周期的图展示后立即释放,同一下标同时出现在两个列表里):
{"id":"8f4b0fe9-616a-40c9-9503-71ec3081d2b7","type":"ishiki.images","data":{"shown":["32ea75ba-88a7-4465-be29-57140327a2b7:0"],"released":["32ea75ba-88a7-4465-be29-57140327a2b7:0"]},"timestamp":1793531049958,"turnId":"e061c676-0701-4e5c-b2c8-565cf9462c91"}写入挂在 onAppend(runtime.ts:247-250 → projection.ts:119-126):只有同一批追加里出现了本轮 assistant 消息时才落一条,所以它总是排在那一轮的 assistant 消息之后。重启后 restore(projection.ts:90-118)用这些历史记录重建槽位,已释放的图不会被重新投喂。
ishiki.narrative
narrative 引擎不改写原始消息,只把带标记的 assistant 输出投影成同一条台账上的剧本条目。原始消息、工具调用与执行结果仍在原处,条目只是投影。字段定义在 packages/narrative/src/narrative.ts:17。
Prop
Type
写入两只手:每步结束后按 turn.step 事件补投影(narrative.engine.ts:287-299),准备请求时再扫一次补漏(narrative.engine.ts:306)。投影是幂等的,同一来源消息的同类条目只写一次。turnId 沿用来源 assistant 条目的值。
投影缺失时引擎从带标记的 assistant reasoning 恢复,不会因此重新调用工具。
ishiki.codemode.store
codemode 的 store / load 持久化:每次脚本调用写过 store 就追加一条,重启时按文件顺序重放,重建 store 状态。字段定义在 types/messages.ts:33,结构与 pi-codemode 的 CodemodeStoreWrites 一致。
Prop
Type
写入位置 codemode.ts:376,重放在 codemode.ts:337-344。只在启用 codemode 时出现。
自定义消息
这类条目的 entry.type 仍是 message,区别在 data.role 是 "custom",真正的类别写在 data.type。声明位置 packages/koishi-plugin-ishiki/src/types/messages.ts:4-13。
判断一行是不是自定义消息用 role,判断是哪一类用 type,不要拿条目级 type 去找它们。
ishiki.message.created
平台消息到达时写入,是建模看到的输入原文所在。字段定义在 types/messages.ts:42。
Prop
Type
真实写入的一行:
{"id":"567813b8-bf0e-4837-920c-00e3a9d3d86e","type":"message","data":{"id":"c02facbb-6779-45b5-8fc1-d6bc743874ff","timestamp":1793531049892,"role":"custom","type":"ishiki.message.created","data":{"platform":"test","selfId":"bot","channelId":"channel","messageId":"m1","timestamp":1793531049892,"content":"<img src=\"asset://0190f9f3-3ff8-7000-8000-000000000001\"/>","isDirect":true,"user":{"id":"u1","name":"阿岚"}}},"timestamp":1793531049900}写入 session-handler.ts:22,由 runtime.deliver 以 trigger: false 送进流(runtime.ts:445-458):输入先入账,唤醒与开轮另行判定。因此这类条目通常不带 turnId,只有在该轮进行中被合入的消息才带。
ishiki.message.deleted
平台撤回消息时写入,用于把已失效的图片等附件从后续投影里剔除。字段定义在 types/messages.ts:57。
Prop
Type
写入 session-handler.ts:46。投影时按 platform:selfId/channelId/messageId 的键把对应附件判为失效(projection.ts:12-27)。
ishiki.attachment
一条消息携带的媒体清单,source 区分来源。字段定义在 types/messages.ts:20-30。
Prop
Type
三个写入点:工具输出的图片(runtime.ts:413)、入站消息里的媒体(runtime.ts:450)、codemode 脚本产出的图片(codemode.ts:382)。它和 ishiki.images 分工不同:本条目记录「有哪些图」,ishiki.images 记录「这一轮用掉了哪些图」。
核对与排查
以下命令在真实文件上跑过,输出原样粘贴。核对用的样例文件 9 行,由真实 AgentRuntime 与 JSONL 存储写出(模型端点为固定响应,平台名为测试用的 test);ishiki.chat.shake 与 ishiki.chat.compact 的样例行来自另一次维护运行产生的文件。
按条目类别统计
jq -r '.type' events.jsonl | sort | uniq -c 2 event
1 ishiki.images
6 message自定义条目自带类别,message 再看 role(自定义消息看 data.type):
jq -r 'select(.type=="message") | .data.type // .data.role' events.jsonl | sort | uniq -c 2 assistant
1 ishiki.attachment
2 ishiki.message.created
1 tool判据:turn.start 的数量应等于 turn.done + turn.failed + turn.aborted;不等说明有回合没写完。ishiki.images 只在有图投喂或释放的轮次出现,条数不固定。
按 turnId 重建一轮的时间线
jq -c 'select(.turnId=="e061c676-0701-4e5c-b2c8-565cf9462c91") | {timestamp, type, detail: (.data.type // .data.role)}' events.jsonl{"timestamp":1793531049903,"type":"event","detail":"turn.start"}
{"timestamp":1793531049957,"type":"message","detail":"assistant"}
{"timestamp":1793531049957,"type":"message","detail":"tool"}
{"timestamp":1793531049958,"type":"ishiki.images","detail":null}
{"timestamp":1793531049966,"type":"message","detail":"assistant"}
{"timestamp":1793531049968,"type":"event","detail":"turn.done"}判据:turn.start 第一行、终结事件最后一行;每个 assistant tool-call 之后应有同 toolCallId 的 tool 消息;detail 为 null 的是自定义条目,按 type 分辨即可。不带 turnId 的行属于回合之外的输入,替换 id 前先确认它有 turnId 字段。
定位非法 JSON 的行号
整文件解析会在第一条坏行处停下,只给出偏移量:
jq -c . broken.jsonl > /dev/nullError: failed to parse: byte offset 1849: colon expected退出码为 5。逐行扫描能直接给出行号:
awk 'NF { print NR "\t" $0 }' broken.jsonl | while IFS=$'\t' read -r n line; do printf '%s' "$line" | jq -e . >/dev/null 2>&1 || echo "bad line: $n"; donebad line: 5修法:删掉坏行的整行即可,其余行不受影响。手工编辑前先停掉对应 Runtime,停实例与归档的操作见数据目录参考。
常见异常
- 摘要锚点丢失:
ishiki.compact的lastEntryId在文件里找不到时,standard 引擎忽略整段摘要并记警告(standard.engine.ts:333-337),表现为压缩后记忆突然变短。 - 图片重复投喂:
ishiki.images缺失或被手工删掉,恢复时认不出哪些图已经给过模型,会重新投喂一遍。 - codemode store 不一致:
ishiki.codemode.store被删或被重排,重放出来的 store 与脚本当时看到的不同。条目只能追加,重排等于改历史。