Skip to content

1. 数据来源:Claude Code jsonl


1. 文件存储位置

官方 Claude Code

~/.claude/projects/<项目哈希>/
├── <sessionId>.jsonl              # 主会话 transcript
└── <sessionId>/subagents/         # 子代理 transcript
    ├── agent-<id>.jsonl
    └── agent-<id>.meta.json

trpc-claudecode(本环境常用)

~/.trpc-claudecode/projects/<编码项目路径>/
├── <sessionId>.jsonl
└── <sessionId>/subagents/
    ├── agent-<id>.jsonl
    └── agent-<id>.meta.json

Watcher 默认监听 ~/.claude/projects,也可通过 --projects-root 指向 ~/.trpc-claudecode/projects


2. jsonl 行格式

每行一个 JSON 对象,核心字段:

json
{
  "type": "user",
  "message": {
    "role": "user",
    "content": "帮我写一个快排函数"
  },
  "timestamp": "2026-06-05T10:00:01.123Z",
  "sessionId": "b0dcb60c-5539-42a7-b71b-80ffc878ec0f"
}

常见 type

type含义解析处理
user用户输入真人输入 → 新 Turn;tool_result → 归档
assistant模型输出追加到当前 Turn
attachmentSkill 列表等本方案暂不单独上报
system系统元数据本方案暂不单独上报

assistant 行的 content 结构

content 可能是字符串,也可能是数组:

json
{
  "type": "assistant",
  "message": {
    "role": "assistant",
    "model": "claude-sonnet-4-20250514",
    "id": "msg_01ABC...",
    "content": [
      {"type": "text", "text": "好的,我来帮你写..."},
      {"type": "tool_use", "id": "toolu_01XYZ", "name": "Bash", "input": {"command": "ls"}}
    ],
    "usage": {
      "input_tokens": 1200,
      "output_tokens": 85
    },
    "stop_reason": "end_turn"
  },
  "timestamp": "2026-06-05T10:00:04.567Z"
}

tool_result 行

工具执行结果以 user 角色回灌:

json
{
  "type": "user",
  "message": {
    "role": "user",
    "content": [
      {
        "type": "tool_result",
        "tool_use_id": "toolu_01XYZ",
        "content": "file1.py\nfile2.py"
      }
    ]
  },
  "timestamp": "2026-06-05T10:00:05.000Z"
}

3. trpc-claudecode 的「拆行」特性

同一 API 响应可能占多行 jsonl,共享 message.id

行1: message.id=msg_01, content=[{type:text, text:"好的"}]
行2: message.id=msg_01, content=[{type:tool_use, name:Bash, ...}]
行3: message.id=msg_01, content=[{type:tool_use, name:Read, ...}]

解析策略:按 message.id 分组(_group_assistant_calls()),而不是简单按行去重。这样一次模型调用对应一个 Langfuse Generation,而不是把整轮压成一条。


4. 子代理文件

agent-*.jsonl

子代理(Task / Agent 工具)有独立 transcript,结构与主会话相同。

agent-*.meta.json(trpc-claudecode)

json
{
  "agentType": "Explore",
  "description": "搜索项目中与 langfuse 相关的代码"
}

用于 match_subagent() 把主会话的 tool_use 和磁盘上的子代理文件配对。

两种磁盘布局

代码在 _candidate_subagent_dirs() 中兼容:

布局路径
A(官方 CC)<projects>/<hash>/agent-<id>.jsonl
B(trpc fork)<projects>/<hash>/<sessionId>/subagents/agent-<id>.jsonl

5. Hook 方案的输入来源

Hook 从 stdin 读取 Claude Code 传入的 JSON payload:

python
def read_hook_payload() -> Dict[str, Any]:
    data = sys.stdin.read()
    return json.loads(data)

从中提取:

python
session_id = payload.get("sessionId")
transcript_path = payload.get("transcriptPath")

这是 Hook 与 Watcher 的唯一输入差异——后续解析逻辑完全一致。


6. Watcher 如何发现文件

python
def is_main_session_jsonl(path: Path) -> bool:
    # 文件名是 UUID 格式:<sessionId>.jsonl
    # 排除 agent-*.jsonl(那是子代理,由主会话逻辑嵌套处理)
    return _MAIN_JSONL_STEM_RE.match(path.stem) and not path.name.startswith("agent-")

发现策略:

  • --transcript:监听单个文件(不存在时会等待创建)
  • --projects-root:递归扫描目录下所有主 session jsonl
  • 运行中 rescan() 发现新 session

7. 生活类比

jsonl 就像餐厅的手写流水账

  • 每写一行 = 一件发生的事(用户说话、AI 回复、取食材回来)
  • 同一个 message.id 的多行 = 厨师一次炒菜分好几步记录
  • subagents/agent-*.jsonl = 外包帮厨自己的小本本
  • Watcher = 会计每隔 1 秒翻一页新账;Hook = 每做完一单才翻一次

下一章:2_parse_pipeline.md — 如何把流水账切成 Turn。