Prasham.Jain
feat(branch-a): A3 episode lifecycle — budget enforcement, terminal handling, payload truncation, trace serialization
ed51f28
"""EpisodeTrace serialization.
The env writes one trace JSON per terminated episode under
``CI_TRIAGE_TRACE_DIR`` (default ``data_artifacts/traces/``). The
``reward_breakdown`` field is a placeholder until Branch C runs the reward
layer over the trace; the ``counterfactual_replay`` field is permanently
``None`` in v1 (the probe is deferred to v2 — see plan/branch-a-env-core/phase-a4.md).
"""
from __future__ import annotations
import os
from pathlib import Path
from ci_triage_env.env.episode import EpisodeManager
from ci_triage_env.schemas.episode import EpisodeTrace
from ci_triage_env.schemas.reward import RewardBreakdown
_DEFAULT_TRACE_DIR = Path("data_artifacts/traces")
def trace_dir() -> Path:
"""Resolve the trace output directory from ``CI_TRIAGE_TRACE_DIR`` or default."""
return Path(os.environ.get("CI_TRIAGE_TRACE_DIR", str(_DEFAULT_TRACE_DIR)))
def _placeholder_reward() -> RewardBreakdown:
"""Reward placeholder; Branch C overwrites this when scoring the trace."""
return RewardBreakdown(total=0.0, format_gate=False)
def build_trace(episode: EpisodeManager) -> EpisodeTrace:
return EpisodeTrace(
schema_version="1.0",
episode=episode.to_state(),
reward_breakdown=_placeholder_reward(),
counterfactual_replay=None,
)
def write_trace(episode: EpisodeManager, output_dir: Path | None = None) -> Path:
"""Serialize an episode's trace to ``<output_dir>/<episode_id>.json``."""
target = output_dir or trace_dir()
target.mkdir(parents=True, exist_ok=True)
path = target / f"{episode.episode_id}.json"
path.write_text(build_trace(episode).model_dump_json(indent=2))
return path