TradingAgents/.claude/hooks/log_agent_completion.py

132 lines
4.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""
SubagentStop Hook - Log Agent Completions to Structured Session File
This hook is invoked automatically when a subagent completes execution.
It logs the agent's completion to the structured pipeline JSON file.
Hook Type: SubagentStop
Trigger: After any subagent completes (researcher, planner, etc.)
Usage:
Configured in .claude/settings.local.json:
{
"hooks": {
"SubagentStop": [
{
"hooks": [{
"type": "command",
"command": "python .claude/hooks/log_agent_completion.py"
}]
}
]
}
}
Environment Variables (provided by Claude Code):
CLAUDE_AGENT_NAME - Name of the subagent that completed
CLAUDE_AGENT_OUTPUT - Output from the subagent (truncated)
CLAUDE_AGENT_STATUS - Status: "success" or "error"
Output:
Logs completion to docs/sessions/{date}-{time}-pipeline.json
"""
import os
import sys
from pathlib import Path
# Add project root to path for imports
project_root = Path(__file__).resolve().parents[3] # Go up from plugins/autonomous-dev/hooks/
sys.path.insert(0, str(project_root / "scripts"))
try:
from agent_tracker import AgentTracker
except ImportError:
# Fallback if script not found - just log to stderr
print("Warning: agent_tracker.py not found, skipping structured logging", file=sys.stderr)
sys.exit(0)
def main():
"""Log subagent completion to structured pipeline file"""
# Get agent info from environment (provided by Claude Code)
agent_name = os.environ.get("CLAUDE_AGENT_NAME", "unknown")
agent_output = os.environ.get("CLAUDE_AGENT_OUTPUT", "")
agent_status = os.environ.get("CLAUDE_AGENT_STATUS", "success")
# Initialize tracker
tracker = AgentTracker()
# Issue #104: Auto-detect and track Task tool agents before completion
# This ensures agents invoked via Task tool are properly tracked with start entries
# before being marked as completed. The auto_track_from_environment() method is
# idempotent - it returns False if agent is already tracked, preventing duplicates.
#
# Why this matters:
# - Task tool sets CLAUDE_AGENT_NAME when invoking agents
# - Without this call, complete_agent() may create incomplete entries
# - With this call, agents get proper start + completion tracking
# - /pipeline-status now shows accurate "7 of 7" instead of "4 of 7"
if agent_status == "success":
# Extract tools used from output (if available)
# This is best-effort parsing - Claude Code doesn't provide this directly
tools = extract_tools_from_output(agent_output)
# Create summary message (first 100 chars of output)
summary = agent_output[:100].replace("\n", " ") if agent_output else "Completed"
# Auto-track agent first (idempotent - won't duplicate if already tracked)
tracker.auto_track_from_environment(message=summary)
# Then complete the agent (safe because auto_track was called)
tracker.complete_agent(agent_name, summary, tools)
else:
# Extract error message
error_msg = agent_output[:100].replace("\n", " ") if agent_output else "Failed"
# Auto-track even for failures (ensures proper start entry)
tracker.auto_track_from_environment(message=error_msg)
# Then fail the agent
tracker.fail_agent(agent_name, error_msg)
def extract_tools_from_output(output: str) -> list:
"""
Best-effort extraction of tools used from agent output.
Claude Code doesn't provide this directly, so we parse the output.
This is heuristic-based and may not catch everything.
"""
tools = []
# Common tool mentions in output
if "Read tool" in output or "reading file" in output.lower():
tools.append("Read")
if "Write tool" in output or "writing file" in output.lower():
tools.append("Write")
if "Edit tool" in output or "editing file" in output.lower():
tools.append("Edit")
if "Bash tool" in output or "running command" in output.lower():
tools.append("Bash")
if "Grep tool" in output or "searching" in output.lower():
tools.append("Grep")
if "WebSearch" in output or "web search" in output.lower():
tools.append("WebSearch")
if "WebFetch" in output or "fetching URL" in output.lower():
tools.append("WebFetch")
if "Task tool" in output or "invoking agent" in output.lower():
tools.append("Task")
return tools if tools else None
if __name__ == "__main__":
try:
main()
except Exception as e:
# Don't fail the hook - just log error and continue
print(f"Warning: Agent completion logging failed: {e}", file=sys.stderr)
sys.exit(0) # Exit 0 so we don't block workflow