Source code for execution.trace

"""
Script
------
trace.py

Path
----
python/hillstar/trace.py

Purpose
-------
Trace Logger: Comprehensive audit trail for workflow execution.

Logs all events (node execution, errors, model calls) to JSONL file for auditability
and reproducibility. Timestamps all events automatically.

Inputs
------
output_dir (str): Directory to store trace files

Outputs
-------
Trace file (JSONL): One JSON object per line, each representing an event

Assumptions
-----------
- Output directory exists or can be created
- Write permissions to output_dir

Parameters
----------
None (append-only logging)

Failure Modes
-------------
- No write permissions IOError
- Disk full IOError

Author: Julen Gamboa <julen.gamboa.ds@gmail.com>

Created
-------
2026-02-07

Last Edited
-----------
2026-02-08 (enforce traces/ subdirectory)
"""

import json
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, List


[docs] class TraceLogger: """Log all workflow executions for auditability and reproducibility."""
[docs] def __init__(self, output_dir: str): """ Args: output_dir: Directory to store trace files (will use output_dir/traces/) """ # Use traces/ subdirectory for organized output traces_dir = Path(output_dir) / "traces" traces_dir.mkdir(parents=True, exist_ok=True) self.output_dir = str(traces_dir) self.trace_file = str( traces_dir / f"trace_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jsonl" ) self.events: List[Dict] = []
[docs] def log(self, event: Dict[str, Any]) -> None: """ Log a single event. Args: event: Event dictionary (will be timestamped if not present) """ if "timestamp" not in event: event["timestamp"] = datetime.now().isoformat() self.events.append(event) # Write to file immediately (append) with open(self.trace_file, "a") as f: f.write(json.dumps(event) + "\n")
[docs] def finalize(self) -> str: """ Finalize trace and return file path. Returns: Path to trace file """ return self.trace_file
[docs] def get_events(self) -> List[Dict]: """Get all logged events.""" return self.events
[docs] def get_cost_summary(self) -> Dict[str, Any]: """Extract cost summary from logged events.""" total_cost = 0.0 node_costs = {} model_calls = 0 for event in self.events: if event.get("tool") == "model_call" and "actual_cost_usd" in event: node_id = event.get("node_id") cost = event.get("actual_cost_usd", 0) total_cost += cost node_costs[node_id] = cost model_calls += 1 return { "total_cost_usd": total_cost, "model_calls": model_calls, "node_costs": node_costs, }