"""S10 — the ONE live model run of the programme (D6). RUN-PATH ONLY. Wires the offline-proven pipeline (S6–S9) to the real Claude Agent SDK client for a single controlled run on the micro-bundle: startup contracts validated fail-fast BEFORE the client is built (§10), citations proven citable BEFORE any spend (§9), the read-context navigated + experience-folded exactly as the offline suite proves (§3 Step 1), the loop bounded by the §8 meter AND the per-call USD cap, and the captured artifacts persisted for S11 with the cost logged. Never imported by the test suite. Run: uv run python -m portfolio_optimiser_claude.run_s10 """ from __future__ import annotations import argparse import os from pathlib import Path from portfolio_optimiser_claude.artifacts import build_citations, persist_run_artifacts from portfolio_optimiser_claude.budget import BudgetExceeded, BudgetMeter from portfolio_optimiser_claude.contracts import load_contracts from portfolio_optimiser_claude.experience import ( CandidateFeatures, VerdictStore, fold_experience, seed_store_from_bundle, ) from portfolio_optimiser_claude.ir import load_validator_input from portfolio_optimiser_claude.loop import run_project from portfolio_optimiser_claude.okf import bundle_context, navigate_bundle from portfolio_optimiser_claude.provenance import Provenance from portfolio_optimiser_claude.sdk_client import SdkModelClient from portfolio_optimiser_claude.validator import Rejection _DEFAULT_BUNDLE = Path(__file__).resolve().parents[2] / "shared" / "examples" / "bygg-energi-mikro" _PROPOSER_ROLE = "proposer" def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="S10: the single live run (hard caps, D6).") parser.add_argument("--bundle", type=Path, default=_DEFAULT_BUNDLE) parser.add_argument("--out", type=Path, default=Path("runs") / "s10") parser.add_argument("--max-rounds", type=int, default=12) parser.add_argument("--max-tokens", type=int, default=150_000) parser.add_argument("--max-budget-usd-per-call", type=float, default=0.25) parser.add_argument("--max-debate-rounds", type=int, default=3) parser.add_argument("--max-attempts", type=int, default=3) parser.add_argument("--top-k", type=int, default=3) args = parser.parse_args(argv) if not os.environ.get("ANTHROPIC_API_KEY"): # The bundled CLI resolves its own credentials (Claude Code login) when # no key is exported; an unauthenticated run fails on the FIRST call. print("note: ANTHROPIC_API_KEY not set — relying on the CLI's own credentials.") # §10: ALL startup contracts schema-validated BEFORE any model client exists. contracts = load_contracts( data_source={"docs_dir": str(args.bundle), "top_k": args.top_k}, termination={"max_rounds": args.max_rounds, "max_tokens": args.max_tokens}, feedback={"decision": "approved", "rationale": "startup shape check (§10)"}, ) # §9: prove the context citable BEFORE any spend; §3 Step 1: navigate + fold. concepts = navigate_bundle(args.bundle) citations = build_citations(concepts) ir_projection = load_validator_input(args.bundle) store = VerdictStore() seeded = seed_store_from_bundle(store, args.bundle) context = fold_experience( store, CandidateFeatures.from_proposal(ir_projection), bundle_context(args.bundle), contracts.data_source.top_k, ) meter = BudgetMeter(contracts.termination) client = SdkModelClient( contracts.model_map, max_budget_usd_per_call=args.max_budget_usd_per_call ) print( f"S10 live run: bundle={args.bundle.name} seeded_verdicts={seeded} " f"caps: max_rounds={args.max_rounds} max_tokens={args.max_tokens} " f"max_budget_usd_per_call={args.max_budget_usd_per_call}" ) try: result = run_project( client, context, meter=meter, max_debate_rounds=args.max_debate_rounds, max_attempts=args.max_attempts, default_project_id=ir_projection.project_id, ) except BudgetExceeded as stop: # §8: the structured stop event — report it, never a silent hang. print(f"STOPPED by budget: {stop.kind} observed {stop.observed} > limit {stop.limit}") print(f"cost so far: {client.total_cost_usd:.6f} USD") return 3 provenance = Provenance( citations=citations, model=client.last_model or "unknown", # §9: the REAL id, neutral fallback role=_PROPOSER_ROLE, validator_decision=result.validator_decision, tokens_used=meter.tokens_used, ) paths = persist_run_artifacts( args.out, run=result, provenance=provenance, termination=contracts.termination, tokens_used=meter.tokens_used, rounds_used=meter.rounds_used, cost_usd=round(client.total_cost_usd, 6), ) outcome_kind = "rejected" if isinstance(result.outcome, Rejection) else "validated" print( f"result: validator={result.validator_decision} checker={result.checker_decision} " f"attempts={result.attempts} outcome={outcome_kind}" ) print( f"usage: tokens={meter.tokens_used}/{contracts.termination.max_tokens} " f"rounds={meter.rounds_used}/{contracts.termination.max_rounds}" ) print(f"model: {client.last_model} cost: {client.total_cost_usd:.6f} USD") for name, path in sorted(paths.items()): print(f"artifact: {name} -> {path}") return 0 if __name__ == "__main__": raise SystemExit(main())