| name | session-analysis |
| description | Analyze Pi session JSONL files using jq patterns. Use when extracting
metrics, tool usage, costs, or reviewing session history. Load for
session export, summarization, or workflow analysis.
|
Session Analysis
Extract insights from Pi session files. Covers session discovery, jq patterns for metadata, tools, costs, and workflow analysis.
Session Location
Pi stores sessions as JSONL files:
.bosun-home/.pi/agent/sessions/<encoded-cwd>/<timestamp>_<uuid>.jsonl
Tip: use session_context first when available so you can analyze the exact active session_file instead of guessing paths.
Quick Reference
ls -lt .bosun-home/.pi/agent/sessions/*/*.jsonl | head -10
find .bosun-home/.pi/agent/sessions -name "2026-02-01*.jsonl"
rg -l 'keyword' .bosun-home/.pi/agent/sessions/
wc -l session.jsonl
Session Structure
Each line is a JSON object with a type field:
| Type | Description |
|---|
session | Session metadata (id, cwd, timestamp) |
model_change | Model selection (provider, modelId) |
thinking_level_change | Thinking level (low/medium/high) |
message | User or assistant message |
Message Structure
{
"type": "message",
"id": "...",
"parentId": "...",
"timestamp": "...",
"message": {
"role": "user|assistant|toolResult",
"content": [...],
"usage": { "input": N, "output": N, "cost": {...} }
}
}
Essential jq Patterns
Session Metadata
jq -s '.[0]' session.jsonl
jq -s '.[] | select(.type == "model_change") | {provider, modelId}' session.jsonl
Messages
jq -s '[.[] | select(.type == "message") | .message.role] | group_by(.) | map({role: .[0], count: length})' session.jsonl
jq -s '.[] | select(.type == "message" and .message.role == "user") | .message.content[].text' session.jsonl
jq -s '.[] | select(.type == "message" and .message.role == "assistant") | .message.content[] | select(.type == "text") | .text' session.jsonl
Tool Usage
jq -s '[.[] | select(.type == "message" and .message.role == "assistant") | .message.content[] | select(.type == "toolCall") | .name] | group_by(.) | map({tool: .[0], count: length}) | sort_by(-.count)' session.jsonl
jq -s '.[] | select(.type == "message" and .message.role == "assistant") | .message.content[] | select(.type == "toolCall") | {name, arguments}' session.jsonl
jq -s '[.[] | select(.type == "message") | .message.content[]? | select(.type == "toolCall" and .name == "read") | .arguments.path] | unique' session.jsonl
jq -s '[.[] | select(.type == "message") | .message.content[]? | select(.type == "toolCall" and (.name == "write" or .name == "edit")) | .arguments.path] | unique' session.jsonl
Costs & Usage
jq -s '[.[] | select(.type == "message" and .message.usage.cost) | .message.usage.cost.total] | add' session.jsonl
jq -s '{
input: [.[] | select(.message.usage) | .message.usage.input] | add,
output: [.[] | select(.message.usage) | .message.usage.output] | add,
cacheRead: [.[] | select(.message.usage) | .message.usage.cacheRead] | add,
cacheWrite: [.[] | select(.message.usage) | .message.usage.cacheWrite] | add
}' session.jsonl
jq -s '.[] | select(.type == "message" and .message.role == "assistant" and .message.usage) | {turns: .message.usage.turns, cost: .message.usage.cost.total}' session.jsonl
Subagent Results
jq -s '.[] | select(.type == "message") | .message.content[]? | select(.type == "toolCall" and .name == "spawn_agent")' session.jsonl
ls .bosun-home/.pi/agent/sessions/*/spawn_agent-artifacts/
Trimming for LLM Processing
Large sessions can be trimmed:
jq -s '[.[] | select(.type == "message") | {
role: .message.role,
time: .timestamp,
content: [.message.content[] |
if .type == "text" then {type: "text", text: .text[:500]}
elif .type == "toolCall" then {type: "tool", name: .name}
else empty
end
]
}]' session.jsonl > trimmed.json
Evidence-backed Audit Workflow (pickup/review)
Use this when you must produce a grounded audit and call out unsupported claims.
1) Extract exact user asks (timeline)
jq -r 'select(.type=="message" and .message.role=="user")
| .timestamp + "\t" + ([.message.content[]? | select(.type=="text") | .text] | join("\n"))' \
"$FILE" > "$OUT/user-prompts.tsv"
2) Extract exact tool-call sequence (with args)
jq -r 'select(.type=="message" and .message.role=="assistant")
| .timestamp as $ts
| .message.content[]?
| select(.type=="toolCall")
| [$ts,.name,(.arguments|tojson)]
| @tsv' "$FILE" > "$OUT/toolcalls.tsv"
cut -f2 "$OUT/toolcalls.tsv" | sort | uniq -c | sort -nr > "$OUT/tool-counts.txt"
3) Extract concrete files touched
jq -r 'select(.type=="message" and .message.role=="assistant")
| .message.content[]?
| select(.type=="toolCall" and (.name=="write" or .name=="edit"))
| .arguments.path' "$FILE" | sort -u > "$OUT/write-edit-paths.txt"
jq -r 'select(.type=="message" and .message.role=="assistant")
| .message.content[]?
| select(.type=="toolCall" and .name=="read")
| .arguments.path' "$FILE" | sort -u > "$OUT/read-paths.txt"
4) Extract explicit outcome evidence (commits/push/errors)
jq -r 'select(.type=="message" and .message.role=="toolResult" and .message.toolName=="bash")
| .timestamp + "\t" + (.message.content[0].text // "")' "$FILE" \
| grep -E '\\[main [0-9a-f]{7,}\\]|git push|To https://' > "$OUT/git-outcomes.txt"
jq -r 'select(.type=="message" and .message.role=="toolResult" and .message.isError==true)
| .timestamp + "\t" + .message.toolName + "\t" + (.message.content[0].text // "")' \
"$FILE" > "$OUT/errors.tsv"
5) Claim validation rule
For each summary claim, attach at least one evidence line from:
- user prompt timeline
- tool-call timeline
- tool results (especially git/toolResult output)
If no line supports it, mark as inferred/unsupported.
Common Pitfalls
- Prefer streaming jq filters (
select(...)) over broad jq -s when sessions are large.
- With
set -euo pipefail, define shell vars before use in the same command (FILE='...'; ... "$FILE").
- Save intermediate artifacts (
.tsv/.txt) under workspace/scratch/ to keep audits reproducible.
When to Use
- Reviewing what happened in a session
- Extracting costs and usage metrics
- Finding files modified in a session
- Analyzing tool usage patterns
- Preparing session summaries for chronicles
Subagent Session Files
Subagent runs create their own session files in:
.bosun-home/.pi/agent/sessions/*/spawn_agent-artifacts/<id>_<agent>.jsonl
Use the same jq patterns to analyze spawn_agent sessions.