mirror of
https://github.com/alirezarezvani/claude-skills.git
synced 2026-10-07 02:58:13 +00:00
Adds the agent-launcher/ top-level domain — a plugin re-implementation of Anthropic's launch-your-agent reference skill (Apache-2.0; independent, not a fork) for building Claude Managed Agents (CMA) in the user's own account. Every session starts with a goal (./my-agent/goal.json, surfaced by an opt-in AGENT_LAUNCHER_SESSION=1 SessionStart hook + /cs:goal); loop_compiler.py compiles that goal into a bounded grade->iterate loop (CMA user.define_outcome self-grading, max_iterations 1..20), a recurring POSIX-cron scheduled-deployment loop, or a single-pass interview->stage->launch workflow. - 6 skills: agent-launcher-orchestrator (context: fork goal router) + interview + stage-launch + grade-iterate + run-without-you + wrap-up - 18 stdlib-only deterministic scaffolder tools (NO network/API calls; live launches emitted as BYOK curl that never prints the key); all pass --help/--sample - 4 agents (orchestrator + interviewer + grader + deployer), 8 /cs:* commands - opt-in SessionStart/SessionEnd hooks (exit 0 on any error), 5 shared references, 4 assets (build-sheet schema + overview/next-directions templates + example) - validators enforce CMA limits (<=20 skills/session, <=8 memory stores, depth-1 multiagent, max_iterations <=20, <=1000 deployments/org) - registered in marketplace.json; headline counters trued up via derive_counters.py --check (skills 362->368, domains 18->19, tools 644->664, refs 741->746, agents 102->106, commands 116->124, plugins 88->89) Distinct from engineering/agent-harness (generic bounded loop over any domain) and engineering/write-a-skill (authors Claude Code skills, not CMAs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012FwXG6TqCXKZQvF4iD69cv
109 lines
4.2 KiB
Python
109 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""verdict_reader.py — read a grader result and decide the next move.
|
|
|
|
Reads a CMA outcome result (from a saved SSE event or a JSON file you paste) and
|
|
tables the rubric outcome, then recommends the next move:
|
|
satisfied -> SHIP (or promote to schedule)
|
|
needs_revision -> SHARPEN (prompt/tools) then RE-RUN
|
|
max_iterations_reached / failed -> ESCALATE to the founder
|
|
interrupted -> RESUME
|
|
Stdlib-only; no network calls.
|
|
|
|
Result JSON shape (minimal):
|
|
{"status": "needs_revision", "iteration": 2, "max_iterations": 5,
|
|
"rubric_results": [{"criterion": "...", "pass": true, "note": "..."}],
|
|
"explanation": "..."}
|
|
|
|
Examples:
|
|
verdict_reader.py --result ./my-agent/last-verdict.json
|
|
verdict_reader.py --sample
|
|
"""
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
NEXT_MOVE = {
|
|
"satisfied": ("SHIP", "Rubric satisfied. Ship v0, or promote to a scheduled deployment (Phase 4)."),
|
|
"needs_revision": ("SHARPEN", "Fix the failing rubric lines (prompt/tools/inputs), then re-run."),
|
|
"max_iterations_reached": ("ESCALATE", "Cap hit without convergence. Escalate to the founder; re-scope the rubric or the tools."),
|
|
"failed": ("ESCALATE", "Run failed. Read the explanation; likely a tool/permission/setup issue to fix before re-run."),
|
|
"interrupted": ("RESUME", "Send a user.message to resume; checkpoints last 30 days."),
|
|
}
|
|
|
|
|
|
def read(result: dict):
|
|
status = result.get("status", "unknown")
|
|
move, why = NEXT_MOVE.get(status, ("INVESTIGATE", f"Unknown status '{status}'. Inspect the raw event."))
|
|
rr = result.get("rubric_results", []) or []
|
|
passed = [r for r in rr if r.get("pass")]
|
|
failed = [r for r in rr if not r.get("pass")]
|
|
it = result.get("iteration")
|
|
mx = result.get("max_iterations")
|
|
budget_note = None
|
|
if it is not None and mx:
|
|
remaining = mx - it
|
|
budget_note = f"{it}/{mx} iterations used, {remaining} left"
|
|
if status == "needs_revision" and remaining <= 1:
|
|
move = "SHARPEN-OR-ESCALATE"
|
|
why = "One iteration left — make the single highest-value fix, or escalate now rather than waste the cap."
|
|
return {
|
|
"status": status,
|
|
"next_move": move,
|
|
"why": why,
|
|
"passed": [r.get("criterion", "?") for r in passed],
|
|
"failed": [{"criterion": r.get("criterion", "?"), "note": r.get("note", "")} for r in failed],
|
|
"budget": budget_note,
|
|
"explanation": result.get("explanation", ""),
|
|
}
|
|
|
|
|
|
def _emit(v, as_json):
|
|
if as_json:
|
|
print(json.dumps(v, indent=2))
|
|
return
|
|
print(f"STATUS: {v['status']} NEXT MOVE: {v['next_move']}")
|
|
print(f" {v['why']}")
|
|
if v.get("budget"):
|
|
print(f" budget: {v['budget']}")
|
|
if v["passed"]:
|
|
print(f" passed ({len(v['passed'])}): " + "; ".join(v["passed"]))
|
|
if v["failed"]:
|
|
print(f" FAILED ({len(v['failed'])}):")
|
|
for f in v["failed"]:
|
|
note = f" — {f['note']}" if f["note"] else ""
|
|
print(f" - {f['criterion']}{note}")
|
|
if v["explanation"]:
|
|
print(f" grader: {v['explanation']}")
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser(description="Read a grader verdict and recommend the next move.")
|
|
ap.add_argument("--result", help="Path to a grader result JSON.")
|
|
ap.add_argument("--json", action="store_true")
|
|
ap.add_argument("--sample", action="store_true")
|
|
args = ap.parse_args()
|
|
|
|
if args.sample:
|
|
sample = {
|
|
"status": "needs_revision", "iteration": 4, "max_iterations": 5,
|
|
"rubric_results": [
|
|
{"criterion": "Every email labeled", "pass": True},
|
|
{"criterion": "No invented facts", "pass": False, "note": "row 7 invented an SLA"},
|
|
{"criterion": "Urgent precision", "pass": True},
|
|
],
|
|
"explanation": "Close, but one reason wasn't grounded in the email text.",
|
|
}
|
|
_emit(read(sample), False)
|
|
return 0
|
|
|
|
if not args.result:
|
|
print("Provide --result path.", file=sys.stderr)
|
|
return 2
|
|
result = json.loads(Path(args.result).read_text())
|
|
_emit(read(result), args.json)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|