Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Investigate fast-agent session and history files to diagnose issues. Use when a session ended unexpectedly, when debugging tool loops, when correlating sub-agent traces with main sessions, or when analyzing conversation flow and timing. Covers session.json metadata, history JSON format, message structure, tool call/result correlation, and common failure patterns.
.claude/skills/evalstate-session-investigator/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | 85% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 65% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 66% | 0% |
| case-15 | ✗→✓ | ▲ Improved | -30% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 60% | 0% |
Diagnose fast-agent session issues by examining session and history files.
Sessions are stored in .fast-agent/sessions/<session-id>/:
2601181023-Kob2h3/
├── session.json # Session metadata
├── history_<agent>.json # Current agent history
└── history_<agent>_previous.json # Previous save (rotation backup)Session IDs encode creation time: YYMMDDHHMM-<random> (e.g., 2601181023 = 2026-01-18 10:23).
json{ "name": "2601181023-Kob2h3", "created_at": "2026-01-18T10:23:24.116526", "last_activity": "2026-01-18T10:39:42.873467", "history_files": ["history_dev_previous.json", "history_dev.json"], "metadata": { "agent_name": "dev", "first_user_preview": "is it possible to override..." } }
json{ "messages": [ { "role": "user|assistant", "content": [{"type": "text", "text": "..."}], "tool_calls": {"<id>": {"method": "tools/call", "params": {"name": "...", "arguments": {}}}}, "tool_results": {"<id>": {"content": [...], "isError": false}}, "channels": { "fast-agent-timing": [{"type": "text", "text": "{\"start_time\": ..., \"end_time\": ..., \"duration_ms\": ...}"}], "fast-agent-tool-timing": [{"type": "text", "text": "{\"<tool_id>\": {\"timing_ms\": ..., \"transport_channel\": ...}}"}], "reasoning": [{"type": "text", "text": "..."}] }, "stop_reason": "endTurn|toolUse|error", "is_template": false } ] }
bash# Message count jq '.messages | length' history_dev.json # Last N messages overview jq '.messages[-5:] | .[] | {role, stop_reason, has_tool_calls: (.tool_calls != null), has_tool_results: (.tool_results != null)}' history_dev.json # View specific message jq '.messages[227]' history_dev.json
Tool calls and results are linked by correlation ID. Valid pattern: assistant with tool_calls → user with matching tool_results.
bash# Check tool call/result pairing jq '.messages[-10:] | to_entries | .[] | { index: .key, role: .value.role, tool_calls: (if .value.tool_calls then (.value.tool_calls | keys) else [] end), tool_results: (if .value.tool_results then (.value.tool_results | keys) else [] end) }' history_dev.json
bash# Find all calls to a specific tool jq '.messages | to_entries | .[] | select(.value.tool_calls != null) | select(.value.tool_calls | to_entries | .[0].value.params.name == "agent__ripgrep_search") | {index: .key, timing: (.value.channels."fast-agent-timing"[0].text)}' history_dev.json
bash# Total LLM time and call count jq '[.messages[] | select(.role == "assistant") | select(.channels."fast-agent-timing") | .channels."fast-agent-timing"[0].text | fromjson | .duration_ms] | {count: length, total_ms: add, avg_ms: (add/length), max_ms: max, min_ms: min}' history_dev.json # LLM calls sorted by duration (slowest first) jq '[.messages | to_entries | .[] | select(.value.role == "assistant") | select(.value.channels."fast-agent-timing") | {index: .key, duration_ms: (.value.channels."fast-agent-timing"[0].text | fromjson | .duration_ms)}] | sort_by(-.duration_ms) | .[0:10]' history_dev.json
bash# All tool timings aggregated jq '[.messages[] | select(.channels."fast-agent-tool-timing") | .channels."fast-agent-tool-timing"[0].text | fromjson | to_entries | .[].value.timing_ms] | {count: length, total_ms: add, avg_ms: (add/length), max_ms: max, min_ms: min}' history_dev.json # Tool calls by name with timing jq '[.messages | to_entries | .[] | select(.value.tool_calls) | (.value.tool_calls | to_entries | .[0]) as $tc | {index: .key, tool: $tc.value.params.name, llm_ms: (.value.channels."fast-agent-timing"[0].text | fromjson | .duration_ms)}] | group_by(.tool) | map({tool: .[0].tool, count: length, total_llm_ms: (map(.llm_ms) | add)}) | sort_by(-.count)' history_dev.json
bash# Session duration from first to last timing jq '.messages | [ (map(select(.channels."fast-agent-timing")) | first | .channels."fast-agent-timing"[0].text | fromjson | .start_time), (map(select(.channels."fast-agent-timing")) | last | .channels."fast-agent-timing"[0].text | fromjson | .end_time) ] | {start: .[0], end: .[1], duration_sec: ((.[1] - .[0]) | round)}' history_dev.json # Message rate over time (messages per minute estimate) jq '{ messages: (.messages | length), llm_calls: [.messages[] | select(.role == "assistant" and .channels."fast-agent-timing")] | length, total_llm_ms: [.messages[] | select(.channels."fast-agent-timing") | .channels."fast-agent-timing"[0].text | fromjson | .duration_ms] | add, total_tool_ms: [.messages[] | select(.channels."fast-agent-tool-timing") | .channels."fast-agent-tool-timing"[0].text | fromjson | to_entries | .[].value.timing_ms] | add } | . + {llm_sec: (.total_llm_ms/1000), tool_sec: ((.total_tool_ms//0)/1000)}' history_dev.json
bash# Sub-agent calls (tools starting with "agent__") jq '[.messages | to_entries | .[] | select(.value.tool_calls) | (.value.tool_calls | to_entries | .[0]) as $tc | select($tc.value.params.name | startswith("agent__")) | {index: .key, agent: $tc.value.params.name, llm_ms: (.value.channels."fast-agent-timing"[0].text | fromjson | .duration_ms)}] | group_by(.agent) | map({agent: .[0].agent, calls: length, total_ms: (map(.llm_ms) | add), avg_ms: ((map(.llm_ms) | add) / length)})' history_dev.json
Symptom: API error "No tool output found for function call"
Pattern: History ends with assistant message having tool_calls and stop_reason: "toolUse", followed by user message WITHOUT matching tool_results.
bash# Check last message for pending tool call jq '.messages[-1] | {role, has_tool_calls: (.tool_calls != null), stop_reason}' history_dev.json
Cause: Session interrupted mid-tool-loop, then resumed with new user input before tool completed.
Fix: Truncate history to last valid tool result:
bash# Find last user message with tool_results jq '.messages | to_entries | map(select(.value.role == "user" and .value.tool_results != null)) | last | .key' history_dev.json # Truncate (keep messages 0 to N inclusive, so use N+1) jq '.messages = .messages[0:227]' history_dev.json > /tmp/fixed.json && mv /tmp/fixed.json history_dev.json
Pattern: Two consecutive user messages before assistant response.
Cause: Often from before_llm_call hooks appending instructions. Check agent card's tool_hooks configuration.
Sub-agent traces are saved as <agent_name>-<timestamp>.json in the working directory.
bash# List traces around session time ls -la ripgrep_search*2026-01-18-10-3*.json # Correlate via timing - match monotonic clock values jq '.messages[-1].channels."fast-agent-timing"[0].text' ripgrep_search*.json
Compare start_time/end_time values between main session and sub-agent traces to correlate which sub-agent call corresponds to which main session tool call.
Check <fast-agent-home>/fast-agent-log.jsonl for errors during the session timeframe:
bash# Filter by timestamp range cat .fast-agent/fast-agent-log.jsonl | while read line; do ts=$(echo "$line" | jq -r '.timestamp // empty' 2>/dev/null) if [[ "$ts" > "2026-01-18T10:20" && "$ts" < "2026-01-18T10:45" ]]; then echo "$line" | jq -c '{timestamp, level, message}' fi done
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 3,476 | 3,217 | -7% | 1 | 1 | 0% | 303 | 2,964 | +878% | 0 | 0 | — |
case-02 | fail→fail | 15,133 | 3,056 | -80% | 1 | 1 | 0% | 2,813 | 2,901 | +3% | 0 | 0 | — |
case-03 | fail→fail | 7,031 | 7,452 | +6% | 1 | 1 | 0% | 426 | 2,961 | +595% | 0 | 0 | — |
case-04 | pass→pass | 10,336 | 8,075 | -22% | 1 | 1 | 0% | 2,052 | 4,133 | +101% | 0 | 0 | — |
case-05 | pass→pass | 10,743 | 6,507 | -39% | 1 | 1 | 0% | 2,226 | 3,806 | +71% | 0 | 0 | — |
case-06 | pass→pass | 9,613 | 4,724 | -51% | 1 | 1 | 0% | 1,823 | 3,607 | +98% | 0 | 0 | — |
case-07 | pass→pass | 6,307 | 1,205 | -81% | 1 | 1 | 0% | 1,228 | 2,728 | +122% | 0 | 0 | — |
case-08 | pass→pass | 8,891 | 1,899 | -79% | 1 | 1 | 0% | 2,036 | 2,915 | +43% | 0 | 0 | — |
case-09 | pass→pass | 6,691 | 5,026 | -25% | 1 | 1 | 0% | 1,382 | 3,591 | +160% | 0 | 0 | — |
case-10 | pass→pass | 7,780 | 7,884 | +1% | 1 | 1 | 0% | 1,495 | 4,426 | +196% | 0 | 0 | — |
case-11 | fail→pass | 8,511 | 3,194 | -62% | 1 | 1 | 0% | 1,793 | 3,313 | +85% | 0 | 0 | — |
case-12 | fail→pass | 11,712 | 7,109 | -39% | 1 | 1 | 0% | 2,488 | 4,098 | +65% | 0 | 0 | — |
case-13 | fail→pass | 10,751 | 5,745 | -47% | 1 | 1 | 0% | 2,402 | 3,989 | +66% | 0 | 0 | — |
case-14 | pass→pass | 11,822 | 4,019 | -66% | 1 | 1 | 0% | 2,590 | 3,439 | +33% | 0 | 0 | — |
case-15 | fail→pass | 23,763 | 3,424 | -86% | 1 | 1 | 0% | 4,820 | 3,390 | -30% | 0 | 0 | — |
case-16 | fail→pass | 9,813 | 4,264 | -57% | 1 | 1 | 0% | 2,171 | 3,467 | +60% | 0 | 0 | — |
case-17 | fail→pass | 21,895 | 2,722 | -88% | 1 | 1 | 0% | 1,602 | 3,119 | +95% | 0 | 0 | — |
case-18 | fail→pass | 11,174 | 5,702 | -49% | 1 | 1 | 0% | 2,012 | 3,681 | +83% | 0 | 0 | — |
case-19 | fail→pass | 11,017 | 3,986 | -64% | 1 | 1 | 0% | 2,066 | 2,909 | +41% | 0 | 0 | — |
case-20 | fail→pass | 29,664 | 7,374 | -75% | 1 | 1 | 0% | 2,159 | 4,186 | +94% | 0 | 0 | — |
case-21 | pass→pass | 9,215 | 5,384 | -42% | 1 | 1 | 0% | 2,107 | 3,867 | +84% | 0 | 0 | — |
case-22 | pass→pass | 8,793 | 2,456 | -72% | 1 | 1 | 0% | 1,908 | 3,093 | +62% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 19 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +41 percentage points is the difference between those two pass rates over the 19 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.