360 Secure SkillHub
360 安全技能中心

Session Logs Forensics

这是面向OpenClaw运维者的会话日志分析工具,基于jq和rg实现。核心功能包括检索历史对话、统计会话成本、分析工具调用情况、排查成本飙升/工具调用漂移/行为退化等异常。典型场景为用户询问不在内存的历史对话、进行事件取证。日志存于~/.openclaw/agents/<agentId>/sessions/,通过命令行执行脚本,输出对应日志结果与统计数据,支持多种常见查询。

danielsinewe
danielsinewe
热度 44.60
最近更新 2026-05-11 17:41:50
分类:数据依赖:代码执行 / 读写文件应用场景:数据处理 / 报告撰写

安装技能

复制下面的话术发给你的 Agent,它会通过 secure-skills 检索并安装该技能。

执行命令安装此 skill: npx secure-skills-cli add session-logs-forensics
Metadata
name
session-logs
description
Analyze OpenClaw session JSONL history for cost spikes, tool-call anomalies, and behavior regressions with jq + rg.
metadata
{"openclaw":{"emoji":"📜","requires":{"bins":["jq","rg"]}}}

session-logs

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.

Trigger

Use this skill when the user asks about prior chats, parent conversations, or historical context that isn't in memory files.

Location

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 session

Structure

Each .jsonl file contains messages with:

  • type: "session" (metadata) or "message"
  • timestamp: ISO timestamp
  • message.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 response

Common Queries

List all sessions by date and size

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

Find sessions from a specific day

for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do
  head -1 "$f" | jq -r '.timestamp' | grep -q "2026-01-06" && echo "$f"
done

Extract user messages from a session

jq -r 'select(.message.role == "user") | .message.content[]? | select(.type == "text") | .text' <session>.jsonl

Search for keyword in assistant responses

jq -r 'select(.message.role == "assistant") | .message.content[]? | select(.type == "text") | .text' <session>.jsonl | rg -i "keyword"

Get total cost for a session

jq -s '[.[] | .message.usage.cost.total // 0] | add' <session>.jsonl

Daily cost summary

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

Count messages and tokens in a session

jq -s '{
  messages: length,
  user: [.[] | select(.message.role == "user")] | length,
  assistant: [.[] | select(.message.role == "assistant")] | length,
  first: .[0].timestamp,
  last: .[-1].timestamp
}' <session>.jsonl

Tool usage breakdown

jq -r '.message.content[]? | select(.type == "toolCall") | .name' <session>.jsonl | sort | uniq -c | sort -rn

Daily tool-call volume (find sudden jumps)

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

Cost outlier scan (quick anomaly triage)

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

Threshold anomaly flagger (cost or tool-call spikes)

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.

Compare two sessions (message mix regression)

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

Compact forensic snapshot for one session

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.

Search across ALL sessions for a phrase

rg -l "phrase" ~/.openclaw/agents/<agentId>/sessions/*.jsonl

Tips

  • Sessions are append-only JSONL (one JSON object per line)
  • Large sessions can be several MB - use head/tail for sampling
  • The sessions.json index maps chat providers (discord, whatsapp, etc.) to session IDs
  • Deleted sessions have .deleted.<timestamp> suffix
  • Keep one baseline "healthy" session id around to compare against regressions quickly.

Fast text-only hint (low noise)

jq -r 'select(.type=="message") | .message.content[]? | select(.type=="text") | .text' ~/.openclaw/agents/<agentId>/sessions/<id>.jsonl | rg 'keyword'