This repository has been archived on 2026-10-29. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
Epiphany/tools/epiphany_mvp_status.py
2026-05-06 08:05:35 +01:00

481 lines
17 KiB
Python

from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import subprocess
from typing import Any
from epiphany_phase5_smoke import AppServerClient
from epiphany_phase5_smoke import DEFAULT_APP_SERVER
from epiphany_phase5_smoke import ROOT
DEFAULT_CODEX_HOME = Path(os.environ.get("CODEX_HOME", Path.home() / ".codex"))
DEFAULT_HEARTBEAT_STORE = ROOT / "state" / "agent-heartbeats.msgpack"
DEFAULT_HEARTBEAT_ARTIFACT_DIR = ROOT / ".epiphany-heartbeats"
DEFAULT_FACE_ARTIFACT_DIR = ROOT / ".epiphany-face"
DEFAULT_TRANSCRIPT = ROOT / ".epiphany-status" / "epiphany-mvp-status-transcript.jsonl"
DEFAULT_STDERR = ROOT / ".epiphany-status" / "epiphany-mvp-status-server.stderr.log"
SEALED_DIRECT_THOUGHT_KEYS = {
"rawResult",
"turns",
"items",
"inputTranscript",
"activeTranscript",
}
def native_epiphany_bin(bin_name: str, *args: str) -> dict[str, Any]:
exe = Path(os.environ.get("CARGO_TARGET_DIR", r"C:\Users\Meta\.cargo-target-codex")) / "debug" / f"{bin_name}.exe"
if exe.exists():
command = [str(exe), *args]
else:
command = [
"cargo",
"run",
"--quiet",
"--manifest-path",
str(ROOT / "epiphany-core" / "Cargo.toml"),
"--bin",
bin_name,
"--",
*args,
]
completed = subprocess.run(command, cwd=ROOT, text=True, capture_output=True, check=False)
if completed.returncode != 0:
raise RuntimeError(completed.stderr.strip() or completed.stdout.strip() or f"{bin_name} failed")
return json.loads(completed.stdout)
def write_transcript_telemetry(transcript_path: Path, telemetry_path: Path) -> dict[str, Any]:
telemetry = native_epiphany_bin(
"epiphany-agent-telemetry",
str(transcript_path.resolve()),
"--output",
str(telemetry_path),
)
if telemetry_path.exists():
return json.loads(telemetry_path.read_text(encoding="utf-8"))
return telemetry
def heartbeat_status(*, artifact_dir: Path = DEFAULT_HEARTBEAT_ARTIFACT_DIR, limit: int = 8) -> dict[str, Any]:
return native_epiphany_bin(
"epiphany-heartbeat-store",
"status",
"--store",
str(DEFAULT_HEARTBEAT_STORE),
"--artifact-dir",
str(artifact_dir),
"--limit",
str(limit),
)
def latest_face_artifacts(artifact_dir: Path, *, limit: int = 8) -> list[dict[str, Any]]:
result = native_epiphany_bin(
"epiphany-face-discord",
"latest",
"--artifact-dir",
str(artifact_dir),
"--limit",
str(limit),
)
return result.get("latestArtifacts", [])
def sealed_direct_thought(key: str, value: Any) -> dict[str, Any]:
length: int | None = None
if isinstance(value, (list, str, dict)):
length = len(value)
sealed: dict[str, Any] = {
"sealed": True,
"key": key,
"reason": (
"Operator-safe dogfood views use projected findings and audit "
"receipts; direct agent transcript/thought payloads stay sealed "
"unless the user explicitly requests forensic debugging."
),
}
if length is not None:
sealed["size"] = length
return sealed
def sanitize_for_operator(value: Any) -> Any:
if isinstance(value, dict):
sanitized: dict[str, Any] = {}
for key, item in value.items():
if key in SEALED_DIRECT_THOUGHT_KEYS:
sanitized[key] = sealed_direct_thought(key, item)
else:
sanitized[key] = sanitize_for_operator(item)
return sanitized
if isinstance(value, list):
return [sanitize_for_operator(item) for item in value]
return value
def maybe(value: Any, fallback: str = "none") -> str:
if value is None:
return fallback
if value == "":
return fallback
return str(value)
def list_text(values: list[Any] | None, fallback: str = "none") -> str:
if not values:
return fallback
return ", ".join(str(value) for value in values)
def job_summary(job: dict[str, Any]) -> str:
progress = ""
processed = job.get("itemsProcessed")
total = job.get("itemsTotal")
if processed is not None or total is not None:
progress = f" ({maybe(processed, '?')}/{maybe(total, '?')})"
backend = job.get("backendJobId") or job.get("runtimeAgentJobId")
backend_text = f", backend {backend}" if backend else ""
return (
f"- {job.get('id')}: {job.get('status')} {job.get('kind')}, "
f"{job.get('ownerRole')} [{job.get('scope')}]{progress}{backend_text}"
)
def job_by_id(jobs: list[dict[str, Any]], job_id: str) -> dict[str, Any] | None:
for job in jobs:
if job.get("id") == job_id:
return job
return None
def collect_status(
client: AppServerClient,
*,
thread_id: str | None,
cwd: Path,
ephemeral: bool,
) -> dict[str, Any]:
if thread_id is None:
started = client.send(
"thread/start",
{"cwd": str(cwd), "ephemeral": ephemeral},
)
if started is None:
raise RuntimeError("thread/start returned no response")
thread_id = started["thread"]["id"]
elif not ephemeral:
client.send("thread/resume", {"threadId": thread_id})
read = client.send("thread/read", {"threadId": thread_id, "includeTurns": False})
scene = client.send("thread/epiphany/scene", {"threadId": thread_id})
pressure = client.send("thread/epiphany/pressure", {"threadId": thread_id})
reorient = client.send("thread/epiphany/reorient", {"threadId": thread_id})
jobs = client.send("thread/epiphany/jobs", {"threadId": thread_id})
roles = client.send("thread/epiphany/roles", {"threadId": thread_id})
planning = client.send("thread/epiphany/planning", {"threadId": thread_id})
role_results = {
"imagination": client.send(
"thread/epiphany/roleResult", {"threadId": thread_id, "roleId": "imagination"}
),
"modeling": client.send(
"thread/epiphany/roleResult", {"threadId": thread_id, "roleId": "modeling"}
),
"verification": client.send(
"thread/epiphany/roleResult", {"threadId": thread_id, "roleId": "verification"}
),
}
reorient_result = client.send("thread/epiphany/reorientResult", {"threadId": thread_id})
crrc = client.send("thread/epiphany/crrc", {"threadId": thread_id})
coordinator = client.send("thread/epiphany/coordinator", {"threadId": thread_id})
heartbeat = heartbeat_status(artifact_dir=DEFAULT_HEARTBEAT_ARTIFACT_DIR)
face = {
"status": "ready",
"artifactDir": str(DEFAULT_FACE_ARTIFACT_DIR),
"latestArtifacts": latest_face_artifacts(DEFAULT_FACE_ARTIFACT_DIR),
"availableActions": ["faceBubble"],
}
status = {
"threadId": thread_id,
"read": read,
"scene": scene,
"pressure": pressure,
"reorient": reorient,
"jobs": jobs,
"roles": roles,
"planning": planning,
"roleResults": role_results,
"reorientResult": reorient_result,
"crrc": crrc,
"coordinator": coordinator,
"heartbeat": heartbeat,
"face": face,
}
return status
def render_status(status: dict[str, Any]) -> str:
thread_id = status["threadId"]
scene = status["scene"]["scene"]
pressure = status["pressure"]["pressure"]
reorient = status["reorient"]["decision"]
jobs = status["jobs"]["jobs"]
result = status["reorientResult"]
crrc = status["crrc"]
roles = status["roles"]["roles"]
planning_response = status.get("planning") or {}
planning_summary = planning_response.get("summary") or {}
planning_state = planning_response.get("planning") or {}
objective_drafts = planning_state.get("objective_drafts") or []
role_results = status.get("roleResults") or {}
recommendation = crrc["recommendation"]
coordinator = status.get("coordinator") or {}
heartbeat = status.get("heartbeat") or {}
face = status.get("face") or {}
checkpoint = scene.get("investigationCheckpoint") or {}
lines = [
"Epiphany MVP Status",
f"Thread: {thread_id}",
f"State: {scene['stateStatus']} rev {maybe(scene.get('revision'))} ({scene['source']})",
"",
"Recommendation",
f"- action: {recommendation['action']}",
f"- scene action: {maybe(recommendation.get('recommendedSceneAction'))}",
f"- reason: {recommendation['reason']}",
f"- coordinator: {maybe(coordinator.get('action'))} ({maybe(coordinator.get('targetRole'))})",
"",
"Continuity",
(
f"- pressure: {pressure['level']} ({pressure['status']}, "
f"prepare={str(pressure['shouldPrepareCompaction']).lower()})"
),
f"- reorient: {reorient['action']} via {list_text(reorient.get('reasons'))}",
f"- next: {reorient['nextAction']}",
f"- result: {result['status']} for {result['bindingId']}",
]
latest_heartbeat = heartbeat.get("latestEvent") or {}
latest_face = (face.get("latestArtifacts") or [{}])[0]
lines.extend(
[
"",
"Heartbeat",
(
f"- status: {maybe(heartbeat.get('status'))}; "
f"clock {maybe(heartbeat.get('sceneClock'))}; "
f"rate {maybe(heartbeat.get('targetHeartbeatRate'))}"
),
(
f"- latest: {maybe(latest_heartbeat.get('selectedRole'))} / "
f"{maybe(latest_heartbeat.get('actionType'))} / "
f"{maybe(latest_heartbeat.get('coordinatorAction'))}"
),
"",
"Face",
f"- latest artifact: {maybe(latest_face.get('name'))}",
f"- latest content: {maybe(latest_face.get('content'))}",
]
)
finding = result.get("finding")
if finding:
lines.extend(
[
f"- finding mode: {maybe(finding.get('mode'))}",
f"- finding next: {maybe(finding.get('nextSafeMove'))}",
]
)
lines.extend(
[
"",
"Planning",
(
f"- state: {maybe(planning_response.get('stateStatus'))} rev "
f"{maybe(planning_response.get('stateRevision'))}"
),
(
f"- captures: {maybe(planning_summary.get('captureCount'), '0')} "
f"(pending {maybe(planning_summary.get('pendingCaptureCount'), '0')}, "
f"github {maybe(planning_summary.get('githubIssueCaptureCount'), '0')})"
),
(
f"- backlog: {maybe(planning_summary.get('backlogItemCount'), '0')} "
f"(ready {maybe(planning_summary.get('readyBacklogItemCount'), '0')})"
),
(
f"- roadmap streams: {maybe(planning_summary.get('roadmapStreamCount'), '0')}; "
f"objective drafts: {maybe(planning_summary.get('objectiveDraftCount'), '0')} "
f"(draft {maybe(planning_summary.get('draftObjectiveCount'), '0')})"
),
f"- active objective: {maybe(planning_summary.get('activeObjective'))}",
f"- note: {maybe(planning_summary.get('note'))}",
]
)
if objective_drafts:
draft = objective_drafts[0]
lines.append(
f"- first draft: {maybe(draft.get('id'))} / {maybe(draft.get('status'))} - "
f"{maybe(draft.get('title'))}"
)
lines.extend(
[
"",
"Role Lanes",
]
)
for lane in roles:
lines.append(
f"- {lane['title']}: {lane['status']} ({lane['ownerRole']}) - {lane['note']}"
)
lines.extend(
[
"",
"Role Findings",
]
)
role_labels = {
"imagination": "Imagination / Planning",
"modeling": "Modeling / Checkpoint",
"verification": "Verification / Review",
}
for role_id in ("imagination", "modeling", "verification"):
role_result = role_results.get(role_id) or {}
finding = role_result.get("finding")
label = role_labels[role_id]
lines.append(
f"- {label}: {maybe(role_result.get('status'))} for "
f"{maybe(role_result.get('bindingId'))}"
)
if finding:
lines.append(f" verdict: {maybe(finding.get('verdict'))}")
lines.append(f" summary: {maybe(finding.get('summary'))}")
lines.append(f" next: {maybe(finding.get('nextSafeMove'))}")
lines.append(
" state patch: "
+ ("available" if isinstance(finding.get("statePatch"), dict) else "none")
)
self_persistence = finding.get("selfPersistence") or {}
if self_persistence:
lines.append(
" self persistence: "
+ maybe(self_persistence.get("status"))
+ " for "
+ maybe(self_persistence.get("targetAgentId"))
)
if self_persistence.get("reasons"):
lines.append(
" self persistence reasons: "
+ "; ".join(map(str, self_persistence["reasons"]))
)
if finding.get("openQuestions"):
lines.append(f" open questions: {', '.join(map(str, finding['openQuestions']))}")
if finding.get("evidenceGaps"):
lines.append(f" evidence gaps: {', '.join(map(str, finding['evidenceGaps']))}")
if finding.get("risks"):
lines.append(f" risks: {', '.join(map(str, finding['risks']))}")
lines.extend(
[
"",
"Checkpoint",
f"- id: {maybe(checkpoint.get('checkpointId'))}",
f"- disposition: {maybe(checkpoint.get('disposition'))}",
f"- focus: {maybe(checkpoint.get('focus'))}",
f"- next: {maybe(checkpoint.get('nextAction'))}",
"",
"Jobs",
]
)
if jobs:
lines.extend(job_summary(job) for job in jobs)
else:
lines.append("- none")
lines.extend(
[
"",
"Available Actions",
f"- {list_text(scene.get('availableActions'))}",
]
)
return "\n".join(lines) + "\n"
def run(args: argparse.Namespace) -> dict[str, Any]:
app_server = args.app_server.resolve()
if not app_server.exists():
raise FileNotFoundError(f"codex app-server binary not found: {app_server}")
codex_home = args.codex_home.resolve()
transcript_path = args.transcript.resolve()
stderr_path = args.stderr.resolve()
cwd = args.cwd.resolve()
codex_home.mkdir(parents=True, exist_ok=True)
with AppServerClient(app_server, codex_home, transcript_path, stderr_path) as client:
client.send(
"initialize",
{
"clientInfo": {
"name": "epiphany-mvp-status",
"title": "Epiphany MVP Status",
"version": "0.1.0",
},
"capabilities": {"experimentalApi": True},
},
)
client.send("initialized", expect_response=False)
status = collect_status(
client,
thread_id=args.thread_id,
cwd=cwd,
ephemeral=args.ephemeral,
)
status = sanitize_for_operator(status)
write_transcript_telemetry(transcript_path, transcript_path.with_suffix(".telemetry.json"))
if args.result is not None:
result_path = args.result.resolve()
result_path.parent.mkdir(parents=True, exist_ok=True)
result_path.write_text(
json.dumps(status, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
write_transcript_telemetry(transcript_path, result_path.with_suffix(".telemetry.json"))
return status
def main() -> int:
parser = argparse.ArgumentParser(
description="Print a compact Epiphany MVP operator view for one thread."
)
parser.add_argument("--app-server", type=Path, default=DEFAULT_APP_SERVER)
parser.add_argument("--codex-home", type=Path, default=DEFAULT_CODEX_HOME)
parser.add_argument("--thread-id")
parser.add_argument("--cwd", type=Path, default=ROOT)
parser.add_argument("--ephemeral", action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--json", action="store_true", help="Print operator-safe collected JSON.")
parser.add_argument("--result", type=Path)
parser.add_argument("--transcript", type=Path, default=DEFAULT_TRANSCRIPT)
parser.add_argument("--stderr", type=Path, default=DEFAULT_STDERR)
args = parser.parse_args()
status = run(args)
if args.json:
print(json.dumps(status, indent=2, ensure_ascii=False))
else:
print(render_status(status), end="")
return 0
if __name__ == "__main__":
raise SystemExit(main())