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.
- agentloop/__init__.py +136 -0
- agentloop/ablation_protocol.py +229 -0
- agentloop/ablations.py +564 -0
- agentloop/aggregate_cli.py +88 -0
- agentloop/aggregate_stats.py +175 -0
- agentloop/aggregates.py +532 -0
- agentloop/audit.py +39 -0
- agentloop/autoinstrument.py +145 -0
- agentloop/batch_experiments.py +295 -0
- agentloop/batching.py +95 -0
- agentloop/budget_types.py +335 -0
- agentloop/budgets.py +539 -0
- agentloop/calibration.py +872 -0
- agentloop/checkpoint_state.py +306 -0
- agentloop/checkpoint_store.py +86 -0
- agentloop/ci.py +202 -0
- agentloop/cli.py +1314 -0
- agentloop/client.py +228 -0
- agentloop/completion.py +345 -0
- agentloop/completion_types.py +199 -0
- agentloop/config.py +50 -0
- agentloop/context_controls.py +443 -0
- agentloop/context_experiments.py +337 -0
- agentloop/context_types.py +216 -0
- agentloop/costs.py +667 -0
- agentloop/cursor_validation.py +48 -0
- agentloop/decorators.py +445 -0
- agentloop/demo.py +300 -0
- agentloop/doctor.py +341 -0
- agentloop/drift.py +361 -0
- agentloop/drift_cli.py +147 -0
- agentloop/drift_types.py +110 -0
- agentloop/drift_windows.py +340 -0
- agentloop/entrypoint.py +201 -0
- agentloop/estimates.py +133 -0
- agentloop/events.py +75 -0
- agentloop/experiment_artifacts.py +80 -0
- agentloop/experiment_observations.py +169 -0
- agentloop/experiment_reports.py +545 -0
- agentloop/experiment_types.py +355 -0
- agentloop/experiments.py +392 -0
- agentloop/exporters.py +117 -0
- agentloop/finding_benchmark_types.py +131 -0
- agentloop/finding_benchmarks.py +424 -0
- agentloop/findings.py +419 -0
- agentloop/graph.py +464 -0
- agentloop/harness.py +825 -0
- agentloop/harness_bench/__init__.py +1 -0
- agentloop/harness_bench/cli.py +77 -0
- agentloop/harness_bench/drivers.py +96 -0
- agentloop/harness_bench/offline.py +507 -0
- agentloop/harness_evidence.py +585 -0
- agentloop/harness_usage.py +91 -0
- agentloop/html_report.py +860 -0
- agentloop/integrations/__init__.py +1 -0
- agentloop/integrations/crewai.py +144 -0
- agentloop/integrations/harbor/__init__.py +1 -0
- agentloop/integrations/harbor/atif.py +982 -0
- agentloop/integrations/harbor/atif_validation.py +329 -0
- agentloop/integrations/harbor/cli.py +207 -0
- agentloop/integrations/harbor/manifest.py +86 -0
- agentloop/integrations/harbor/trial_evidence.py +196 -0
- agentloop/integrations/harbor/trials.py +1109 -0
- agentloop/integrations/harbor/verifier.py +118 -0
- agentloop/integrations/langgraph.py +150 -0
- agentloop/integrations/langgraph_harness.py +466 -0
- agentloop/integrations/langgraph_recovery.py +366 -0
- agentloop/integrations/omnigent/__init__.py +1 -0
- agentloop/integrations/omnigent/cli.py +78 -0
- agentloop/integrations/omnigent/evidence.py +93 -0
- agentloop/integrations/omnigent/telemetry.py +422 -0
- agentloop/integrations/openai.py +415 -0
- agentloop/integrations/openai_agents.py +291 -0
- agentloop/integrations/sdk.py +82 -0
- agentloop/integrations/vercel_ai.py +83 -0
- agentloop/interoperability/__init__.py +5 -0
- agentloop/interoperability/artifacts.py +112 -0
- agentloop/interoperability/cli.py +59 -0
- agentloop/interoperability/cohort_evidence.py +175 -0
- agentloop/interoperability/cohorts.py +681 -0
- agentloop/interoperability/contracts.py +427 -0
- agentloop/interoperability/coordination.py +554 -0
- agentloop/interoperability/evidence.py +215 -0
- agentloop/interoperability/otlp_jsonl.py +775 -0
- agentloop/interoperability/otlp_validation.py +496 -0
- agentloop/interoperability/registry.py +561 -0
- agentloop/interoperability/validation.py +333 -0
- agentloop/intervention_service.py +110 -0
- agentloop/intervention_store.py +118 -0
- agentloop/interventions.py +190 -0
- agentloop/issues.py +90 -0
- agentloop/judgment_adapters.py +125 -0
- agentloop/judgment_benchmark_types.py +98 -0
- agentloop/judgment_benchmarks.py +606 -0
- agentloop/judgment_types.py +241 -0
- agentloop/judgment_views.py +38 -0
- agentloop/judgments.py +342 -0
- agentloop/loop_guards.py +274 -0
- agentloop/loop_types.py +66 -0
- agentloop/markdown.py +102 -0
- agentloop/metrics.py +342 -0
- agentloop/migrations.py +303 -0
- agentloop/model_routing.py +470 -0
- agentloop/onboarding.py +315 -0
- agentloop/operations.py +44 -0
- agentloop/optimizer.py +199 -0
- agentloop/otel.py +715 -0
- agentloop/otel_ids.py +81 -0
- agentloop/otel_semantics.py +134 -0
- agentloop/parallelism.py +141 -0
- agentloop/patches.py +603 -0
- agentloop/plan_export.py +126 -0
- agentloop/policy_promotion.py +325 -0
- agentloop/promotion_evidence.py +304 -0
- agentloop/promotion_types.py +209 -0
- agentloop/quality.py +446 -0
- agentloop/ranking.py +529 -0
- agentloop/recovery_evidence.py +96 -0
- agentloop/replay.py +753 -0
- agentloop/retention.py +375 -0
- agentloop/retention_types.py +113 -0
- agentloop/reuse_experiments.py +247 -0
- agentloop/routing_checks.py +116 -0
- agentloop/routing_types.py +241 -0
- agentloop/rules.py +396 -0
- agentloop/runtime.py +286 -0
- agentloop/savings.py +158 -0
- agentloop/scheduling_plan.py +99 -0
- agentloop/scheduling_types.py +169 -0
- agentloop/schema.py +257 -0
- agentloop/semantic_rules.py +200 -0
- agentloop/semantic_waste.py +345 -0
- agentloop/semantic_waste_types.py +88 -0
- agentloop/server.py +494 -0
- agentloop/store.py +1429 -0
- agentloop/structural_experiments.py +431 -0
- agentloop/structural_types.py +113 -0
- agentloop/structured_quality.py +1020 -0
- agentloop/studies.py +629 -0
- agentloop/study_statistics.py +58 -0
- agentloop/substitution_evidence.py +199 -0
- agentloop/substitution_export.py +190 -0
- agentloop/substitution_reports.py +407 -0
- agentloop/substitution_types.py +284 -0
- agentloop/substitutions.py +267 -0
- agentloop/timing.py +142 -0
- agentloop/tokens.py +188 -0
- agentloop/tool_scheduling.py +410 -0
- agentloop/trace_sources.py +31 -0
- agentloop/tracer.py +494 -0
- agentloop/value.py +306 -0
- agentloop/version.py +3 -0
- agentloop/workflow_types.py +264 -0
- agentloop/workflows.py +284 -0
- agentloop_profiler-0.1.0.dist-info/METADATA +513 -0
- agentloop_profiler-0.1.0.dist-info/RECORD +166 -0
- agentloop_profiler-0.1.0.dist-info/WHEEL +5 -0
- agentloop_profiler-0.1.0.dist-info/entry_points.txt +2 -0
- agentloop_profiler-0.1.0.dist-info/licenses/LICENSE +201 -0
- agentloop_profiler-0.1.0.dist-info/licenses/THIRD_PARTY_LICENSES.md +101 -0
- agentloop_profiler-0.1.0.dist-info/top_level.txt +2 -0
- dashboard/__init__.py +5 -0
- dashboard/app.py +922 -0
- dashboard/streamlit_compat.py +63 -0
- dashboard/trace_upload.py +57 -0
- dashboard/value_view.py +105 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
"""Finished-trial cohorts using native studies and independent quality gates."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import math
|
|
7
|
+
from collections import Counter, defaultdict
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from hashlib import sha256
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from agentloop.integrations.harbor.trial_evidence import PAIRING_KEYS, attach_trial
|
|
14
|
+
from agentloop.integrations.harbor.trials import HarborImportResult, HarborOptions, import_harbor
|
|
15
|
+
from agentloop.integrations.harbor.verifier import ScoringContract, external_outcome
|
|
16
|
+
from agentloop.interoperability.artifacts import (
|
|
17
|
+
native_bytes,
|
|
18
|
+
output_artifact_path,
|
|
19
|
+
write_artifact,
|
|
20
|
+
)
|
|
21
|
+
from agentloop.interoperability.cohort_evidence import (
|
|
22
|
+
COHORT_KEY,
|
|
23
|
+
COHORT_PAIRING_KEYS,
|
|
24
|
+
cohort_record,
|
|
25
|
+
read_cohort,
|
|
26
|
+
)
|
|
27
|
+
from agentloop.interoperability.contracts import external_id
|
|
28
|
+
from agentloop.interoperability.validation import (
|
|
29
|
+
ImportLimits,
|
|
30
|
+
ImportValidationError,
|
|
31
|
+
indirect_path,
|
|
32
|
+
load_json_artifact,
|
|
33
|
+
relative_reference,
|
|
34
|
+
validate_json_tree,
|
|
35
|
+
)
|
|
36
|
+
from agentloop.interventions import InterventionRecord, canonical_json
|
|
37
|
+
from agentloop.studies import _quantile_sorted, summarize_study, summarize_values
|
|
38
|
+
from agentloop.study_statistics import task_weighted_summary
|
|
39
|
+
from agentloop.tracer import AgentTrace
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _fail(field: str, reason: str) -> None:
|
|
43
|
+
raise ImportValidationError("invalid_cohort", field, reason)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _label(value: Any, field: str) -> None:
|
|
47
|
+
if not isinstance(value, str) or not value.strip() or len(value.encode()) > 512:
|
|
48
|
+
_fail(field, "must be a bounded nonempty label")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _hash(value: Any) -> str:
|
|
52
|
+
return sha256(canonical_json(value).encode()).hexdigest()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _summary(values: list) -> dict:
|
|
56
|
+
result = summarize_values(values)
|
|
57
|
+
ordered = sorted(value for value in values if value is not None)
|
|
58
|
+
result["p50"] = result["median"]
|
|
59
|
+
result["p90"] = _quantile_sorted(ordered, 0.9)
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class CohortProtocol:
|
|
65
|
+
protocol_id: str
|
|
66
|
+
comparison_kind: str = "system_level_confounded"
|
|
67
|
+
missingness_policy: str = "indeterminate"
|
|
68
|
+
minimum_runtime_improvement_pct: float = 0.0
|
|
69
|
+
bootstrap_samples: int = 1000
|
|
70
|
+
bootstrap_seed: int = 0
|
|
71
|
+
bootstrap_confidence: float = 0.95
|
|
72
|
+
|
|
73
|
+
def __post_init__(self) -> None:
|
|
74
|
+
_label(self.protocol_id, "protocol_id")
|
|
75
|
+
if (
|
|
76
|
+
not isinstance(self.comparison_kind, str)
|
|
77
|
+
or self.comparison_kind
|
|
78
|
+
not in {
|
|
79
|
+
"system_level_confounded",
|
|
80
|
+
"controlled_harness_ablation",
|
|
81
|
+
}
|
|
82
|
+
or not isinstance(self.missingness_policy, str)
|
|
83
|
+
or self.missingness_policy not in {"indeterminate", "reject"}
|
|
84
|
+
):
|
|
85
|
+
_fail("protocol", "unsupported comparison/missingness policy")
|
|
86
|
+
if (
|
|
87
|
+
type(self.minimum_runtime_improvement_pct) not in {int, float}
|
|
88
|
+
or not math.isfinite(self.minimum_runtime_improvement_pct)
|
|
89
|
+
or self.minimum_runtime_improvement_pct < 0
|
|
90
|
+
):
|
|
91
|
+
_fail("minimum_runtime_improvement_pct", "must be finite nonnegative")
|
|
92
|
+
if (
|
|
93
|
+
type(self.bootstrap_samples) is not int
|
|
94
|
+
or not 1 <= self.bootstrap_samples <= 10000
|
|
95
|
+
or type(self.bootstrap_seed) is not int
|
|
96
|
+
or type(self.bootstrap_confidence) not in {int, float}
|
|
97
|
+
or not 0 < self.bootstrap_confidence < 1
|
|
98
|
+
):
|
|
99
|
+
_fail("bootstrap", "invalid task-cluster bootstrap settings")
|
|
100
|
+
|
|
101
|
+
def uncertainty_settings(self) -> dict:
|
|
102
|
+
return {
|
|
103
|
+
"samples": self.bootstrap_samples,
|
|
104
|
+
"seed": self.bootstrap_seed,
|
|
105
|
+
"confidence": self.bootstrap_confidence,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass(frozen=True)
|
|
110
|
+
class CohortStudyResult:
|
|
111
|
+
manifest_path: Path | None
|
|
112
|
+
_report_json: str
|
|
113
|
+
|
|
114
|
+
def report(self) -> dict:
|
|
115
|
+
return json.loads(self._report_json)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _trial_metrics(receipt: dict | None) -> dict:
|
|
119
|
+
metadata = receipt["source_metadata"] if receipt else {}
|
|
120
|
+
usage = metadata.get("usage", {})
|
|
121
|
+
phases = metadata.get("phase_timing", {})
|
|
122
|
+
return {
|
|
123
|
+
"runtime_ms": phases.get("agent_execution", {}).get("duration_ms"),
|
|
124
|
+
"trial_runtime_ms": metadata.get("trial_wall_timing", {}).get("duration_ms"),
|
|
125
|
+
"environment_setup_ms": phases.get("environment_setup", {}).get("duration_ms"),
|
|
126
|
+
"agent_setup_ms": phases.get("agent_setup", {}).get("duration_ms"),
|
|
127
|
+
"verifier_ms": phases.get("verifier", {}).get("duration_ms"),
|
|
128
|
+
"input_tokens": usage.get("input_tokens"),
|
|
129
|
+
"output_tokens": usage.get("output_tokens"),
|
|
130
|
+
"cached_input_tokens": usage.get("cached_input_tokens"),
|
|
131
|
+
"cost_usd": usage.get("cost_usd"),
|
|
132
|
+
"model_call_count": None,
|
|
133
|
+
"tool_call_count": None,
|
|
134
|
+
"retry_count": None,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _observation(
|
|
139
|
+
result: HarborImportResult,
|
|
140
|
+
row: dict,
|
|
141
|
+
condition: str,
|
|
142
|
+
protocol: CohortProtocol,
|
|
143
|
+
index: int,
|
|
144
|
+
*,
|
|
145
|
+
inventory: dict,
|
|
146
|
+
receipt: dict | None,
|
|
147
|
+
trace_index: dict,
|
|
148
|
+
inventory_sha256: str,
|
|
149
|
+
inventory_identity_sha256: str,
|
|
150
|
+
) -> tuple[AgentTrace, dict]:
|
|
151
|
+
source_identity = (
|
|
152
|
+
receipt["external_identity"]
|
|
153
|
+
if receipt
|
|
154
|
+
else {
|
|
155
|
+
"job_id": inventory["job_id"],
|
|
156
|
+
"trial_id": row.get("trial_id"),
|
|
157
|
+
"task_id": None,
|
|
158
|
+
"task_digest": None,
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
pairing = {key: row.get("pairing", {}).get(key) for key in PAIRING_KEYS}
|
|
162
|
+
if pairing["protocol_id"] is not None and pairing["protocol_id"] != protocol.protocol_id:
|
|
163
|
+
_fail("protocol_id", "cohort protocol differs from captured source protocol")
|
|
164
|
+
metadata = receipt["source_metadata"] if receipt else {}
|
|
165
|
+
task_id, verifier_hash = source_identity.get("task_id"), metadata.get("verifier_config_hash")
|
|
166
|
+
metrics = _trial_metrics(receipt)
|
|
167
|
+
trajectories = bool(row.get("trajectory_run_ids"))
|
|
168
|
+
if trajectories and row["import_status"] == "imported":
|
|
169
|
+
projected = [
|
|
170
|
+
trace_index[run_id] for run_id in row["trajectory_run_ids"] if run_id in trace_index
|
|
171
|
+
]
|
|
172
|
+
source_counts = [
|
|
173
|
+
trace.metadata.get("agentloop.external", {}).get("reported_model_call_count")
|
|
174
|
+
for trace in projected
|
|
175
|
+
]
|
|
176
|
+
metrics["model_call_count"] = (
|
|
177
|
+
sum(source_counts)
|
|
178
|
+
if len(projected) == len(row["trajectory_run_ids"])
|
|
179
|
+
and all(value is not None for value in source_counts)
|
|
180
|
+
else None
|
|
181
|
+
)
|
|
182
|
+
metrics["tool_call_count"] = (
|
|
183
|
+
sum(event.operation_kind == "tool" for trace in projected for event in trace.events)
|
|
184
|
+
if len(projected) == len(row["trajectory_run_ids"])
|
|
185
|
+
else None
|
|
186
|
+
)
|
|
187
|
+
usage = metrics["input_tokens"] is not None and metrics["output_tokens"] is not None
|
|
188
|
+
outcome = (
|
|
189
|
+
receipt["outcome"]
|
|
190
|
+
if receipt
|
|
191
|
+
else external_outcome(
|
|
192
|
+
execution_status="unknown", verifier_status="invalid", dimensions=None
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
source_ref = (
|
|
196
|
+
receipt["source"]
|
|
197
|
+
if receipt
|
|
198
|
+
else {
|
|
199
|
+
"artifact_reference": "harbor-inventory.json",
|
|
200
|
+
"artifact_sha256": inventory_sha256,
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
captured_id = (
|
|
204
|
+
receipt["receipt_id"]
|
|
205
|
+
if receipt
|
|
206
|
+
else external_id(
|
|
207
|
+
"receipt",
|
|
208
|
+
"harbor",
|
|
209
|
+
{
|
|
210
|
+
"job_id": inventory["job_id"],
|
|
211
|
+
"inventory_sha256": inventory_identity_sha256,
|
|
212
|
+
"row": index,
|
|
213
|
+
},
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
run_id = external_id(
|
|
217
|
+
"run", "harbor", {"source_receipt_id": captured_id, "scope": "cohort_observation"}
|
|
218
|
+
)
|
|
219
|
+
record = cohort_record(
|
|
220
|
+
source_receipt=receipt,
|
|
221
|
+
import_status=row["import_status"],
|
|
222
|
+
trajectory_available=trajectories,
|
|
223
|
+
usage_available=usage,
|
|
224
|
+
missingness_policy=protocol.missingness_policy,
|
|
225
|
+
condition=condition,
|
|
226
|
+
task_id=task_id,
|
|
227
|
+
verifier_config_hash=verifier_hash,
|
|
228
|
+
)
|
|
229
|
+
trace = AgentTrace(
|
|
230
|
+
name="external_cohort_observation",
|
|
231
|
+
run_id=run_id,
|
|
232
|
+
started_at="unknown",
|
|
233
|
+
metadata={
|
|
234
|
+
"source": "harbor_cohort",
|
|
235
|
+
"synthetic": inventory.get("synthetic") is True,
|
|
236
|
+
"execution_data_present": False,
|
|
237
|
+
"condition": condition,
|
|
238
|
+
**pairing,
|
|
239
|
+
"task_id": task_id,
|
|
240
|
+
"verifier_config_hash": verifier_hash,
|
|
241
|
+
COHORT_KEY: record,
|
|
242
|
+
},
|
|
243
|
+
)
|
|
244
|
+
attach_trial(
|
|
245
|
+
trace,
|
|
246
|
+
{
|
|
247
|
+
"receipt_id": captured_id,
|
|
248
|
+
"source_result": {
|
|
249
|
+
"reference": source_ref["artifact_reference"],
|
|
250
|
+
"sha256": source_ref["artifact_sha256"],
|
|
251
|
+
},
|
|
252
|
+
"measurement": {
|
|
253
|
+
"runtime_ms": metrics["runtime_ms"],
|
|
254
|
+
"scope": "agent_execution",
|
|
255
|
+
"input_tokens": metrics["input_tokens"],
|
|
256
|
+
"output_tokens": metrics["output_tokens"],
|
|
257
|
+
"cached_input_tokens": metrics["cached_input_tokens"],
|
|
258
|
+
"cost_usd": metrics["cost_usd"],
|
|
259
|
+
"usage_provenance": "external_reported",
|
|
260
|
+
"cost_scope": "agent_context",
|
|
261
|
+
},
|
|
262
|
+
"outcome": outcome,
|
|
263
|
+
"pairing": pairing,
|
|
264
|
+
},
|
|
265
|
+
)
|
|
266
|
+
accepted = read_cohort(trace)["accepted"]
|
|
267
|
+
facts = {
|
|
268
|
+
"run_id": run_id,
|
|
269
|
+
"source_receipt_id": receipt["receipt_id"] if receipt else None,
|
|
270
|
+
"source_identity": source_identity,
|
|
271
|
+
"trial_directory": row["trial_directory"],
|
|
272
|
+
"trial_id": source_identity.get("trial_id"),
|
|
273
|
+
"import_status": row["import_status"],
|
|
274
|
+
"outcome": outcome,
|
|
275
|
+
"accepted": accepted,
|
|
276
|
+
"trajectory_available": trajectories,
|
|
277
|
+
"usage_available": usage,
|
|
278
|
+
"pairing": {**pairing, "task_id": task_id, "verifier_config_hash": verifier_hash},
|
|
279
|
+
"metrics": metrics,
|
|
280
|
+
"agent_info": metadata.get("agent_info"),
|
|
281
|
+
"agent_config_hash": metadata.get("agent_config_hash"),
|
|
282
|
+
"environment": metadata.get("environment"),
|
|
283
|
+
"dataset": metadata.get("dataset"),
|
|
284
|
+
"dataset_version": metadata.get("dataset_version"),
|
|
285
|
+
"exception_info": metadata.get("exception_info"),
|
|
286
|
+
"source_versions": inventory["source_versions"],
|
|
287
|
+
"prediction_attribution": "unavailable unless an original finding-linked intervention record is supplied",
|
|
288
|
+
"measurement_provenance": {
|
|
289
|
+
"runtime_scope": "agent_execution",
|
|
290
|
+
"usage": "external_reported_agent_context",
|
|
291
|
+
"cost": "external_reported_agent_context",
|
|
292
|
+
"provider_billing_verified": False,
|
|
293
|
+
"local_estimated_usd": None,
|
|
294
|
+
"self_hosted_operating_cost": None,
|
|
295
|
+
},
|
|
296
|
+
}
|
|
297
|
+
return trace, facts
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _confounders(left: dict, right: dict, protocol: CohortProtocol) -> list[str]:
|
|
301
|
+
result = []
|
|
302
|
+
for field in ("agent_info", "agent_config_hash", "dataset", "dataset_version"):
|
|
303
|
+
a, b = left.get(field), right.get(field)
|
|
304
|
+
if a is None or b is None:
|
|
305
|
+
result.append(field + "_unrecorded")
|
|
306
|
+
elif a != b:
|
|
307
|
+
result.append(field + "_differs")
|
|
308
|
+
if protocol.comparison_kind == "system_level_confounded":
|
|
309
|
+
result.append("system_level_descriptive_design")
|
|
310
|
+
result.extend(
|
|
311
|
+
[
|
|
312
|
+
"provider_queue_cache_and_rate_limit_control_unestablished",
|
|
313
|
+
"independent_held_out_design_unestablished",
|
|
314
|
+
]
|
|
315
|
+
)
|
|
316
|
+
return result
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _compare(
|
|
320
|
+
native: dict | None,
|
|
321
|
+
left: list[dict],
|
|
322
|
+
right: list[dict],
|
|
323
|
+
right_inventory: dict,
|
|
324
|
+
protocol: CohortProtocol,
|
|
325
|
+
) -> dict:
|
|
326
|
+
by_id = {row["run_id"]: row for row in (*left, *right)}
|
|
327
|
+
pairs = []
|
|
328
|
+
for pair in native["pairs"] if native else []:
|
|
329
|
+
before, after = by_id[pair["baseline_run_id"]], by_id[pair["candidate_run_id"]]
|
|
330
|
+
correct = (
|
|
331
|
+
before["outcome"]["quality_pass"] is True and after["outcome"]["quality_pass"] is True
|
|
332
|
+
)
|
|
333
|
+
rejected = any(row["accepted"] is False for row in (before, after))
|
|
334
|
+
complete = before["accepted"] is True and after["accepted"] is True
|
|
335
|
+
a, b = before["metrics"]["runtime_ms"], after["metrics"]["runtime_ms"]
|
|
336
|
+
improvement = 100 * (a - b) / a if a is not None and b is not None and a > 0 else None
|
|
337
|
+
improved = (
|
|
338
|
+
improvement is not None
|
|
339
|
+
and b < a
|
|
340
|
+
and improvement >= protocol.minimum_runtime_improvement_pct
|
|
341
|
+
)
|
|
342
|
+
state = (
|
|
343
|
+
"rejected"
|
|
344
|
+
if rejected
|
|
345
|
+
else "verified_quality_preserving_improvement"
|
|
346
|
+
if complete and correct and improved
|
|
347
|
+
else "no_verified_improvement"
|
|
348
|
+
if complete and improvement is not None
|
|
349
|
+
else "indeterminate"
|
|
350
|
+
)
|
|
351
|
+
confounders = _confounders(before, after, protocol)
|
|
352
|
+
pairs.append(
|
|
353
|
+
{
|
|
354
|
+
**pair,
|
|
355
|
+
"state": state,
|
|
356
|
+
"external_correctness_pass": correct,
|
|
357
|
+
"source_acceptance_complete": complete,
|
|
358
|
+
"runtime_improvement_pct": improvement,
|
|
359
|
+
"confounders": confounders,
|
|
360
|
+
"causal_harness_effect": "not_established",
|
|
361
|
+
"source_trial_ids": {
|
|
362
|
+
"baseline": before["trial_id"],
|
|
363
|
+
"candidate": after["trial_id"],
|
|
364
|
+
},
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
unidentified = right_inventory["planned_unidentified_trials"] or 0
|
|
368
|
+
denominator = len(right) + unidentified
|
|
369
|
+
wins = sum(pair["state"] == "verified_quality_preserving_improvement" for pair in pairs)
|
|
370
|
+
observations = [
|
|
371
|
+
(pair["pairing_metadata"]["task_id"], pair["deltas"]["runtime_ms"]) for pair in pairs
|
|
372
|
+
]
|
|
373
|
+
uncertainty = task_weighted_summary(observations, protocol.uncertainty_settings())
|
|
374
|
+
native_unmatched = (
|
|
375
|
+
native["unmatched"]
|
|
376
|
+
if native
|
|
377
|
+
else [
|
|
378
|
+
{
|
|
379
|
+
"side": side,
|
|
380
|
+
"run_id": row["run_id"],
|
|
381
|
+
"reason": "no_native_manifest_for_empty_condition",
|
|
382
|
+
}
|
|
383
|
+
for side, rows in (("baseline", left), ("candidate", right))
|
|
384
|
+
for row in rows
|
|
385
|
+
]
|
|
386
|
+
)
|
|
387
|
+
mismatch = []
|
|
388
|
+
attempt_indexes = {}
|
|
389
|
+
for side, rows in (("baseline", left), ("candidate", right)):
|
|
390
|
+
indexed = defaultdict(list)
|
|
391
|
+
for row in rows:
|
|
392
|
+
repetition = row["pairing"].get("repetition")
|
|
393
|
+
if repetition is not None:
|
|
394
|
+
indexed[(row["pairing"].get("task_id"), type(repetition), repetition)].append(row)
|
|
395
|
+
attempt_indexes[side] = indexed
|
|
396
|
+
for unmatched in native_unmatched:
|
|
397
|
+
row = by_id[unmatched["run_id"]]
|
|
398
|
+
repetition = row["pairing"].get("repetition")
|
|
399
|
+
compatible_attempt = attempt_indexes[
|
|
400
|
+
"candidate" if unmatched["side"] == "baseline" else "baseline"
|
|
401
|
+
].get((row["pairing"].get("task_id"), type(repetition), repetition), [])
|
|
402
|
+
reasons = sorted(
|
|
403
|
+
{
|
|
404
|
+
key + "_incompatible"
|
|
405
|
+
for other in compatible_attempt
|
|
406
|
+
for key in COHORT_PAIRING_KEYS
|
|
407
|
+
if row["pairing"].get(key) != other["pairing"].get(key)
|
|
408
|
+
}
|
|
409
|
+
)
|
|
410
|
+
mismatch.append({**unmatched, "source_exclusion_reasons": reasons or [unmatched["reason"]]})
|
|
411
|
+
return {
|
|
412
|
+
"pairs": pairs,
|
|
413
|
+
"paired_count": len(pairs),
|
|
414
|
+
"unmatched": mismatch,
|
|
415
|
+
"unpaired_count": len(mismatch),
|
|
416
|
+
"quality_preserving_intervention_rate": {
|
|
417
|
+
"numerator": wins,
|
|
418
|
+
"denominator": denominator,
|
|
419
|
+
"value": wins / denominator if denominator else None,
|
|
420
|
+
"selection_policy": "all observed candidate attempts plus planned unidentified candidate attempts; unmatched/incomplete attempts cannot win",
|
|
421
|
+
"unidentified_planned_attempts": unidentified,
|
|
422
|
+
"interpretation": "verified wins divided by the full candidate attempt population; unknown/unpaired attempts remain in the denominator, so this is a conservative observed fraction, not an exact causal rate",
|
|
423
|
+
},
|
|
424
|
+
"runtime_task_uncertainty": uncertainty,
|
|
425
|
+
"comparison_kind": protocol.comparison_kind,
|
|
426
|
+
"controlled_design_validated": False,
|
|
427
|
+
"limitations": [
|
|
428
|
+
"source hashes establish supplied-byte consistency, not authenticity",
|
|
429
|
+
"causal model/tool/prompt control and held-out allocation are not proven by equal labels",
|
|
430
|
+
"planned unidentified tasks cannot contribute fabricated task clusters",
|
|
431
|
+
"job concurrency/image cache/provider queuing may confound runtime",
|
|
432
|
+
],
|
|
433
|
+
"state_counts": dict(sorted(Counter(pair["state"] for pair in pairs).items())),
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def write_cohort_study(
|
|
438
|
+
conditions: dict[str, HarborImportResult],
|
|
439
|
+
out: str | Path,
|
|
440
|
+
*,
|
|
441
|
+
baseline: str,
|
|
442
|
+
protocol: CohortProtocol,
|
|
443
|
+
name="External task-paired study",
|
|
444
|
+
original_interventions: tuple[InterventionRecord, ...] = (),
|
|
445
|
+
) -> CohortStudyResult:
|
|
446
|
+
"""Materialize all observed attempts without execution spans or success invention."""
|
|
447
|
+
if not isinstance(conditions, dict) or len(conditions) < 2 or baseline not in conditions:
|
|
448
|
+
_fail("conditions", "at least two explicit conditions and a baseline are required")
|
|
449
|
+
_label(name, "name")
|
|
450
|
+
for key, value in conditions.items():
|
|
451
|
+
_label(key, "condition")
|
|
452
|
+
if not isinstance(value, HarborImportResult):
|
|
453
|
+
_fail("conditions", "sources must be captured Harbor import results")
|
|
454
|
+
if any(not isinstance(record, InterventionRecord) for record in original_interventions):
|
|
455
|
+
_fail(
|
|
456
|
+
"interventions", "original intervention records must use the existing native contract"
|
|
457
|
+
)
|
|
458
|
+
root = Path(out)
|
|
459
|
+
for record in original_interventions:
|
|
460
|
+
InterventionRecord.from_dict(record.to_dict())
|
|
461
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
462
|
+
root = root.resolve()
|
|
463
|
+
folders = {
|
|
464
|
+
condition: output_artifact_path(root, f"condition-{index}")
|
|
465
|
+
for index, condition in enumerate(sorted(conditions))
|
|
466
|
+
}
|
|
467
|
+
paths, populations, observations = {}, {}, {}
|
|
468
|
+
for index, (condition, result) in enumerate(sorted(conditions.items())):
|
|
469
|
+
folder = f"condition-{index}"
|
|
470
|
+
result.write(folders[condition])
|
|
471
|
+
inventory = result.inventory()
|
|
472
|
+
inventory_serialized = canonical_json(inventory).encode()
|
|
473
|
+
inventory_sha256 = sha256(inventory_serialized + b"\n").hexdigest()
|
|
474
|
+
inventory_identity_sha256 = sha256(inventory_serialized).hexdigest()
|
|
475
|
+
receipt_index = {item.receipt_id: item.to_dict() for item in result.trial_receipts}
|
|
476
|
+
trace_index = {trace.run_id: trace for trace in result.traces}
|
|
477
|
+
facts, references = [], []
|
|
478
|
+
for ordinal, row in enumerate(inventory["trial_rows"]):
|
|
479
|
+
trace, fact = _observation(
|
|
480
|
+
result,
|
|
481
|
+
row,
|
|
482
|
+
condition,
|
|
483
|
+
protocol,
|
|
484
|
+
ordinal,
|
|
485
|
+
inventory=inventory,
|
|
486
|
+
receipt=receipt_index.get(row.get("receipt_id")),
|
|
487
|
+
trace_index=trace_index,
|
|
488
|
+
inventory_sha256=inventory_sha256,
|
|
489
|
+
inventory_identity_sha256=inventory_identity_sha256,
|
|
490
|
+
)
|
|
491
|
+
reference = f"{folder}/observations/{trace.run_id}.json"
|
|
492
|
+
write_artifact(root, reference, native_bytes(trace))
|
|
493
|
+
facts.append(fact)
|
|
494
|
+
references.append(reference)
|
|
495
|
+
paths[condition] = references
|
|
496
|
+
observations[condition] = facts
|
|
497
|
+
planned_gap = inventory["planned_unidentified_trials"] or 0
|
|
498
|
+
populations[condition] = {
|
|
499
|
+
"observed_attempts": len(facts),
|
|
500
|
+
"planned_attempts": inventory["trials_planned"],
|
|
501
|
+
"planned_unidentified_attempts": planned_gap,
|
|
502
|
+
"denominator": len(facts) + planned_gap,
|
|
503
|
+
"import_status_counts": dict(
|
|
504
|
+
sorted(Counter(fact["import_status"] for fact in facts).items())
|
|
505
|
+
),
|
|
506
|
+
"execution_status_counts": dict(
|
|
507
|
+
sorted(Counter(fact["outcome"]["execution_status"] for fact in facts).items())
|
|
508
|
+
),
|
|
509
|
+
"verifier_status_counts": dict(
|
|
510
|
+
sorted(Counter(fact["outcome"]["verifier_status"] for fact in facts).items())
|
|
511
|
+
),
|
|
512
|
+
"quality_pass": sum(fact["outcome"]["quality_pass"] is True for fact in facts),
|
|
513
|
+
"quality_fail": sum(fact["outcome"]["quality_pass"] is False for fact in facts),
|
|
514
|
+
"quality_indeterminate": sum(fact["outcome"]["quality_pass"] is None for fact in facts)
|
|
515
|
+
+ planned_gap,
|
|
516
|
+
"accepted": sum(fact["accepted"] is True for fact in facts),
|
|
517
|
+
"rejected": sum(fact["accepted"] is False for fact in facts),
|
|
518
|
+
"acceptance_indeterminate": sum(fact["accepted"] is None for fact in facts)
|
|
519
|
+
+ planned_gap,
|
|
520
|
+
"metrics": {
|
|
521
|
+
key: _summary([fact["metrics"].get(key) for fact in facts] + [None] * planned_gap)
|
|
522
|
+
for key in _trial_metrics(None)
|
|
523
|
+
},
|
|
524
|
+
"job_timing": inventory.get("job_timing"),
|
|
525
|
+
"job_concurrency": inventory.get("job_concurrency"),
|
|
526
|
+
"source_versions": inventory["source_versions"],
|
|
527
|
+
"trial_rows": facts,
|
|
528
|
+
}
|
|
529
|
+
native = None
|
|
530
|
+
manifest_path = None
|
|
531
|
+
if all(paths.values()):
|
|
532
|
+
manifest = {
|
|
533
|
+
"schema_version": "1.0",
|
|
534
|
+
"name": name,
|
|
535
|
+
"baseline": baseline,
|
|
536
|
+
"conditions": paths,
|
|
537
|
+
"pairing_keys": list(COHORT_PAIRING_KEYS),
|
|
538
|
+
}
|
|
539
|
+
# Native manifest/statistics semantics remain unchanged. Clustered
|
|
540
|
+
# uncertainty is added by the existing shared task-weighted helper.
|
|
541
|
+
write_artifact(root, "study.json", (json.dumps(manifest, indent=2) + "\n").encode())
|
|
542
|
+
manifest_path = root / "study.json"
|
|
543
|
+
native = summarize_study(manifest_path)
|
|
544
|
+
comparisons = {
|
|
545
|
+
candidate: _compare(
|
|
546
|
+
native["comparisons"][candidate] if native else None,
|
|
547
|
+
observations[baseline],
|
|
548
|
+
observations[candidate],
|
|
549
|
+
conditions[candidate].inventory(),
|
|
550
|
+
protocol,
|
|
551
|
+
)
|
|
552
|
+
for candidate in conditions
|
|
553
|
+
if candidate != baseline
|
|
554
|
+
}
|
|
555
|
+
report = {
|
|
556
|
+
"schema_version": "1.0",
|
|
557
|
+
"name": name,
|
|
558
|
+
"baseline": baseline,
|
|
559
|
+
"protocol": {
|
|
560
|
+
"protocol_id": protocol.protocol_id,
|
|
561
|
+
"comparison_kind": protocol.comparison_kind,
|
|
562
|
+
"missingness_policy": protocol.missingness_policy,
|
|
563
|
+
"minimum_runtime_improvement_pct": protocol.minimum_runtime_improvement_pct,
|
|
564
|
+
},
|
|
565
|
+
"conditions": populations,
|
|
566
|
+
"comparisons": comparisons,
|
|
567
|
+
"native_study": native,
|
|
568
|
+
"native_study_scope": "one receipt-only observation per discovered attempt, including failures/missing trajectories; planned unidentified attempts remain count-only gaps",
|
|
569
|
+
"native_manifest": "study.json" if manifest_path else None,
|
|
570
|
+
"native_missing_reason": None
|
|
571
|
+
if manifest_path
|
|
572
|
+
else "a condition has no identified observed attempts",
|
|
573
|
+
"original_intervention_records": [record.to_dict() for record in original_interventions],
|
|
574
|
+
"prediction_attribution": "original supplied records preserved; no estimator attribution invented for arbitrary harness differences",
|
|
575
|
+
"instrumentation_overhead": {
|
|
576
|
+
"status": "unavailable",
|
|
577
|
+
"reason": "no recording-disabled control supplied",
|
|
578
|
+
},
|
|
579
|
+
"synthetic": any(
|
|
580
|
+
result.inventory().get("synthetic") is True for result in conditions.values()
|
|
581
|
+
),
|
|
582
|
+
}
|
|
583
|
+
serialized = canonical_json(report)
|
|
584
|
+
write_artifact(root, "cohort-report.json", (serialized + "\n").encode())
|
|
585
|
+
from agentloop.html_report import cohort_to_html
|
|
586
|
+
|
|
587
|
+
write_artifact(root, "cohort-report.html", cohort_to_html(report).encode())
|
|
588
|
+
return CohortStudyResult(manifest_path, serialized)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def _source_directory(root: Path, reference: str) -> Path:
|
|
592
|
+
reference = relative_reference(reference)
|
|
593
|
+
current = root.resolve(strict=True)
|
|
594
|
+
base = current
|
|
595
|
+
for part in reference.split("/"):
|
|
596
|
+
current = current / part
|
|
597
|
+
if indirect_path(current):
|
|
598
|
+
_fail("job_path", "source directories cannot traverse symlinks/junctions")
|
|
599
|
+
current = current.resolve(strict=True)
|
|
600
|
+
if not current.is_dir() or not current.is_relative_to(base):
|
|
601
|
+
_fail("job_path", "must be a local directory under the selected source root")
|
|
602
|
+
return current
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def materialize_source_study(
|
|
606
|
+
path: str | Path, out: str | Path, *, limits: ImportLimits = ImportLimits()
|
|
607
|
+
) -> CohortStudyResult:
|
|
608
|
+
"""Load a strict data-only source manifest; never launch a benchmark/runtime."""
|
|
609
|
+
path = Path(path)
|
|
610
|
+
payload = load_json_artifact(path.parent, path.name, limits=limits).payload
|
|
611
|
+
validate_json_tree(payload, limits)
|
|
612
|
+
if (
|
|
613
|
+
not isinstance(payload, dict)
|
|
614
|
+
or set(payload) != {"schema_version", "name", "baseline", "sources", "protocol"}
|
|
615
|
+
or payload["schema_version"] != "1.0"
|
|
616
|
+
or not isinstance(payload["sources"], dict)
|
|
617
|
+
):
|
|
618
|
+
_fail("manifest", "must contain exactly the documented source manifest fields")
|
|
619
|
+
if not isinstance(payload["protocol"], dict) or set(payload["protocol"]) - set(
|
|
620
|
+
CohortProtocol.__dataclass_fields__
|
|
621
|
+
):
|
|
622
|
+
_fail("protocol", "unsupported protocol fields")
|
|
623
|
+
if "protocol_id" not in payload["protocol"]:
|
|
624
|
+
_fail("protocol", "an explicit protocol identity is required")
|
|
625
|
+
protocol = CohortProtocol(**payload["protocol"])
|
|
626
|
+
sources = {}
|
|
627
|
+
for condition, source in payload["sources"].items():
|
|
628
|
+
required = {
|
|
629
|
+
"system",
|
|
630
|
+
"job_path",
|
|
631
|
+
"scoring",
|
|
632
|
+
"task_scoring",
|
|
633
|
+
"trial_context",
|
|
634
|
+
"expected_hashes",
|
|
635
|
+
"expected_agent",
|
|
636
|
+
"expected_model",
|
|
637
|
+
}
|
|
638
|
+
if (
|
|
639
|
+
not isinstance(source, dict)
|
|
640
|
+
or not required <= set(source)
|
|
641
|
+
or set(source) - (required | {"synthetic"})
|
|
642
|
+
or source["system"] != "harbor"
|
|
643
|
+
):
|
|
644
|
+
_fail("sources", "initial source manifests require strict completed-Harbor fields")
|
|
645
|
+
scoring = (
|
|
646
|
+
ScoringContract.from_dict(source["scoring"]) if source["scoring"] is not None else None
|
|
647
|
+
)
|
|
648
|
+
if not isinstance(source["task_scoring"], dict):
|
|
649
|
+
_fail("task_scoring", "must be task digest to scoring definitions")
|
|
650
|
+
by_task = {
|
|
651
|
+
key: ScoringContract.from_dict(value) for key, value in source["task_scoring"].items()
|
|
652
|
+
}
|
|
653
|
+
result = import_harbor(
|
|
654
|
+
_source_directory(path.parent, source["job_path"]),
|
|
655
|
+
options=HarborOptions(
|
|
656
|
+
scoring=scoring,
|
|
657
|
+
scoring_by_task_digest=by_task,
|
|
658
|
+
condition=condition,
|
|
659
|
+
protocol_id=protocol.protocol_id,
|
|
660
|
+
trial_context=source["trial_context"],
|
|
661
|
+
expected_hashes=source["expected_hashes"],
|
|
662
|
+
synthetic_fixture=source.get("synthetic", False),
|
|
663
|
+
),
|
|
664
|
+
limits=limits,
|
|
665
|
+
)
|
|
666
|
+
for receipt in result.trial_receipts:
|
|
667
|
+
info = receipt.to_dict()["source_metadata"].get("agent_info") or {}
|
|
668
|
+
model = (info.get("model_info") or {}).get("name")
|
|
669
|
+
if (
|
|
670
|
+
source["expected_agent"] is not None
|
|
671
|
+
and source["expected_agent"] != info.get("name")
|
|
672
|
+
or source["expected_model"] is not None
|
|
673
|
+
and source["expected_model"] != model
|
|
674
|
+
):
|
|
675
|
+
_fail(
|
|
676
|
+
"source_identity", "declared model/agent differs from recorded source evidence"
|
|
677
|
+
)
|
|
678
|
+
sources[condition] = result
|
|
679
|
+
return write_cohort_study(
|
|
680
|
+
sources, out, baseline=payload["baseline"], protocol=protocol, name=payload["name"]
|
|
681
|
+
)
|