claude-skills/agent-launcher/skills/grade-iterate/scripts/outcome_builder.py
Claude d1f2396c6f
feat(agent-launcher): new domain plugin for building Claude Managed Agents
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
2026-08-17 02:39:34 +00:00

102 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""outcome_builder.py — build a CMA user.define_outcome payload (the grade->iterate loop).
The rubric is REQUIRED; max_iterations is clamped to 1..20 (CMA ceiling, default
3). Can pull description + rubric from a build sheet's primitives.outcome, or take
them on the command line. Stdlib-only; no network calls.
Examples:
outcome_builder.py --sheet ./my-agent/build-sheet.json --max-iterations 5 --out ./my-agent/payloads/outcome.json
outcome_builder.py --description "Label every email" --rubric-file rubric.md
outcome_builder.py --sample
"""
import argparse
import json
import sys
from pathlib import Path
MAX_ITER = 20
DEFAULT_ITER = 3
def clamp(n):
try:
n = int(n)
except (TypeError, ValueError):
return DEFAULT_ITER, "non-integer -> default 3"
if n < 1:
return 1, f"{n} < 1 -> 1"
if n > MAX_ITER:
return MAX_ITER, f"{n} > {MAX_ITER} -> clamped {MAX_ITER}"
return n, None
def build(description, rubric, max_iterations):
cap, note = clamp(max_iterations)
if not (rubric or "").strip():
return None, "rubric is required and cannot be empty", cap, note
payload = {
"type": "user.define_outcome",
"description": description.strip(),
"rubric": rubric.strip(),
"max_iterations": cap,
}
return payload, None, cap, note
def main() -> int:
ap = argparse.ArgumentParser(description="Build a user.define_outcome payload (rubric required, max_iterations clamped 1..20).")
ap.add_argument("--sheet", help="build-sheet.json to read primitives.outcome from.")
ap.add_argument("--description", help="Outcome/task description.")
ap.add_argument("--rubric", help="Inline markdown rubric.")
ap.add_argument("--rubric-file", help="Path to a markdown rubric file.")
ap.add_argument("--max-iterations", default=DEFAULT_ITER)
ap.add_argument("--out", help="Write payload JSON here.")
ap.add_argument("--json", action="store_true")
ap.add_argument("--sample", action="store_true")
args = ap.parse_args()
if args.sample:
payload, err, cap, note = build(
"Label every overnight support email with one grounded category.",
"- Every email has exactly one label\n- Reason quotes the email (no invented facts)\n- Urgent = outage / paying-customer blocker",
99,
)
print(json.dumps(payload, indent=2))
print(f"\n(max_iterations note: {note})", file=sys.stderr)
return 0
description = args.description
rubric = args.rubric
if args.rubric_file:
rubric = Path(args.rubric_file).read_text()
if args.sheet:
sheet = json.loads(Path(args.sheet).read_text())
oc = sheet.get("primitives", {}).get("outcome", {})
description = description or oc.get("description") or sheet.get("goal", "")
rubric = rubric or oc.get("rubric", "")
if args.max_iterations == DEFAULT_ITER and oc.get("max_iterations"):
args.max_iterations = oc["max_iterations"]
if not description:
print("Provide --description or --sheet.", file=sys.stderr)
return 2
payload, err, cap, note = build(description, rubric or "", args.max_iterations)
if err:
print(f"ERROR: {err}", file=sys.stderr)
return 1
text = json.dumps(payload, indent=2)
if args.out:
Path(args.out).parent.mkdir(parents=True, exist_ok=True)
Path(args.out).write_text(text + "\n")
print(f"Wrote {args.out} (max_iterations={cap})")
if args.json or not args.out:
print(text)
if note:
print(f"# {note}", file=sys.stderr)
return 0
if __name__ == "__main__":
raise SystemExit(main())