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,335 @@
1
+ """Payload-free resource declarations shared by dispatch wrappers and budgets."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import math
7
+ import re
8
+ from dataclasses import asdict, dataclass
9
+ from decimal import Decimal, localcontext
10
+ from fractions import Fraction
11
+ from typing import Any
12
+
13
+ from agentloop.loop_types import StepInfo
14
+
15
+ TOKEN_PROVENANCE = frozenset(
16
+ {
17
+ "provider",
18
+ "tokenizer",
19
+ "user_supplied",
20
+ "estimated_words",
21
+ "legacy",
22
+ "unspecified",
23
+ "unavailable",
24
+ }
25
+ )
26
+ COST_PROVENANCE = frozenset(
27
+ {"provider_reported", "user_reported", "calculated", "estimated", "unavailable"}
28
+ )
29
+ RETRY_SOURCES = frozenset({"framework", "provider", "harness"})
30
+ EXACT_TOKENS = frozenset({"provider", "tokenizer", "user_supplied"})
31
+ COUNT_KEYS = ("model_calls", "tool_calls", "iterations", "retries")
32
+ METRIC_KEYS = (*COUNT_KEYS, "tokens", "cost_usd")
33
+ _MONEY_TEXT = re.compile(r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?\Z")
34
+
35
+
36
+ class BudgetValidationError(ValueError):
37
+ """A resource declaration is invalid; errors never include response payloads."""
38
+
39
+
40
+ def count(value: Any, name: str, *, optional: bool = True) -> int | None:
41
+ if value is None and optional:
42
+ return None
43
+ if type(value) is not int or value < 0:
44
+ raise BudgetValidationError(f"{name} must be a nonnegative integer")
45
+ return value
46
+
47
+
48
+ def number(value: Any, name: str, *, optional: bool = True) -> float | None:
49
+ if value is None and optional:
50
+ return None
51
+ try:
52
+ if type(value) not in {int, float} or value < 0 or not math.isfinite(float(value)):
53
+ raise ValueError
54
+ except (ValueError, OverflowError):
55
+ raise BudgetValidationError(f"{name} must be finite and nonnegative") from None
56
+ return float(value)
57
+
58
+
59
+ def amount(value: int | float) -> Fraction:
60
+ """Accumulate declared decimal amounts exactly, avoiding float drift at a cap."""
61
+ number(value, "cost_usd", optional=False)
62
+ return Fraction(str(value))
63
+
64
+
65
+ def money_text(value: Fraction) -> str:
66
+ """Render exact decimal-input arithmetic as portable JSON decimal text."""
67
+ with localcontext() as context:
68
+ context.prec = len(str(value.numerator)) + len(str(value.denominator)) + 5
69
+ text = format(Decimal(value.numerator) / Decimal(value.denominator), "f")
70
+ return text.rstrip("0").rstrip(".") if "." in text else text
71
+
72
+
73
+ def _boolean(value: Any, name: str) -> None:
74
+ if type(value) is not bool:
75
+ raise BudgetValidationError(f"{name} must be a boolean")
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class Reservation:
80
+ """Caller-declared conservative bounds, not proof of a provider billing cap."""
81
+
82
+ tokens: int | None = None
83
+ cost_usd: float | None = None
84
+ provenance: str = "unavailable"
85
+ pricing_known: bool = False
86
+ evidence_refs: tuple[str, ...] = ()
87
+
88
+ def __post_init__(self) -> None:
89
+ count(self.tokens, "reserved tokens")
90
+ number(self.cost_usd, "reserved cost")
91
+ if not isinstance(self.provenance, str) or self.provenance not in {
92
+ "upper_bound",
93
+ "estimated",
94
+ "unavailable",
95
+ }:
96
+ raise BudgetValidationError("unsupported reservation provenance")
97
+ _boolean(self.pricing_known, "pricing_known")
98
+ if not isinstance(self.evidence_refs, (tuple, list)) or any(
99
+ not isinstance(item, str) or not item for item in self.evidence_refs
100
+ ):
101
+ raise BudgetValidationError("reservation evidence_refs must contain opaque identifiers")
102
+ object.__setattr__(self, "evidence_refs", tuple(sorted(set(self.evidence_refs))))
103
+
104
+ @property
105
+ def tokens_known(self) -> bool:
106
+ return self.tokens is not None and self.provenance == "upper_bound"
107
+
108
+ @property
109
+ def cost_known(self) -> bool:
110
+ return self.cost_usd is not None and self.provenance == "upper_bound" and self.pricing_known
111
+
112
+ def to_dict(self) -> dict[str, Any]:
113
+ return asdict(self)
114
+
115
+
116
+ @dataclass(frozen=True)
117
+ class ResourceUsage:
118
+ """One exclusive usage snapshot; streams must explicitly identify final usage."""
119
+
120
+ tokens: int | None = None
121
+ cost_usd: float | None = None
122
+ token_provenance: str | None = None
123
+ cost_provenance: str | None = None
124
+ pricing_known: bool = False
125
+ complete: bool = False
126
+ exclusive: bool = True
127
+ usage_id: str | None = None
128
+
129
+ def __post_init__(self) -> None:
130
+ count(self.tokens, "usage tokens")
131
+ number(self.cost_usd, "usage cost")
132
+ if self.token_provenance is not None and (
133
+ not isinstance(self.token_provenance, str)
134
+ or self.token_provenance not in TOKEN_PROVENANCE
135
+ ):
136
+ raise BudgetValidationError("unsupported token provenance")
137
+ if self.cost_provenance is not None and (
138
+ not isinstance(self.cost_provenance, str) or self.cost_provenance not in COST_PROVENANCE
139
+ ):
140
+ raise BudgetValidationError("unsupported cost provenance")
141
+ for name in ("pricing_known", "complete", "exclusive"):
142
+ _boolean(getattr(self, name), name)
143
+ if self.usage_id is not None and (not isinstance(self.usage_id, str) or not self.usage_id):
144
+ raise BudgetValidationError("usage_id must be an opaque nonempty string")
145
+
146
+ @property
147
+ def tokens_known(self) -> bool:
148
+ return (
149
+ self.complete
150
+ and self.exclusive
151
+ and self.tokens is not None
152
+ and self.token_provenance in EXACT_TOKENS
153
+ )
154
+
155
+ @property
156
+ def cost_known(self) -> bool:
157
+ return (
158
+ self.complete
159
+ and self.exclusive
160
+ and self.cost_usd is not None
161
+ and (
162
+ self.cost_provenance in {"provider_reported", "user_reported"}
163
+ or (
164
+ self.cost_provenance == "calculated"
165
+ and self.pricing_known
166
+ and self.tokens_known
167
+ )
168
+ )
169
+ )
170
+
171
+ def to_dict(self) -> dict[str, Any]:
172
+ return asdict(self)
173
+
174
+
175
+ @dataclass(frozen=True)
176
+ class DispatchOptions:
177
+ """Safe per-binding admission hints; no call arguments or output content."""
178
+
179
+ reservation: Reservation | None = None
180
+ retry_source: str | None = None
181
+ step: StepInfo | None = None
182
+
183
+ def __post_init__(self) -> None:
184
+ if self.step is not None and type(self.step) is not StepInfo:
185
+ raise BudgetValidationError("step must be StepInfo")
186
+ if self.reservation is not None and type(self.reservation) is not Reservation:
187
+ raise BudgetValidationError("reservation must be a Reservation")
188
+ if self.retry_source is not None and (
189
+ not isinstance(self.retry_source, str) or self.retry_source not in RETRY_SOURCES
190
+ ):
191
+ raise BudgetValidationError("unsupported retry source")
192
+
193
+
194
+ @dataclass(frozen=True)
195
+ class BudgetSnapshot:
196
+ """A closed numeric schema prevents arbitrary payloads entering decision evidence."""
197
+
198
+ _json: str
199
+
200
+ def to_dict(self) -> dict[str, Any]:
201
+ return json.loads(self._json)
202
+
203
+ @classmethod
204
+ def from_dict(cls, value: dict[str, Any]) -> BudgetSnapshot:
205
+ fields = {
206
+ "schema_version",
207
+ "scope",
208
+ "hard_spend_cap",
209
+ "deadline_enforcement",
210
+ "metered_boundaries",
211
+ "limits",
212
+ "committed",
213
+ "in_flight",
214
+ "refunded",
215
+ "unknown_usage",
216
+ "total_usage",
217
+ "retry_sources",
218
+ "status_codes",
219
+ "soft_exceeded",
220
+ "overshoot",
221
+ "deadline_remaining_s",
222
+ "last_usage",
223
+ "usage_error",
224
+ "reservation",
225
+ "budget_scope_id",
226
+ "spend_enforcement",
227
+ "token_enforcement",
228
+ "possible_spend_overshoot",
229
+ "unknown_usage_policy",
230
+ "soft_fraction",
231
+ }
232
+ if not isinstance(value, dict) or set(value) != fields or value["schema_version"] != "1.0":
233
+ raise BudgetValidationError("invalid budget snapshot fields")
234
+ if (
235
+ value["scope"] != "wrapped_dispatch"
236
+ or value["hard_spend_cap"] is not False
237
+ or value["deadline_enforcement"] != "cooperative_admission"
238
+ ):
239
+ raise BudgetValidationError("unsupported budget guarantee")
240
+ if not isinstance(value["budget_scope_id"], str) or not value["budget_scope_id"]:
241
+ raise BudgetValidationError("budget scope must have an opaque identity")
242
+ if not isinstance(value["metered_boundaries"], list) or any(
243
+ not isinstance(item, str) or item not in {"model", "tool"}
244
+ for item in value["metered_boundaries"]
245
+ ):
246
+ raise BudgetValidationError("invalid metered boundaries")
247
+ groups = {
248
+ "limits": (set(COUNT_KEYS) | {"tokens", "cost_usd", "deadline_at"}, True),
249
+ "committed": (
250
+ set(COUNT_KEYS) | {"metered_calls", "tokens_known", "cost_known_usd"},
251
+ False,
252
+ ),
253
+ "in_flight": (
254
+ set(COUNT_KEYS)
255
+ | {
256
+ "metered_calls",
257
+ "tokens_reserved",
258
+ "cost_reserved_usd",
259
+ "unknown_token_reservations",
260
+ "unknown_cost_reservations",
261
+ },
262
+ False,
263
+ ),
264
+ "refunded": (set(COUNT_KEYS) | {"tokens_bound", "cost_bound_usd"}, False),
265
+ "unknown_usage": ({"calls", "tokens", "cost", "tokens_held", "cost_held_usd"}, False),
266
+ "total_usage": ({"tokens", "cost_usd"}, True),
267
+ "overshoot": ({"tokens", "cost_usd"}, True),
268
+ "retry_sources": (set(RETRY_SOURCES), False),
269
+ }
270
+ for group, (keys, optional) in groups.items():
271
+ items = value[group]
272
+ if not isinstance(items, dict) or set(items) != keys:
273
+ raise BudgetValidationError("invalid budget counter group")
274
+ for key, item in items.items():
275
+ if optional and item is None:
276
+ continue
277
+ if key.endswith("usd"):
278
+ if not isinstance(item, str) or not _MONEY_TEXT.fullmatch(item):
279
+ raise BudgetValidationError("budget costs must be nonnegative decimal text")
280
+ elif key == "deadline_at":
281
+ number(item, key, optional=optional)
282
+ else:
283
+ count(item, key, optional=optional)
284
+ for key, limit in (("spend_enforcement", "cost_usd"), ("token_enforcement", "tokens")):
285
+ if value[key] not in ("not_configured", "shadow", "best_effort"):
286
+ raise BudgetValidationError("unsupported resource enforcement claim")
287
+ if (value[key] == "not_configured") != (value["limits"][limit] is None):
288
+ raise BudgetValidationError("resource enforcement must match configured limits")
289
+ expected_overshoot = None if value["limits"]["cost_usd"] is None else True
290
+ if value["possible_spend_overshoot"] is not expected_overshoot:
291
+ raise BudgetValidationError("the Python adapter cannot exclude spend overshoot")
292
+ if not isinstance(value["unknown_usage_policy"], str) or value[
293
+ "unknown_usage_policy"
294
+ ] not in {"deny", "escalate", "monitor_only"}:
295
+ raise BudgetValidationError("invalid unknown-usage policy")
296
+ soft = number(value["soft_fraction"], "soft_fraction")
297
+ if soft is not None and soft > 1:
298
+ raise BudgetValidationError("soft_fraction must not exceed one")
299
+ number(value["deadline_remaining_s"], "deadline remaining")
300
+ codes = {
301
+ "ok",
302
+ "not_admitted",
303
+ "unknown_tokens",
304
+ "unknown_cost",
305
+ "bound_exceeded",
306
+ "deadline_expired",
307
+ "duplicate_usage",
308
+ "usage_conflict",
309
+ "usage_collector_error",
310
+ "clock_invalid",
311
+ }
312
+ codes |= {f"limit_{key}" for key in METRIC_KEYS}
313
+ if not isinstance(value["status_codes"], list) or any(
314
+ not isinstance(code, str) or code not in codes for code in value["status_codes"]
315
+ ):
316
+ raise BudgetValidationError("invalid budget status")
317
+ if not isinstance(value["soft_exceeded"], list) or any(
318
+ key not in METRIC_KEYS for key in value["soft_exceeded"]
319
+ ):
320
+ raise BudgetValidationError("invalid soft threshold metric")
321
+ if value["usage_error"] is not None and (
322
+ not isinstance(value["usage_error"], str)
323
+ or value["usage_error"]
324
+ not in {"collector_error", "collector_cancelled", "invalid_usage"}
325
+ ):
326
+ raise BudgetValidationError("invalid usage diagnostic")
327
+ try:
328
+ if value["last_usage"] is not None:
329
+ ResourceUsage(**value["last_usage"])
330
+ if value["reservation"] is not None:
331
+ Reservation(**value["reservation"])
332
+ serialized = json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False)
333
+ except (TypeError, ValueError, OverflowError, RecursionError):
334
+ raise BudgetValidationError("invalid budget usage or reservation") from None
335
+ return cls(serialized)