Search your complete conversation history stored in session JSONL files. Use this when a user references older/parent conversations or asks what was said before.
This fork is tuned for OpenClaw operators who need fast incident forensics (cost spikes, tool-call drift, and behavior regressions) across many sessions.
Use this skill when the user asks about prior chats, parent conversations, or historical context that isn't in memory files.
Session logs live at: ~/.openclaw/agents/<agentId>/sessions/ (use the agent=<id> value from the system prompt Runtime line).
sessions.json - Index mapping session keys to session IDs<session-id>.jsonl - Full conversation transcript per sessionEach .jsonl file contains messages with:
type: "session" (metadata) or "message"timestamp: ISO timestampmessage.role: "user", "assistant", or "toolResult"message.content[]: Text, thinking, or tool calls (filter type=="text" for human-readable content)message.usage.cost.total: Cost per responsebash
for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do
date=$(head -1 "$f" | jq -r '.timestamp' | cut -dT -f1)
size=$(ls -lh "$f" | awk '{print $5}')
echo "$date $size $(basename $f)"
done | sort -r
bash
for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do
head -1 "$f" | jq -r '.timestamp' | grep -q "2026-01-06" && echo "$f"
done
bash
jq -r 'select(.message.role == "user") | .message.content[]? | select(.type == "text") | .text' <session>.jsonl
bash
jq -r 'select(.message.role == "assistant") | .message.content[]? | select(.type == "text") | .text' <session>.jsonl | rg -i "keyword"
bash
jq -s '[.[] | .message.usage.cost.total // 0] | add' <session>.jsonl
bash
for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do
date=$(head -1 "$f" | jq -r '.timestamp' | cut -dT -f1)
cost=$(jq -s '[.[] | .message.usage.cost.total // 0] | add' "$f")
echo "$date $cost"
done | awk '{a[$1]+=$2} END {for(d in a) print d, "$"a[d]}' | sort -r
bash
jq -s '{
messages: length,
user: [.[] | select(.message.role == "user")] | length,
assistant: [.[] | select(.message.role == "assistant")] | length,
first: .[0].timestamp,
last: .[-1].timestamp
}' <session>.jsonl
bash
jq -r '.message.content[]? | select(.type == "toolCall") | .name' <session>.jsonl | sort | uniq -c | sort -rn
bash
for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do
date=$(head -1 "$f" | jq -r '.timestamp' | cut -dT -f1)
calls=$(jq -r '.message.content[]? | select(.type=="toolCall") | .name' "$f" | wc -l | tr -d ' ')
echo "$date $calls"
done | awk '{a[$1]+=$2} END {for(d in a) print d, a[d]}' | sort
bash
for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do
sid=$(basename "$f" .jsonl)
cost=$(jq -s '[.[] | .message.usage.cost.total // 0] | add' "$f")
echo "$sid $cost"
done | sort -k2,2nr | head -20
bash
COST_THRESHOLD=2
CALL_THRESHOLD=40
for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do
sid=$(basename "$f" .jsonl)
cost=$(jq -s '[.[] | .message.usage.cost.total // 0] | add' "$f")
calls=$(jq -r '.message.content[]? | select(.type=="toolCall") | .name' "$f" | wc -l | tr -d ' ')
if awk "BEGIN {exit !($cost > $COST_THRESHOLD || $calls > $CALL_THRESHOLD)}"; then
printf "%s cost=%s tool_calls=%s\n" "$sid" "$cost" "$calls"
fi
done | sort -t= -k2,2nr
Set COST_THRESHOLD and CALL_THRESHOLD from your baseline, then run this after incidents to immediately shortlist suspicious sessions.
bash
for s in <old-session>.jsonl <new-session>.jsonl; do
echo "== $s =="
jq -s '{
total: length,
user: ([.[] | select(.message.role=="user")] | length),
assistant: ([.[] | select(.message.role=="assistant")] | length),
tool_calls: ([.[] | .message.content[]? | select(.type=="toolCall")] | length),
total_cost: ([.[] | .message.usage.cost.total // 0] | add)
}' "$s"
done
bash
jq -s '
def tool_names: [.[] | .message.content[]? | select(.type=="toolCall") | .name];
{
session_id: (input_filename | split("/")[-1] | sub("\\.jsonl$"; "")),
first: .[0].timestamp,
last: .[-1].timestamp,
messages: length,
user_msgs: ([.[] | select(.message.role=="user")] | length),
assistant_msgs: ([.[] | select(.message.role=="assistant")] | length),
tool_calls: (tool_names | length),
top_tools: (
tool_names
| group_by(.)
| map({name: .[0], count: length})
| sort_by(-.count)
| .[:5]
),
total_cost: ([.[] | .message.usage.cost.total // 0] | add)
}
' <session>.jsonl
Use this when you need a fast "what changed?" summary to share in incident notes.
bash
rg -l "phrase" ~/.openclaw/agents/<agentId>/sessions/*.jsonl
head/ tail for samplingsessions.json index maps chat providers (discord, whatsapp, etc.) to session IDs.deleted.<timestamp> suffixbash
jq -r 'select(.type=="message") | .message.content[]? | select(.type=="text") | .text' ~/.openclaw/agents/<agentId>/sessions/<id>.jsonl | rg 'keyword'