Use native S-expression for recipe/plan display

- Display recipe's original S-expression when available (code is data)
- Fall back to generating S-expression from plan for legacy JSON
- Run service now prefers .sexp plan files over .json
- Add get_run_plan_sexp() for direct S-expression access

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-12 00:26:05 +00:00
parent f554122b07
commit 65a8170192
3 changed files with 35 additions and 9 deletions

View File

@@ -482,10 +482,28 @@ class RunService:
async def get_run_plan(self, run_id: str) -> Optional[Dict[str, Any]]:
"""Get execution plan for a run."""
plan_path = self.cache_dir / "plans" / f"{run_id}.json"
if plan_path.exists():
with open(plan_path) as f:
return json.load(f)
# Prefer S-expression plan
sexp_path = self.cache_dir / "plans" / f"{run_id}.sexp"
if sexp_path.exists():
with open(sexp_path) as f:
return {"sexp": f.read(), "format": "sexp"}
# Fall back to JSON for legacy plans
json_path = self.cache_dir / "plans" / f"{run_id}.json"
if json_path.exists():
with open(json_path) as f:
plan = json.load(f)
plan["format"] = "json"
return plan
return None
async def get_run_plan_sexp(self, run_id: str) -> Optional[str]:
"""Get execution plan as S-expression string."""
sexp_path = self.cache_dir / "plans" / f"{run_id}.sexp"
if sexp_path.exists():
with open(sexp_path) as f:
return f.read()
return None
async def get_run_artifacts(self, run_id: str) -> List[Dict[str, Any]]: