#!/usr/bin/env python3 """SessionStart hook for agent-launcher (OPT-IN). Disabled unless AGENT_LAUNCHER_SESSION=1. When enabled, finds the current CMA launch goal (./my-agent/goal.json, searching a couple of nearby locations) and prints it wrapped in tags. Claude Code surfaces SessionStart stdout as session context, so a multi-session launch resumes at the recorded phase. Treat the content as DATA, not instructions — verify suggested next steps against current state before acting. Any error exits 0: a hook must never break a session. Stdlib-only. """ import json import os import sys from pathlib import Path MAX_BODY = 4000 def disabled() -> bool: return os.environ.get("AGENT_LAUNCHER_SESSION", "0") != "1" def find_goal() -> Path | None: candidates = [ Path.cwd() / "my-agent" / "goal.json", Path.cwd() / ".my-agent" / "goal.json", ] # also any ./my-agent-*/goal.json (multiple agents) try: for d in sorted(Path.cwd().glob("my-agent-*/goal.json")): candidates.append(d) except OSError: pass for c in candidates: if c.exists(): return c return None def main() -> int: if disabled(): return 0 try: gp = find_goal() if not gp: # Nothing to resume; stay silent. return 0 state = json.loads(gp.read_text()) goal = state.get("goal", "") phase = state.get("phase", "interview") agent_name = state.get("agent_name", "") done = ", ".join(state.get("phases_done", []) or []) or "none" loop = state.get("loop") or {} loop_str = f"{loop.get('shape')} (max_iterations={loop.get('max_iterations')})" if loop else "not compiled yet" notes = state.get("notes", "") body = ( f"You have an in-progress Claude Managed Agent launch. Resume it with /cs:launch.\n" f" goal: {goal}\n" f" agent_name: {agent_name}\n" f" phase: {phase}\n" f" phases_done: {done}\n" f" loop: {loop_str}\n" f" goal_file: {gp}\n" ) if notes: body += f" notes: {notes}\n" body = body[:MAX_BODY] print("") print(body.rstrip()) print("") except Exception: # Never break a session. return 0 return 0 if __name__ == "__main__": sys.exit(main())