agentloop-profiler 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. agentloop/__init__.py +136 -0
  2. agentloop/ablation_protocol.py +229 -0
  3. agentloop/ablations.py +564 -0
  4. agentloop/aggregate_cli.py +88 -0
  5. agentloop/aggregate_stats.py +175 -0
  6. agentloop/aggregates.py +532 -0
  7. agentloop/audit.py +39 -0
  8. agentloop/autoinstrument.py +145 -0
  9. agentloop/batch_experiments.py +295 -0
  10. agentloop/batching.py +95 -0
  11. agentloop/budget_types.py +335 -0
  12. agentloop/budgets.py +539 -0
  13. agentloop/calibration.py +872 -0
  14. agentloop/checkpoint_state.py +306 -0
  15. agentloop/checkpoint_store.py +86 -0
  16. agentloop/ci.py +202 -0
  17. agentloop/cli.py +1314 -0
  18. agentloop/client.py +228 -0
  19. agentloop/completion.py +345 -0
  20. agentloop/completion_types.py +199 -0
  21. agentloop/config.py +50 -0
  22. agentloop/context_controls.py +443 -0
  23. agentloop/context_experiments.py +337 -0
  24. agentloop/context_types.py +216 -0
  25. agentloop/costs.py +667 -0
  26. agentloop/cursor_validation.py +48 -0
  27. agentloop/decorators.py +445 -0
  28. agentloop/demo.py +300 -0
  29. agentloop/doctor.py +341 -0
  30. agentloop/drift.py +361 -0
  31. agentloop/drift_cli.py +147 -0
  32. agentloop/drift_types.py +110 -0
  33. agentloop/drift_windows.py +340 -0
  34. agentloop/entrypoint.py +201 -0
  35. agentloop/estimates.py +133 -0
  36. agentloop/events.py +75 -0
  37. agentloop/experiment_artifacts.py +80 -0
  38. agentloop/experiment_observations.py +169 -0
  39. agentloop/experiment_reports.py +545 -0
  40. agentloop/experiment_types.py +355 -0
  41. agentloop/experiments.py +392 -0
  42. agentloop/exporters.py +117 -0
  43. agentloop/finding_benchmark_types.py +131 -0
  44. agentloop/finding_benchmarks.py +424 -0
  45. agentloop/findings.py +419 -0
  46. agentloop/graph.py +464 -0
  47. agentloop/harness.py +825 -0
  48. agentloop/harness_bench/__init__.py +1 -0
  49. agentloop/harness_bench/cli.py +77 -0
  50. agentloop/harness_bench/drivers.py +96 -0
  51. agentloop/harness_bench/offline.py +507 -0
  52. agentloop/harness_evidence.py +585 -0
  53. agentloop/harness_usage.py +91 -0
  54. agentloop/html_report.py +860 -0
  55. agentloop/integrations/__init__.py +1 -0
  56. agentloop/integrations/crewai.py +144 -0
  57. agentloop/integrations/harbor/__init__.py +1 -0
  58. agentloop/integrations/harbor/atif.py +982 -0
  59. agentloop/integrations/harbor/atif_validation.py +329 -0
  60. agentloop/integrations/harbor/cli.py +207 -0
  61. agentloop/integrations/harbor/manifest.py +86 -0
  62. agentloop/integrations/harbor/trial_evidence.py +196 -0
  63. agentloop/integrations/harbor/trials.py +1109 -0
  64. agentloop/integrations/harbor/verifier.py +118 -0
  65. agentloop/integrations/langgraph.py +150 -0
  66. agentloop/integrations/langgraph_harness.py +466 -0
  67. agentloop/integrations/langgraph_recovery.py +366 -0
  68. agentloop/integrations/omnigent/__init__.py +1 -0
  69. agentloop/integrations/omnigent/cli.py +78 -0
  70. agentloop/integrations/omnigent/evidence.py +93 -0
  71. agentloop/integrations/omnigent/telemetry.py +422 -0
  72. agentloop/integrations/openai.py +415 -0
  73. agentloop/integrations/openai_agents.py +291 -0
  74. agentloop/integrations/sdk.py +82 -0
  75. agentloop/integrations/vercel_ai.py +83 -0
  76. agentloop/interoperability/__init__.py +5 -0
  77. agentloop/interoperability/artifacts.py +112 -0
  78. agentloop/interoperability/cli.py +59 -0
  79. agentloop/interoperability/cohort_evidence.py +175 -0
  80. agentloop/interoperability/cohorts.py +681 -0
  81. agentloop/interoperability/contracts.py +427 -0
  82. agentloop/interoperability/coordination.py +554 -0
  83. agentloop/interoperability/evidence.py +215 -0
  84. agentloop/interoperability/otlp_jsonl.py +775 -0
  85. agentloop/interoperability/otlp_validation.py +496 -0
  86. agentloop/interoperability/registry.py +561 -0
  87. agentloop/interoperability/validation.py +333 -0
  88. agentloop/intervention_service.py +110 -0
  89. agentloop/intervention_store.py +118 -0
  90. agentloop/interventions.py +190 -0
  91. agentloop/issues.py +90 -0
  92. agentloop/judgment_adapters.py +125 -0
  93. agentloop/judgment_benchmark_types.py +98 -0
  94. agentloop/judgment_benchmarks.py +606 -0
  95. agentloop/judgment_types.py +241 -0
  96. agentloop/judgment_views.py +38 -0
  97. agentloop/judgments.py +342 -0
  98. agentloop/loop_guards.py +274 -0
  99. agentloop/loop_types.py +66 -0
  100. agentloop/markdown.py +102 -0
  101. agentloop/metrics.py +342 -0
  102. agentloop/migrations.py +303 -0
  103. agentloop/model_routing.py +470 -0
  104. agentloop/onboarding.py +315 -0
  105. agentloop/operations.py +44 -0
  106. agentloop/optimizer.py +199 -0
  107. agentloop/otel.py +715 -0
  108. agentloop/otel_ids.py +81 -0
  109. agentloop/otel_semantics.py +134 -0
  110. agentloop/parallelism.py +141 -0
  111. agentloop/patches.py +603 -0
  112. agentloop/plan_export.py +126 -0
  113. agentloop/policy_promotion.py +325 -0
  114. agentloop/promotion_evidence.py +304 -0
  115. agentloop/promotion_types.py +209 -0
  116. agentloop/quality.py +446 -0
  117. agentloop/ranking.py +529 -0
  118. agentloop/recovery_evidence.py +96 -0
  119. agentloop/replay.py +753 -0
  120. agentloop/retention.py +375 -0
  121. agentloop/retention_types.py +113 -0
  122. agentloop/reuse_experiments.py +247 -0
  123. agentloop/routing_checks.py +116 -0
  124. agentloop/routing_types.py +241 -0
  125. agentloop/rules.py +396 -0
  126. agentloop/runtime.py +286 -0
  127. agentloop/savings.py +158 -0
  128. agentloop/scheduling_plan.py +99 -0
  129. agentloop/scheduling_types.py +169 -0
  130. agentloop/schema.py +257 -0
  131. agentloop/semantic_rules.py +200 -0
  132. agentloop/semantic_waste.py +345 -0
  133. agentloop/semantic_waste_types.py +88 -0
  134. agentloop/server.py +494 -0
  135. agentloop/store.py +1429 -0
  136. agentloop/structural_experiments.py +431 -0
  137. agentloop/structural_types.py +113 -0
  138. agentloop/structured_quality.py +1020 -0
  139. agentloop/studies.py +629 -0
  140. agentloop/study_statistics.py +58 -0
  141. agentloop/substitution_evidence.py +199 -0
  142. agentloop/substitution_export.py +190 -0
  143. agentloop/substitution_reports.py +407 -0
  144. agentloop/substitution_types.py +284 -0
  145. agentloop/substitutions.py +267 -0
  146. agentloop/timing.py +142 -0
  147. agentloop/tokens.py +188 -0
  148. agentloop/tool_scheduling.py +410 -0
  149. agentloop/trace_sources.py +31 -0
  150. agentloop/tracer.py +494 -0
  151. agentloop/value.py +306 -0
  152. agentloop/version.py +3 -0
  153. agentloop/workflow_types.py +264 -0
  154. agentloop/workflows.py +284 -0
  155. agentloop_profiler-0.1.0.dist-info/METADATA +513 -0
  156. agentloop_profiler-0.1.0.dist-info/RECORD +166 -0
  157. agentloop_profiler-0.1.0.dist-info/WHEEL +5 -0
  158. agentloop_profiler-0.1.0.dist-info/entry_points.txt +2 -0
  159. agentloop_profiler-0.1.0.dist-info/licenses/LICENSE +201 -0
  160. agentloop_profiler-0.1.0.dist-info/licenses/THIRD_PARTY_LICENSES.md +101 -0
  161. agentloop_profiler-0.1.0.dist-info/top_level.txt +2 -0
  162. dashboard/__init__.py +5 -0
  163. dashboard/app.py +922 -0
  164. dashboard/streamlit_compat.py +63 -0
  165. dashboard/trace_upload.py +57 -0
  166. dashboard/value_view.py +105 -0
@@ -0,0 +1,872 @@
1
+ """Offline calibration of preserved prediction/outcome artifacts, never runtime learning."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import math
7
+ import re
8
+ from collections import Counter, defaultdict
9
+ from datetime import datetime
10
+ from fractions import Fraction
11
+ from hashlib import sha256
12
+ from pathlib import Path
13
+
14
+ from agentloop.harness_evidence import METADATA_KEY, validate_evidence
15
+ from agentloop.interventions import InterventionRecord, canonical_json
16
+ from agentloop.markdown import markdown_code_span, markdown_heading, markdown_table_cell
17
+ from agentloop.study_statistics import task_weighted_summary
18
+ from agentloop.trace_sources import TraceSource
19
+
20
+ SCHEMA_VERSION = "1.0"
21
+ _DIGEST = re.compile(r"[a-f0-9]{64}\Z")
22
+ _LABEL = re.compile(r"[A-Za-z0-9_.:-]{1,96}\Z")
23
+ _CONTEXT_FIELDS = {
24
+ "workload",
25
+ "model",
26
+ "provider",
27
+ "environment",
28
+ "scorer",
29
+ "policy",
30
+ "cost_basis",
31
+ "pricing",
32
+ "quality_gate",
33
+ "intervention",
34
+ }
35
+ _METRICS = {
36
+ "latency": ("estimated_latency_savings_ms", "runtime_ms"),
37
+ "cost": ("estimated_cost_savings_usd", "estimated_cost_usd"),
38
+ }
39
+
40
+
41
+ class CalibrationValidationError(ValueError):
42
+ """The calibration declaration is ambiguous or malformed."""
43
+
44
+
45
+ def _fields(value, keys, label):
46
+ if not isinstance(value, dict) or set(value) != set(keys):
47
+ raise CalibrationValidationError(f"{label} must contain exactly: {', '.join(sorted(keys))}")
48
+
49
+
50
+ def _text(value, label):
51
+ if not isinstance(value, str) or not value.strip():
52
+ raise CalibrationValidationError(f"{label} must be nonempty text")
53
+
54
+
55
+ def _finite(value, *, nonnegative=False):
56
+ try:
57
+ return (
58
+ type(value) in {int, float} and math.isfinite(value) and (not nonnegative or value >= 0)
59
+ )
60
+ except (OverflowError, TypeError):
61
+ return False
62
+
63
+
64
+ def _time(value):
65
+ try:
66
+ result = datetime.fromisoformat(value.replace("Z", "+00:00"))
67
+ if result.utcoffset() is None:
68
+ raise ValueError
69
+ return result
70
+ except (AttributeError, TypeError, ValueError):
71
+ raise CalibrationValidationError("timestamps require ISO-8601 with a timezone") from None
72
+
73
+
74
+ def _digest(value):
75
+ return sha256(canonical_json(value).encode()).hexdigest()
76
+
77
+
78
+ def _scalar(value, label):
79
+ if type(value) is int or isinstance(value, str) and value:
80
+ return canonical_json([type(value).__name__, value])
81
+ raise CalibrationValidationError(f"{label} must be a nonempty string or integer")
82
+
83
+
84
+ def _validate(manifest):
85
+ _fields(
86
+ manifest,
87
+ {
88
+ "schema_version",
89
+ "name",
90
+ "as_of",
91
+ "valid_until",
92
+ "fit_method",
93
+ "min_fit_tasks",
94
+ "selection_inventory_complete",
95
+ "bootstrap",
96
+ "registrations",
97
+ },
98
+ "calibration manifest",
99
+ )
100
+ if manifest["schema_version"] != SCHEMA_VERSION:
101
+ raise CalibrationValidationError("calibration schema_version must be 1.0")
102
+ _text(manifest["name"], "name")
103
+ _time(manifest["as_of"])
104
+ _time(manifest["valid_until"])
105
+ if manifest["fit_method"] not in ("none", "scale"):
106
+ raise CalibrationValidationError("fit_method must be none or scale")
107
+ if type(manifest["min_fit_tasks"]) is not int or manifest["min_fit_tasks"] < 2:
108
+ raise CalibrationValidationError("min_fit_tasks must be an integer of at least two")
109
+ if type(manifest["selection_inventory_complete"]) is not bool:
110
+ raise CalibrationValidationError("selection_inventory_complete must be a boolean")
111
+ settings = manifest["bootstrap"]
112
+ _fields(settings, {"samples", "seed", "confidence"}, "bootstrap")
113
+ if (
114
+ type(settings["samples"]) is not int
115
+ or not 1 <= settings["samples"] <= 10000
116
+ or type(settings["seed"]) is not int
117
+ or not _finite(settings["confidence"])
118
+ or not 0 < settings["confidence"] < 1
119
+ ):
120
+ raise CalibrationValidationError(
121
+ "bootstrap requires 1..10000 samples, integer seed and confidence in (0,1)"
122
+ )
123
+ cases = manifest["registrations"]
124
+ if not isinstance(cases, list):
125
+ raise CalibrationValidationError("registrations must be a list, including when empty")
126
+ ids, tasks, digests = set(), {}, {}
127
+ for case in cases:
128
+ _fields(
129
+ case,
130
+ {
131
+ "case_id",
132
+ "task_id",
133
+ "task_sha256",
134
+ "repetition",
135
+ "split",
136
+ "selection",
137
+ "selection_reason",
138
+ "synthetic",
139
+ "context",
140
+ "prediction",
141
+ "prediction_recorded_at",
142
+ "outcome",
143
+ },
144
+ "registration",
145
+ )
146
+ _text(case["case_id"], "case_id")
147
+ if case["case_id"] in ids:
148
+ raise CalibrationValidationError("case IDs must be unique")
149
+ ids.add(case["case_id"])
150
+ task = _scalar(case["task_id"], "task_id")
151
+ _scalar(case["repetition"], "repetition")
152
+ if case["split"] not in ("fit", "held_out") or case["selection"] not in (
153
+ "selected",
154
+ "rejected",
155
+ "unselected",
156
+ ):
157
+ raise CalibrationValidationError("invalid split or selection")
158
+ if not isinstance(case["selection_reason"], str) or not _LABEL.fullmatch(
159
+ case["selection_reason"]
160
+ ):
161
+ raise CalibrationValidationError("selection_reason must be a bounded identifier")
162
+ if type(case["synthetic"]) is not bool:
163
+ raise CalibrationValidationError("synthetic must be a boolean")
164
+ if not isinstance(case["task_sha256"], str) or not _DIGEST.fullmatch(case["task_sha256"]):
165
+ raise CalibrationValidationError("task_sha256 must identify the full frozen task")
166
+ context = case["context"]
167
+ _fields(context, _CONTEXT_FIELDS, "context")
168
+ for key in ("workload", "model", "provider", "environment", "scorer"):
169
+ _text(context[key], key)
170
+ _fields(context["intervention"], {"type", "configuration"}, "planned intervention")
171
+ _text(context["intervention"]["type"], "intervention type")
172
+ if not isinstance(context["intervention"]["configuration"], dict):
173
+ raise CalibrationValidationError("planned intervention configuration must be an object")
174
+ if context["cost_basis"] not in ("provider_reported", "calculated", "mixed", "unknown"):
175
+ raise CalibrationValidationError("unsupported cost_basis")
176
+ if context["pricing"] is not None:
177
+ _text(context["pricing"], "pricing")
178
+ gate = context["quality_gate"]
179
+ _fields(gate, {"min_score", "max_regression"}, "quality_gate")
180
+ if any(not _finite(value) or not 0 <= value <= 1 for value in gate.values()):
181
+ raise CalibrationValidationError("quality thresholds must be in [0,1]")
182
+ policy = context["policy"]
183
+ if policy is not None:
184
+ _fields(policy, {"id", "version", "config_hash"}, "policy")
185
+ for key in ("id", "version"):
186
+ _text(policy[key], key)
187
+ if not isinstance(policy["config_hash"], str) or not _DIGEST.fullmatch(
188
+ policy["config_hash"]
189
+ ):
190
+ raise CalibrationValidationError("policy config_hash must be a SHA-256 digest")
191
+ identity = (context["workload"], task)
192
+ declaration = (case["split"], case["task_sha256"])
193
+ if identity in tasks and tasks[identity] != declaration:
194
+ raise CalibrationValidationError(
195
+ "a task cannot cross splits or change its input digest"
196
+ )
197
+ tasks[identity] = declaration
198
+ digest = case["task_sha256"]
199
+ if digest in digests and digests[digest] != (case["split"], identity):
200
+ raise CalibrationValidationError(
201
+ "identical task inputs must not cross splits or inflate task IDs"
202
+ )
203
+ digests[digest] = (case["split"], identity)
204
+ if not isinstance(case["prediction"], dict):
205
+ raise CalibrationValidationError(
206
+ "prediction must be the original complete finding snapshot"
207
+ )
208
+ _text(case["prediction"].get("finding_id"), "prediction finding_id")
209
+ if case["prediction_recorded_at"] is not None:
210
+ _time(case["prediction_recorded_at"])
211
+ outcome = case["outcome"]
212
+ if outcome is not None:
213
+ _fields(
214
+ outcome,
215
+ {"record", "baseline_trace", "candidate_trace", "recorded_at", "status"},
216
+ "outcome",
217
+ )
218
+ for key in ("record", "baseline_trace", "candidate_trace"):
219
+ if outcome[key] is not None:
220
+ _text(outcome[key], key)
221
+ _time(outcome["recorded_at"])
222
+ if outcome["status"] not in (
223
+ "completed",
224
+ "failed",
225
+ "cancelled",
226
+ "timed_out",
227
+ "stopped",
228
+ "incomplete",
229
+ ):
230
+ raise CalibrationValidationError("unsupported outcome status")
231
+ if case["selection"] != "selected":
232
+ raise CalibrationValidationError("an executed outcome requires selected status")
233
+
234
+
235
+ def _load(path, kind, sources, cache):
236
+ if path is None:
237
+ return None, f"missing_{kind}"
238
+ path = Path(path).resolve()
239
+ sources.add(path)
240
+ key = (path, kind)
241
+ if key not in cache:
242
+ try:
243
+ payload = json.loads(path.read_text(encoding="utf-8"))
244
+ value = (
245
+ InterventionRecord.from_dict(payload).to_dict()
246
+ if kind == "intervention"
247
+ else TraceSource.from_dict(payload)
248
+ )
249
+ cache[key] = (value, None)
250
+ except FileNotFoundError:
251
+ cache[key] = (None, f"missing_{kind}")
252
+ except (OSError, ValueError, TypeError, KeyError, RecursionError):
253
+ cache[key] = (None, f"invalid_{kind}")
254
+ return cache[key]
255
+
256
+
257
+ def _safe_difference(before, after):
258
+ if not _finite(before) or not _finite(after):
259
+ return None
260
+ result = before - after
261
+ return float(result) if _finite(result) else None
262
+
263
+
264
+ def _quality(case, record, traces, verified):
265
+ outcome = case["outcome"]
266
+ if case["selection"] != "selected":
267
+ return "not_evaluated"
268
+ if outcome is not None and outcome["status"] != "completed":
269
+ return "rejected"
270
+ if not verified or record is None or outcome is None:
271
+ return "indeterminate"
272
+ success = [trace.metadata.get("success") for trace in traces]
273
+ if any(value is False for value in success):
274
+ return "rejected"
275
+ if any(value is not True for value in success):
276
+ return "indeterminate"
277
+ quality = record["measured"].get("quality")
278
+ if isinstance(quality, dict):
279
+ scores = [quality.get("baseline_score"), quality.get("candidate_score")]
280
+ else:
281
+ scores = [
282
+ record["measured"].get(side, {}).get("quality_score")
283
+ for side in ("baseline", "candidate")
284
+ ]
285
+ if any(not _finite(value) or not 0 <= value <= 1 for value in scores):
286
+ return "indeterminate"
287
+ before, after = scores
288
+ gate = case["context"]["quality_gate"]
289
+ if min(scores) < gate["min_score"] or Fraction(str(before)) - Fraction(str(after)) > Fraction(
290
+ str(gate["max_regression"])
291
+ ):
292
+ return "rejected"
293
+ return "accepted"
294
+
295
+
296
+ def _policy_observed(policy, trace):
297
+ if policy is None:
298
+ return True
299
+ try:
300
+ evidence = validate_evidence(trace.metadata.get(METADATA_KEY), trace_id=trace.run_id)
301
+ if evidence["capture_errors"]:
302
+ return False
303
+ snapshot = evidence["policies"].get(policy["config_hash"], {})
304
+ return (
305
+ snapshot.get("policy_id") == policy["id"]
306
+ and snapshot.get("version") == policy["version"]
307
+ and any(
308
+ item["policy_config_hash"] == policy["config_hash"]
309
+ and item["mode"] == "enforce"
310
+ and item["origin"] == "policy"
311
+ for item in evidence["decisions"].values()
312
+ )
313
+ )
314
+ except (ValueError, TypeError, KeyError):
315
+ return False
316
+
317
+
318
+ def _cost_known(summary, basis):
319
+ return (
320
+ basis in {"provider_reported", "calculated"}
321
+ and summary.get("cost_status") in {"complete", "empty"}
322
+ and summary.get("has_unknown_cost") is False
323
+ and (basis == "provider_reported" or summary.get("token_status") in {"exact", "empty"})
324
+ )
325
+
326
+
327
+ def _source_cost_basis(traces, summaries, basis):
328
+ """Verify reported-vs-calculated origin without looking up current prices."""
329
+ for trace, summary in zip(traces, summaries):
330
+ if trace is None:
331
+ return False
332
+ model_events = [event for event in trace.events if event.event_type == "model_call"]
333
+ amounts = [
334
+ event.metadata.get("provider_reported_cost_usd", event.metadata.get("cost_usd"))
335
+ for event in model_events
336
+ ]
337
+ if basis == "calculated":
338
+ if any(value is not None for value in amounts):
339
+ return False
340
+ elif basis == "provider_reported":
341
+ if any(not _finite(value, nonnegative=True) for value in amounts):
342
+ return False
343
+ total = sum(amounts)
344
+ if not _finite(total) or round(total, 6) != summary.get("estimated_cost_usd"):
345
+ return False
346
+ else:
347
+ return False
348
+ return len(traces) == 2
349
+
350
+
351
+ def _case(case, root, manifest, sources, cache):
352
+ issues = []
353
+ prediction = case["prediction"]
354
+ estimate = prediction.get("estimate")
355
+ estimator = (
356
+ {
357
+ key: estimate.get(key)
358
+ for key in ("estimator_id", "estimator_version", "method", "formula", "parameters")
359
+ }
360
+ if isinstance(estimate, dict)
361
+ else None
362
+ )
363
+ if (
364
+ estimator is None
365
+ or any(
366
+ not isinstance(estimator[key], str) or not estimator[key]
367
+ for key in ("estimator_id", "estimator_version", "method", "formula")
368
+ )
369
+ or not isinstance(estimator["parameters"], dict)
370
+ ):
371
+ issues.append("missing_estimator_provenance")
372
+ cohort = {"context": case["context"], "estimator": estimator}
373
+ record, traces = None, []
374
+ synthetic = case["synthetic"]
375
+ outcome = case["outcome"]
376
+ if case["selection"] != "selected":
377
+ issues.append("not_selected")
378
+ if case["prediction_recorded_at"] is None:
379
+ issues.append("prediction_time_unknown")
380
+ elif _time(case["prediction_recorded_at"]) > _time(manifest["as_of"]):
381
+ issues.append("prediction_after_as_of")
382
+ if outcome is None:
383
+ issues.append("outcome_not_recorded")
384
+ else:
385
+ observed = _time(outcome["recorded_at"])
386
+ if observed > _time(manifest["as_of"]):
387
+ issues.append("outcome_after_as_of")
388
+ if (
389
+ case["prediction_recorded_at"] is not None
390
+ and _time(case["prediction_recorded_at"]) >= observed
391
+ ):
392
+ issues.append("prediction_not_frozen_before_outcome")
393
+ record, error = _load(
394
+ root / outcome["record"] if outcome["record"] is not None else None,
395
+ "intervention",
396
+ sources,
397
+ cache,
398
+ )
399
+ if error is not None:
400
+ issues.append(error)
401
+ for side in ("baseline", "candidate"):
402
+ source, error = _load(
403
+ root / outcome[f"{side}_trace"] if outcome[f"{side}_trace"] is not None else None,
404
+ "trace",
405
+ sources,
406
+ cache,
407
+ )
408
+ if error is not None:
409
+ issues.append(f"{side}_{error}")
410
+ trace = source.trace if source is not None else None
411
+ traces.append(trace)
412
+ if trace is not None:
413
+ synthetic = synthetic or trace.metadata.get("synthetic") is True
414
+ if side == "candidate" and case["prediction_recorded_at"] is not None:
415
+ try:
416
+ if _time(case["prediction_recorded_at"]) >= _time(trace.started_at):
417
+ issues.append("prediction_not_frozen_before_candidate")
418
+ except CalibrationValidationError:
419
+ issues.append("candidate_time_unverified")
420
+ actual_repetition = trace.metadata.get("repetition", trace.metadata.get("seed"))
421
+ if canonical_json(
422
+ [
423
+ trace.metadata.get("task_id"),
424
+ type(trace.metadata.get("task_id")).__name__,
425
+ actual_repetition,
426
+ type(actual_repetition).__name__,
427
+ ]
428
+ ) != canonical_json(
429
+ [
430
+ case["task_id"],
431
+ type(case["task_id"]).__name__,
432
+ case["repetition"],
433
+ type(case["repetition"]).__name__,
434
+ ]
435
+ ):
436
+ issues.append(f"{side}_pairing_unverified")
437
+ if record is not None and (
438
+ trace.run_id != record[f"{side}_run_id"]
439
+ or record["trace_fingerprints"][side] not in source.fingerprints
440
+ ):
441
+ issues.append(f"{side}_source_mismatch")
442
+ if record is not None:
443
+ synthetic = synthetic or record["metadata"].get("synthetic") is True
444
+ planned = case["context"]["intervention"]
445
+ if record["intervention_type"] != planned["type"] or canonical_json(
446
+ record["configuration"]
447
+ ) != canonical_json(planned["configuration"]):
448
+ issues.append("intervention_configuration_mismatch")
449
+ findings = record["predicted"]["findings"]
450
+ if len(findings) != 1:
451
+ issues.append("combined_intervention_unattributed")
452
+ saved = next(
453
+ (item for item in findings if item.get("finding_id") == prediction["finding_id"]),
454
+ None,
455
+ )
456
+ if saved is None or canonical_json(saved) != canonical_json(prediction):
457
+ issues.append("original_prediction_mismatch")
458
+ if (
459
+ len(traces) == 2
460
+ and traces[1] is not None
461
+ and not _policy_observed(case["context"]["policy"], traces[1])
462
+ ):
463
+ issues.append("policy_not_observed_enforced")
464
+ verified = not issues
465
+ quality = _quality(case, record, traces, verified)
466
+ if synthetic:
467
+ issues.append("synthetic")
468
+ metrics = {}
469
+ for name, (prediction_key, measured_key) in _METRICS.items():
470
+ reasons = []
471
+ savings = prediction.get("savings")
472
+ predicted = savings.get(prediction_key) if isinstance(savings, dict) else None
473
+ modeled = estimate.get("unmodeled_metrics") if isinstance(estimate, dict) else None
474
+ if not isinstance(modeled, list) or any(not isinstance(item, str) for item in modeled):
475
+ predicted = None
476
+ reasons.append("modeled_metric_unknown")
477
+ elif name in modeled:
478
+ predicted = None
479
+ reasons.append("unmodeled_metric")
480
+ if not _finite(predicted):
481
+ predicted = None
482
+ reasons.append("prediction_unavailable")
483
+ before = record["measured"]["baseline"] if record is not None else {}
484
+ after = record["measured"]["candidate"] if record is not None else {}
485
+ a, b = before.get(measured_key), after.get(measured_key)
486
+ realized = (
487
+ _safe_difference(a, b)
488
+ if _finite(a, nonnegative=True) and _finite(b, nonnegative=True)
489
+ else None
490
+ )
491
+ if name == "latency" and any(trace is None or trace.ended_at is None for trace in traces):
492
+ realized = None
493
+ reasons.append("incomplete_timing")
494
+ if name == "cost":
495
+ basis = case["context"]["cost_basis"]
496
+ inputs = estimate.get("inputs", {}) if isinstance(estimate, dict) else {}
497
+ if (
498
+ not isinstance(inputs, dict)
499
+ or inputs.get("cost_status") not in {"complete", "empty"}
500
+ or basis not in {"provider_reported", "calculated"}
501
+ or (basis == "calculated" and inputs.get("token_status") not in {"exact", "empty"})
502
+ ):
503
+ predicted = None
504
+ reasons.append("prediction_cost_provenance_incomplete")
505
+ if not _cost_known(before, basis) or not _cost_known(after, basis):
506
+ realized = None
507
+ reasons.append("outcome_cost_provenance_incomplete")
508
+ if not _source_cost_basis(traces, (before, after), basis):
509
+ realized = None
510
+ reasons.append("source_cost_basis_unverified")
511
+ if basis == "calculated" and case["context"]["pricing"] is None:
512
+ predicted = realized = None
513
+ reasons.append("pricing_reference_missing")
514
+ if realized is None:
515
+ reasons.append("outcome_unavailable")
516
+ error = _safe_difference(predicted, realized)
517
+ ratio, ratio_status = None, "unavailable"
518
+ if predicted is not None and realized is not None:
519
+ if predicted == 0:
520
+ ratio_status = "zero_prediction"
521
+ else:
522
+ try:
523
+ ratio = float(Fraction(str(realized)) / Fraction(str(predicted)))
524
+ ratio_status = "available" if _finite(ratio) else "numeric_range"
525
+ except (OverflowError, ValueError):
526
+ ratio_status = "numeric_range"
527
+ if ratio_status != "available":
528
+ ratio = None
529
+ if error is None:
530
+ reasons.append("error_numeric_range")
531
+ metrics[name] = {
532
+ "predicted_savings": predicted,
533
+ "realized_savings": realized,
534
+ "signed_error": error,
535
+ "realization_ratio": ratio,
536
+ "ratio_status": ratio_status,
537
+ "reasons": sorted(set(reasons)),
538
+ "empirical_eligible": not issues and error is not None,
539
+ }
540
+ return {
541
+ "case_id": case["case_id"],
542
+ "repetition_key": _scalar(case["repetition"], "repetition"),
543
+ "task_key": canonical_json(
544
+ [case["context"]["workload"], _scalar(case["task_id"], "task_id")]
545
+ ),
546
+ "split": case["split"],
547
+ "selection": case["selection"],
548
+ "selection_reason": case["selection_reason"],
549
+ "outcome_status": outcome["status"] if outcome is not None else None,
550
+ "cohort_id": "cal_" + _digest(cohort),
551
+ "cohort": cohort,
552
+ "synthetic": synthetic,
553
+ "issues": sorted(set(issues)),
554
+ "quality_gate": quality,
555
+ "metrics": metrics,
556
+ "prediction_snapshot": prediction,
557
+ "intervention": record,
558
+ "source_event_error_count": sum(
559
+ event.status == "error"
560
+ for trace in traces
561
+ if trace is not None
562
+ for event in trace.events
563
+ ),
564
+ }
565
+
566
+
567
+ def _fit_scale(rows, metric, minimum):
568
+ pairs = defaultdict(list)
569
+ for row in rows:
570
+ value = row["metrics"][metric]
571
+ if row["split"] == "fit" and value["empirical_eligible"]:
572
+ pairs[row["task_key"]].append(
573
+ (
574
+ Fraction(str(value["predicted_savings"])),
575
+ Fraction(str(value["realized_savings"])),
576
+ )
577
+ )
578
+ task_means = [
579
+ (sum(pair[0] for pair in group) / len(group), sum(pair[1] for pair in group) / len(group))
580
+ for _, group in sorted(pairs.items())
581
+ ]
582
+ result = {
583
+ "method": "multiplicative_least_squares_on_equal_weight_task_means",
584
+ "task_count": len(task_means),
585
+ "factor": None,
586
+ "status": "insufficient_tasks",
587
+ }
588
+ if len(task_means) < minimum:
589
+ return result
590
+ denominator = sum(before * before for before, _ in task_means)
591
+ if denominator == 0:
592
+ return {**result, "status": "zero_predictions"}
593
+ factor = sum(before * after for before, after in task_means) / denominator
594
+ try:
595
+ numeric = float(factor)
596
+ except OverflowError:
597
+ numeric = math.inf
598
+ if not _finite(numeric):
599
+ return {**result, "status": "numeric_range"}
600
+ return {**result, "factor": numeric, "status": "fitted"}
601
+
602
+
603
+ def _mark_duplicates(rows):
604
+ by_outcome, by_pair = defaultdict(list), defaultdict(list)
605
+ for row in rows:
606
+ if row["intervention"] is not None:
607
+ by_outcome[row["intervention"]["intervention_id"]].append(row)
608
+ by_pair[
609
+ (
610
+ row["cohort_id"],
611
+ row["task_key"],
612
+ row["repetition_key"],
613
+ row["prediction_snapshot"]["finding_id"],
614
+ )
615
+ ].append(row)
616
+ for groups, issue in ((by_outcome, "duplicate_outcome"), (by_pair, "duplicate_pairing_key")):
617
+ for group in groups.values():
618
+ if len(group) > 1:
619
+ for row in group:
620
+ row["issues"] = sorted(set([*row["issues"], issue]))
621
+ if row["quality_gate"] == "accepted":
622
+ row["quality_gate"] = "indeterminate"
623
+ for metric in row["metrics"].values():
624
+ metric["empirical_eligible"] = False
625
+
626
+
627
+ def _metric_summary(rows, metric, key, settings, *, quality_only=False):
628
+ return task_weighted_summary(
629
+ (
630
+ (
631
+ row["task_key"],
632
+ row["metrics"][metric].get(key)
633
+ if row["metrics"][metric]["empirical_eligible"]
634
+ and (not quality_only or row["quality_gate"] == "accepted")
635
+ else None,
636
+ )
637
+ for row in rows
638
+ if not row["synthetic"]
639
+ ),
640
+ settings,
641
+ )
642
+
643
+
644
+ def _scaled_prediction(value, factor):
645
+ if value is None or factor is None:
646
+ return None
647
+ try:
648
+ result = float(Fraction(str(value)) * Fraction(str(factor)))
649
+ return result if _finite(result) else None
650
+ except (ValueError, OverflowError):
651
+ return None
652
+
653
+
654
+ def _cohort(rows, manifest):
655
+ metrics = {}
656
+ for metric in _METRICS:
657
+ fitted = (
658
+ _fit_scale(rows, metric, manifest["min_fit_tasks"])
659
+ if manifest["fit_method"] == "scale"
660
+ else {"method": "none", "status": "not_requested", "factor": None, "task_count": 0}
661
+ )
662
+ for row in rows:
663
+ values = row["metrics"][metric]
664
+ values["scaled_prediction"] = _scaled_prediction(
665
+ values["predicted_savings"], fitted["factor"]
666
+ )
667
+ values["scaled_signed_error"] = _safe_difference(
668
+ values["scaled_prediction"], values["realized_savings"]
669
+ )
670
+ splits = {}
671
+ for split in ("fit", "held_out"):
672
+ subset = [row for row in rows if row["split"] == split]
673
+ non_synthetic = [row for row in subset if not row["synthetic"]]
674
+ selected = [row for row in non_synthetic if row["selection"] == "selected"]
675
+ counts = Counter(row["quality_gate"] for row in selected)
676
+ splits[split] = {
677
+ "registration_count": len(subset),
678
+ "non_synthetic_registration_count": len(non_synthetic),
679
+ "selected_count": len(selected),
680
+ "synthetic_excluded_count": len(subset) - len(non_synthetic),
681
+ "quality_gate_counts": {
682
+ key: counts[key] for key in ("accepted", "rejected", "indeterminate")
683
+ },
684
+ "quality_acceptance_rate": counts["accepted"] / len(selected) if selected else None,
685
+ "selection_counts": dict(
686
+ sorted(Counter(row["selection"] for row in non_synthetic).items())
687
+ ),
688
+ "original": {
689
+ key: _metric_summary(subset, metric, key, manifest["bootstrap"])
690
+ for key in (
691
+ "predicted_savings",
692
+ "realized_savings",
693
+ "signed_error",
694
+ "realization_ratio",
695
+ )
696
+ },
697
+ "quality_preserving": {
698
+ key: _metric_summary(
699
+ subset, metric, key, manifest["bootstrap"], quality_only=True
700
+ )
701
+ for key in ("realized_savings", "signed_error")
702
+ },
703
+ "scaled_error": _metric_summary(
704
+ subset, metric, "scaled_signed_error", manifest["bootstrap"]
705
+ ),
706
+ }
707
+ metrics[metric] = {
708
+ "fit": fitted,
709
+ "splits": splits,
710
+ "held_out_status": "observed"
711
+ if splits["held_out"]["original"]["signed_error"]["observation_summary"]["count"]
712
+ else "no_evaluable_held_out_outcomes",
713
+ }
714
+ return {
715
+ "cohort_id": rows[0]["cohort_id"],
716
+ **rows[0]["cohort"],
717
+ "registration_count": len(rows),
718
+ "metrics": metrics,
719
+ "issues": dict(sorted(Counter(issue for row in rows for issue in row["issues"]).items())),
720
+ "selection_reasons": dict(sorted(Counter(row["selection_reason"] for row in rows).items())),
721
+ }
722
+
723
+
724
+ def summarize_calibration(manifest_path):
725
+ """Read historical artifacts without re-diagnosing, re-pricing, or rerunning agents."""
726
+ path = Path(manifest_path).resolve()
727
+ sources, cache = {path}, {}
728
+ try:
729
+ manifest = json.loads(path.read_text(encoding="utf-8"))
730
+ # Own a finite JSON snapshot so neither reads nor reporting can mutate inputs.
731
+ manifest = json.loads(canonical_json(manifest))
732
+ _validate(manifest)
733
+ rows = [
734
+ _case(case, path.parent, manifest, sources, cache) for case in manifest["registrations"]
735
+ ]
736
+ rows.sort(key=lambda row: row["case_id"])
737
+ _mark_duplicates(rows)
738
+ groups = defaultdict(list)
739
+ for row in rows:
740
+ groups[row["cohort_id"]].append(row)
741
+ cohorts = [_cohort(group, manifest) for _, group in sorted(groups.items())]
742
+ report = {
743
+ "schema_version": SCHEMA_VERSION,
744
+ "name": manifest["name"],
745
+ "manifest": manifest,
746
+ "manifest_hash": _digest(manifest),
747
+ "registrations": rows,
748
+ "cohorts": cohorts,
749
+ "registration_count": len(rows),
750
+ "synthetic_excluded_count": sum(row["synthetic"] for row in rows),
751
+ "selection_inventory_complete": manifest["selection_inventory_complete"],
752
+ "source_paths": sorted(str(source) for source in sources),
753
+ "validity": {
754
+ "as_of": manifest["as_of"],
755
+ "valid_until": manifest["valid_until"],
756
+ "expired": _time(manifest["as_of"]) > _time(manifest["valid_until"]),
757
+ "scope": "Only the exact recorded estimator/policy, workload, model/provider, environment, scorer and quality-gate cohort. Changes require renewed validation; freshness is assessed at the declared as_of, not the reader's current clock.",
758
+ },
759
+ "runtime_coefficients_changed": False,
760
+ "interpretation": "Historical descriptive calibration, not a causal or universal-confidence claim. Savings are baseline minus candidate; signed error is prediction minus realized savings (positive means overprediction). Synthetic, ambiguous and unverifiable cases are excluded from empirical aggregates but retained. All measurable outcomes, including failures and unknown quality, enter raw error summaries and any requested scale fit. Quality-preserving subsets are conditional and retain denominators. Scale fitting targets raw resource savings, never task utility; fitting-task error is in-sample and held-out error is separate. Task means, not seeds, are the bootstrap units. Selection, missingness, small-task uncertainty, multiplicity, scorer validity and workload drift remain limitations. No estimator or runtime policy is modified.",
761
+ }
762
+ report["artifact_hash"] = _digest(report)
763
+ return report
764
+ except (OSError, ValueError, TypeError, KeyError, RecursionError, OverflowError) as exc:
765
+ raise CalibrationValidationError(f"cannot read calibration manifest: {exc}") from exc
766
+
767
+
768
+ def calibration_to_markdown(report):
769
+ lines = [
770
+ f"# Calibration: {markdown_heading(report['name'])}",
771
+ "",
772
+ report["interpretation"],
773
+ "",
774
+ f"Registrations: {report['registration_count']}; synthetic excluded: {report['synthetic_excluded_count']}.",
775
+ "",
776
+ f"Selection inventory declared complete: {report['selection_inventory_complete']}. This is a host declaration, not proof of unbiased selection.",
777
+ "",
778
+ f"Freshness at {markdown_heading(report['validity']['as_of'])}: {'expired' if report['validity']['expired'] else 'within declared validity window'}; valid until {markdown_heading(report['validity']['valid_until'])}.",
779
+ "",
780
+ ]
781
+ for cohort in report["cohorts"]:
782
+ context = cohort["context"]
783
+ lines.extend(
784
+ [
785
+ f"## {markdown_heading(cohort['cohort_id'])}",
786
+ "",
787
+ f"Workload: {markdown_heading(context['workload'])}; model/provider: {markdown_heading(context['model'])} / {markdown_heading(context['provider'])}; cost basis: {markdown_heading(context['cost_basis'])}.",
788
+ "",
789
+ "Estimator: " + markdown_code_span(canonical_json(cohort["estimator"])),
790
+ "",
791
+ "Environment/scorer/pricing/policy: "
792
+ + markdown_code_span(
793
+ canonical_json(
794
+ {
795
+ key: context[key]
796
+ for key in (
797
+ "environment",
798
+ "scorer",
799
+ "pricing",
800
+ "policy",
801
+ "quality_gate",
802
+ "intervention",
803
+ )
804
+ }
805
+ )
806
+ ),
807
+ "",
808
+ "Selection reasons: "
809
+ + markdown_code_span(canonical_json(cohort["selection_reasons"])),
810
+ "",
811
+ "Excluded/unverifiable evidence: "
812
+ + markdown_code_span(canonical_json(cohort["issues"])),
813
+ "",
814
+ "| Metric / split | Registered (non-synthetic) | Selected | Quality accepted / rejected / unknown | Evaluable / planned: observations; tasks | Mean original error | Mean scaled error | Mean realized savings | Original error interval |",
815
+ "| --- | ---: | ---: | --- | --- | ---: | ---: | ---: | --- |",
816
+ ]
817
+ )
818
+ notes = []
819
+ for metric, details in cohort["metrics"].items():
820
+ for split, values in details["splits"].items():
821
+ errors = values["original"]["signed_error"]
822
+ savings = values["original"]["realized_savings"]
823
+ counts, interval = values["quality_gate_counts"], errors["interval"]
824
+ bounds = (
825
+ f"{interval['confidence']:.1%}: [{interval['lower']:.6g}, {interval['upper']:.6g}]"
826
+ if interval["status"] == "computed"
827
+ else "insufficient tasks"
828
+ )
829
+ cells = [
830
+ f"{metric} / {split}",
831
+ values["non_synthetic_registration_count"],
832
+ values["selected_count"],
833
+ f"{counts['accepted']} / {counts['rejected']} / {counts['indeterminate']}",
834
+ f"{errors['observation_summary']['count']}/{errors['planned_observation_count']}; {errors['task_summary']['count']}/{errors['planned_task_count']}",
835
+ errors["task_summary"]["mean"],
836
+ values["scaled_error"]["task_summary"]["mean"],
837
+ savings["task_summary"]["mean"],
838
+ bounds,
839
+ ]
840
+ lines.append(
841
+ "| "
842
+ + " | ".join(
843
+ markdown_table_cell("unavailable" if value is None else str(value))
844
+ for value in cells
845
+ )
846
+ + " |"
847
+ )
848
+ accepted = values["quality_acceptance_rate"]
849
+ eligible = values["quality_preserving"]["realized_savings"]
850
+ notes.extend(
851
+ [
852
+ "",
853
+ f"{metric}/{split} quality acceptance among selected non-synthetic registrations: {'undefined (zero selected)' if accepted is None else f'{accepted:.1%}'}. Conditional mean savings: {eligible['task_summary']['mean']}; eligible tasks: {eligible['task_summary']['count']}/{eligible['planned_task_count']}.",
854
+ "",
855
+ ]
856
+ )
857
+ fitted = details["fit"]
858
+ notes.extend(
859
+ [
860
+ "",
861
+ f"{metric} diagnostic fit: {fitted['status']}; factor: {fitted['factor']}; fitting tasks: {fitted['task_count']}. {details['held_out_status']}.",
862
+ "",
863
+ ]
864
+ )
865
+ lines.extend(notes)
866
+ lines.extend(
867
+ [
868
+ "Every registration, original snapshot, outcome status, exclusion reason, task/observation denominator and held-out scaled error remains in JSON. No runtime coefficients were changed.",
869
+ "",
870
+ ]
871
+ )
872
+ return "\n".join(lines)