session-investigator

Par evalstate · fast-agent

Examinez les fichiers de session et d'historique de fast-agent pour diagnostiquer des problèmes. À utiliser lorsqu'une session s'est terminée de façon inattendue, pour déboguer des boucles d'outils, pour corréler les traces de sous-agents avec les sessions principales, ou pour analyser le flux et la chronologie des conversations. Couvre les métadonnées session.json, le format JSON de l'historique, la structure des messages, la corrélation appel/résultat d'outil, et les schémas d'échec courants.

npx skills add https://github.com/evalstate/fast-agent --skill session-investigator

Session Investigator

Diagnostiquez les problèmes de session fast-agent en examinant les fichiers de session et d'historique.

Structure du répertoire de session

Les sessions sont stockées dans .fast-agent/sessions/<session-id>/:

2601181023-Kob2h3/
├── session.json              # Métadonnées de session
├── history_<agent>.json      # Historique actuel de l'agent
└── history_<agent>_previous.json  # Sauvegarde précédente (rotation)

Les ID de session encodent l'heure de création: YYMMDDHHMM-<random> (ex: 2601181023 = 2026-01-18 10:23).

Fichiers clés

session.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..."
  }
}

history_<agent>.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
    }
  ]
}

Commandes d'investigation

Inspection basique

# Nombre de messages
jq '.messages | length' history_dev.json

# Aperçu des N derniers messages
jq '.messages[-5:] | .[] | {role, stop_reason, has_tool_calls: (.tool_calls != null), has_tool_results: (.tool_results != null)}' history_dev.json

# Afficher un message spécifique
jq '.messages[227]' history_dev.json

Corrélation des appels d'outils

Les appels d'outils et les résultats sont liés par ID de corrélation. Motif valide: assistant avec tool_calls → user avec tool_results correspondants.

# Vérifier l'appairage appel/résultat d'outils
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

Trouver des appels d'outils spécifiques

# Trouver tous les appels à un outil spécifique
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

Statistiques de session

Statistiques d'appels LLM

# Temps LLM total et nombre d'appels
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

# Appels LLM triés par durée (les plus lents d'abord)
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

Statistiques d'exécution d'outils

# Tous les timings d'outils agrégés
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

# Appels d'outils par nom avec 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

Chronologie de session

# Durée de session du premier au dernier 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

# Taux de messages au fil du temps (estimation de messages par minute)
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

Statistiques des sous-agents

# Appels de sous-agents (outils commençant par "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

Motifs d'échec courants

Appel d'outil sans réponse

Symptôme: Erreur API « No tool output found for function call »

Motif: L'historique se termine par un message assistant avec tool_calls et stop_reason: "toolUse", suivi d'un message user SANS tool_results correspondants.

# Vérifier le dernier message pour un appel d'outil en attente
jq '.messages[-1] | {role, has_tool_calls: (.tool_calls != null), stop_reason}' history_dev.json

Cause: Session interrompue au milieu de la boucle d'outils, puis reprise avec une nouvelle entrée utilisateur avant la fin de l'outil.

Correction: Tronquer l'historique au dernier résultat d'outil valide:

# Trouver le dernier message user avec tool_results
jq '.messages | to_entries | map(select(.value.role == "user" and .value.tool_results != null)) | last | .key' history_dev.json

# Tronquer (conserver les messages 0 à N inclus, donc utiliser N+1)
jq '.messages = .messages[0:227]' history_dev.json > /tmp/fixed.json && mv /tmp/fixed.json history_dev.json

Messages utilisateur en doublon

Motif: Deux messages user consécutifs avant la réponse de l'assistant.

Cause: Souvent due à des hooks before_llm_call ajoutant des instructions. Vérifier la configuration tool_hooks de la carte d'agent.

Corrélation des traces de sous-agents

Les traces de sous-agents sont sauvegardées comme <agent_name>-<timestamp>.json dans le répertoire de travail.

# Lister les traces autour de l'heure de session
ls -la ripgrep_search*2026-01-18-10-3*.json

# Corréler via timing - comparer les valeurs de l'horloge monotone
jq '.messages[-1].channels."fast-agent-timing"[0].text' ripgrep_search*.json

Comparer les valeurs start_time/end_time entre la session principale et les traces de sous-agents pour corréler quel appel de sous-agent correspond à quel appel d'outil de la session principale.

Fichier journal

Vérifier fast-agent-log.jsonl pour les erreurs durant la période de la session:

# Filtrer par plage horaire
cat 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

Skills similaires