Logo
热心市民王先生

pi-mono 架构设计 - JSONL 会话存储与树状结构

详解 pi-mono 框架的会话存储格式、树状结构、消息类型和版本演进

1.1 JSONL 会话存储格式

文件位置

pi-mono 的会话存储在用户主目录下,遵循分层结构:

~/.pi/agent/sessions/
├── --home--ubuntu--workspace--project/
│   ├── 20260307_140000_abc12345.jsonl
│   └── 20260307_150000_def67890.jsonl
├── --home--user--projects--another-project/
│   └── 20260306_090000_12345678.jsonl

路径转换规则

  • 工作目录的 / 替换为 -
  • 前后添加 -- 作为分隔符
  • 避免与文件系统路径冲突

JSONL 格式特点

JSONL(JSON Lines)格式每行是一个独立的 JSON 对象:

{"type":"session","version":3,"id":"uuid-123","timestamp":"2026-03-07T14:00:00.000Z","cwd":"/home/user/project"}
{"type":"message","id":"a1b2c3d4","parentId":null,"timestamp":"2026-03-07T14:00:01.000Z","message":{"role":"user","content":"Hello"}}
{"type":"message","id":"b2c3d4e5","parentId":"a1b2c3d4","timestamp":"2026-03-07T14:00:02.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Hi!"}]}}

优势

  • 流式读写:无需加载整个文件即可追加条目
  • 损坏恢复:单行损坏不影响其他条目
  • 版本控制友好:Git diff 可清晰显示变更
  • 人类可读:可直接用文本编辑器检查

会话版本

当前会话格式为 v3,历史版本:

版本特性迁移方式
v1线性序列(无 id/parentId加载时自动构建树
v2树状结构(id/parentId 链接)自动升级
v3重命名 hookMessagecustom向后兼容

1.2 树状会话结构

树形设计动机

传统聊天系统采用线性日志结构,存在以下局限:

  1. 分支困难:需要创建新文件才能尝试不同方向
  2. 回溯复杂:手动编辑日志易损坏结构
  3. 上下文污染:错误尝试无法隔离

pi-mono 采用树状结构解决这些问题:

[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← 当前分支

                                                            └─ [branch_summary] ─── [user msg] ← 替代分支

树的遍历

buildSessionContext() 方法从当前 leaf 节点向 root 遍历:

function buildSessionContext(leafId: string): Context {
  const path = []  // 从 root 到 leaf 的路径
  let current = entries.get(leafId)
  
  while (current) {
    path.unshift(current)
    current = entries.get(current.parentId)
  }
  
  // 过滤出需要发送给 LLM 的消息
  return extractMessages(path)
}

关键行为

  • 仅遍历当前分支路径
  • 跳过其他分支的条目
  • 在分支点插入 BranchSummaryEntry

分支操作

# 从历史节点创建分支
pi --branch a1b2c3d4

# 带摘要的分支(推荐)
pi --branch a1b2c3d4 --summary "探索了方法 A,但遇到性能问题"

分支后:

  1. 创建 BranchSummaryEntry 记录离开分支的上下文
  2. 新消息以分支点为 parentId
  3. 原分支保持不变,可随时切回

1.3 消息类型详解

基础消息类型(pi-ai 包)

UserMessage

interface UserMessage {
  role: "user"
  content: string | (TextContent | ImageContent)[]
  timestamp: number  // Unix 毫秒
}

支持内容类型

  • 纯文本字符串
  • TextContent 块数组
  • ImageContent 块数组(base64 编码)

AssistantMessage

interface AssistantMessage {
  role: "assistant"
  content: (TextContent | ThinkingContent | ToolCall)[]
  provider: string  // "anthropic", "openai", etc.
  model: string     // "claude-sonnet-4-5", "gpt-5.1-codex"
  usage: Usage
  stopReason: "stop" | "length" | "toolUse" | "error" | "aborted"
  timestamp: number
}

ThinkingContent

interface ThinkingContent {
  type: "thinking"
  thinking: string  // 模型的推理过程
}

跨提供商传递时,thinking traces 转换为 <thinking> 标签包裹的文本。

ToolResultMessage

interface ToolResultMessage {
  role: "toolResult"
  toolCallId: string
  toolName: string
  content: (TextContent | ImageContent)[]
  details?: any      // 工具特定元数据(不发送给 LLM)
  isError: boolean
  timestamp: number
}

扩展消息类型(pi-coding-agent 包)

BashExecutionMessage

interface BashExecutionMessage {
  role: "bashExecution"
  command: string
  output: string
  exitCode: number | undefined
  cancelled: boolean
  truncated: boolean
  fullOutputPath?: string  // 长输出存储路径
  excludeFromContext?: boolean  // !! 前缀命令
  timestamp: number
}

排除上下文

  • 使用 !! 前缀的命令(如 !! cat file.txt)设置 excludeFromContext: true
  • 执行但不发送给 LLM,用于用户自行检查

CustomMessage

interface CustomMessage {
  role: "custom"
  customType: string  // 扩展标识符
  content: string | (TextContent | ImageContent)[]
  display: boolean    // 在 TUI 显示
  details?: any       // 扩展特定元数据
  timestamp: number
}

用途

  • 扩展注入的消息(如检索到的记忆)
  • display: false 时仅发送给 LLM

CompactionSummaryMessage

interface CompactionSummaryMessage {
  role: "compactionSummary"
  summary: string
  tokensBefore: number
  timestamp: number
}

元数据类型

CompactionEntry

上下文压缩记录:

{
  "type": "compaction",
  "id": "f6g7h8i9",
  "parentId": "e5f6g7h8",
  "timestamp": "2026-03-07T14:10:00.000Z",
  "summary": "用户讨论了身份验证模块重构,决定采用 JWT 方案...",
  "firstKeptEntryId": "c3d4e5f6",
  "tokensBefore": 50000
}

LabelEntry

用户标记:

{
  "type": "label",
  "id": "j0k1l2m3",
  "parentId": "i9j0k1l2",
  "timestamp": "2026-03-07T14:30:00.000Z",
  "targetId": "a1b2c3d4",
  "label": "checkpoint-1"
}

1.4 版本演进历史

v0.1-v0.10:基础架构(2025 年 8 月 -9 月)

  • 初始 JSONL 格式(v1,线性序列)
  • 基础 4 工具:read, write, edit, bash
  • 简单会话继续(--continue

v0.11-v0.30:树状结构(2025 年 10 月 -12 月)

  • 引入 id/parentId(v2)
  • 分支支持(--branch
  • 上下文压缩(CompactionEntry

v0.31-v0.56:扩展生态(2026 年 1 月 - 至今)

  • CustomEntry API(v3)
  • Skills 系统
  • Packages 分发机制
  • RPC 模式(远程调用)

本章小结

pi-mono 的会话存储设计体现了其核心哲学:

  1. 文件化:JSONL 格式便于检查、备份、版本控制
  2. 树状结构:原生支持分支探索,无需创建新文件
  3. 类型丰富:区分消息、元数据、扩展内容
  4. 版本兼容:自动迁移旧格式,向后兼容

下一章将深入探讨记忆管理机制,包括上下文压缩、跨模型传递、会话恢复等核心功能。


参考资料

  1. pi-mono Session Documentation. https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/session.md
  2. pi-mono Source Code. packages/coding-agent/src/core/session-manager.ts
  3. Mario Zechner Blog. https://mariozechner.at/posts/2025-11-30-pi-coding-agent/