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,554 @@
|
|
|
1
|
+
"""Source-bound coordination observations; unknown boundaries stay unknown."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import Counter, defaultdict
|
|
6
|
+
from hashlib import sha256
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Iterable
|
|
9
|
+
|
|
10
|
+
from agentloop.graph import ExecutionGraph
|
|
11
|
+
from agentloop.integrations.omnigent.evidence import read_observations
|
|
12
|
+
from agentloop.interoperability.artifacts import native_bytes, write_artifact
|
|
13
|
+
from agentloop.interoperability.contracts import ImportReceipt
|
|
14
|
+
from agentloop.interoperability.evidence import read_external
|
|
15
|
+
from agentloop.interoperability.validation import ImportValidationError
|
|
16
|
+
from agentloop.interventions import canonical_json
|
|
17
|
+
from agentloop.timing import event_interval_ms, timestamp_ms
|
|
18
|
+
from agentloop.tracer import AgentTrace
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _fail(reason: str) -> None:
|
|
22
|
+
raise ImportValidationError("invalid_coordination", "coordination", reason)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _scope(receipt: dict) -> tuple:
|
|
26
|
+
identity = receipt["external_identity"]
|
|
27
|
+
return (
|
|
28
|
+
receipt["source"]["system"],
|
|
29
|
+
identity["job_id"],
|
|
30
|
+
identity["trial_id"],
|
|
31
|
+
identity["task_digest"] or receipt["source"]["artifact_sha256"],
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _trajectory_scope(receipt: dict) -> tuple:
|
|
36
|
+
identity = receipt["external_identity"]
|
|
37
|
+
return (
|
|
38
|
+
receipt["source"]["system"],
|
|
39
|
+
identity["job_id"],
|
|
40
|
+
identity["trial_id"],
|
|
41
|
+
identity["task_digest"],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _overlap(intervals: list[tuple[float, float]]) -> float | None:
|
|
46
|
+
if not intervals:
|
|
47
|
+
return None
|
|
48
|
+
changes = Counter()
|
|
49
|
+
for start, end in intervals:
|
|
50
|
+
changes[start] += 1
|
|
51
|
+
changes[end] -= 1
|
|
52
|
+
active, overlap, previous = 0, 0.0, None
|
|
53
|
+
for moment, change in sorted(changes.items()):
|
|
54
|
+
if previous is not None and active >= 2:
|
|
55
|
+
overlap += moment - previous
|
|
56
|
+
active += change
|
|
57
|
+
previous = moment
|
|
58
|
+
return overlap
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _captured_arguments(value) -> bool:
|
|
62
|
+
if not isinstance(value, dict):
|
|
63
|
+
return False
|
|
64
|
+
pending = [value]
|
|
65
|
+
while pending:
|
|
66
|
+
node = pending.pop()
|
|
67
|
+
if isinstance(node, dict):
|
|
68
|
+
if node.get("capture") == "omitted":
|
|
69
|
+
return False
|
|
70
|
+
pending.extend(node.values())
|
|
71
|
+
elif isinstance(node, list):
|
|
72
|
+
pending.extend(node)
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _aggregate(value) -> dict | None:
|
|
77
|
+
if not isinstance(value, dict):
|
|
78
|
+
return None
|
|
79
|
+
keys = {
|
|
80
|
+
"total_prompt_tokens",
|
|
81
|
+
"total_completion_tokens",
|
|
82
|
+
"total_cached_tokens",
|
|
83
|
+
"total_cost_usd",
|
|
84
|
+
"input_tokens",
|
|
85
|
+
"output_tokens",
|
|
86
|
+
"cached_input_tokens",
|
|
87
|
+
"cost_usd",
|
|
88
|
+
}
|
|
89
|
+
result = {
|
|
90
|
+
key: item
|
|
91
|
+
for key, item in value.items()
|
|
92
|
+
if key in keys and (item is None or type(item) in {int, float})
|
|
93
|
+
}
|
|
94
|
+
extra = value.get("extra")
|
|
95
|
+
usage_scope = extra.get("usage_scope") if isinstance(extra, dict) else None
|
|
96
|
+
if isinstance(usage_scope, str) and usage_scope in {
|
|
97
|
+
"includes_subagents",
|
|
98
|
+
"agent_and_subagents",
|
|
99
|
+
"agent_only",
|
|
100
|
+
"trajectory_only",
|
|
101
|
+
"inclusive",
|
|
102
|
+
"exclusive",
|
|
103
|
+
}:
|
|
104
|
+
result["usage_scope"] = usage_scope
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _critical_path(trace: AgentTrace, receipt: dict) -> dict:
|
|
109
|
+
graph = ExecutionGraph.from_trace(trace)
|
|
110
|
+
nodes = {node.node_id: node for node in graph.nodes}
|
|
111
|
+
roots = [node for node in graph.nodes if node.parent_id is None]
|
|
112
|
+
complete = (
|
|
113
|
+
bool(nodes)
|
|
114
|
+
and len(nodes) == len(graph.nodes)
|
|
115
|
+
and len(roots) == 1
|
|
116
|
+
and receipt["source_metadata"].get("conflicting_segments", 0) == 0
|
|
117
|
+
and all(
|
|
118
|
+
event.metadata.get("timing_available") is True
|
|
119
|
+
and event.metadata.get("timing_provenance") == "external_reported"
|
|
120
|
+
for event in trace.events
|
|
121
|
+
)
|
|
122
|
+
and all(event_interval_ms(node) is not None for node in graph.nodes)
|
|
123
|
+
and all(node.parent_id is None or node.parent_id in nodes for node in graph.nodes)
|
|
124
|
+
and all(not event.metadata.get("unresolved_parent_span_id") for event in trace.events)
|
|
125
|
+
)
|
|
126
|
+
# Native graph traversal detects cycles without inventing sequence edges for
|
|
127
|
+
# qualified imports. Every path must reach the sole recorded root.
|
|
128
|
+
if complete:
|
|
129
|
+
visited, pending = set(), [roots[0].node_id]
|
|
130
|
+
children = defaultdict(list)
|
|
131
|
+
for node in graph.nodes:
|
|
132
|
+
if node.parent_id:
|
|
133
|
+
children[node.parent_id].append(node.node_id)
|
|
134
|
+
while pending:
|
|
135
|
+
identity = pending.pop()
|
|
136
|
+
if identity in visited:
|
|
137
|
+
complete = False
|
|
138
|
+
break
|
|
139
|
+
visited.add(identity)
|
|
140
|
+
pending.extend(children[identity])
|
|
141
|
+
complete = complete and len(visited) == len(nodes)
|
|
142
|
+
value = graph.critical_path().to_dict() if complete else None
|
|
143
|
+
return {
|
|
144
|
+
"critical_path_ms": value["duration_ms"] if value else None,
|
|
145
|
+
"node_ids": value["node_ids"] if value else [],
|
|
146
|
+
"coverage": "recorded_connected_span_tree" if value else "unavailable",
|
|
147
|
+
"scope": "this source trace's explicit span tree; not complete cross-process task causality",
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def summarize_coordination(traces: Iterable[AgentTrace], receipts: Iterable[ImportReceipt]) -> dict:
|
|
152
|
+
"""Join captured projections/receipts without merging trace or session IDs."""
|
|
153
|
+
traces, receipts = tuple(traces), tuple(receipts)
|
|
154
|
+
documents = [receipt.to_dict() for receipt in receipts]
|
|
155
|
+
if len({row["receipt_id"] for row in documents}) != len(documents):
|
|
156
|
+
_fail("duplicate source receipt identity")
|
|
157
|
+
trace_index = {trace.run_id: trace for trace in traces}
|
|
158
|
+
if len(trace_index) != len(traces):
|
|
159
|
+
_fail("duplicate native trace identity")
|
|
160
|
+
bindings, receipt_by_run = {}, {}
|
|
161
|
+
for receipt in documents:
|
|
162
|
+
for captured in receipt["traces"]:
|
|
163
|
+
identity = captured["run_id"]
|
|
164
|
+
if identity in bindings:
|
|
165
|
+
_fail("multiple receipts claim one native trace")
|
|
166
|
+
bindings[identity] = captured["trace_sha256"]
|
|
167
|
+
receipt_by_run[identity] = receipt
|
|
168
|
+
for trace in traces:
|
|
169
|
+
if sha256(native_bytes(trace)).hexdigest() != bindings.get(trace.run_id):
|
|
170
|
+
_fail("native projection differs from captured receipt")
|
|
171
|
+
|
|
172
|
+
actors, actor_index, trajectory_index, event_actors = [], {}, {}, {}
|
|
173
|
+
relationships, per_trace, raw_aggregates, models, tools = [], [], [], [], []
|
|
174
|
+
outcomes = {}
|
|
175
|
+
for row in documents:
|
|
176
|
+
if row["source"]["format"] == "harbor_trial":
|
|
177
|
+
key = (row["external_identity"]["job_id"], row["external_identity"]["trial_id"])
|
|
178
|
+
if key in outcomes:
|
|
179
|
+
_fail("ambiguous parent trial identity")
|
|
180
|
+
outcomes[key] = row
|
|
181
|
+
for trace in traces:
|
|
182
|
+
receipt = receipt_by_run[trace.run_id]
|
|
183
|
+
identity, source = receipt["external_identity"], receipt["source"]
|
|
184
|
+
if source["format"] == "harbor_trial":
|
|
185
|
+
# Agent-phase usage already includes trajectory/subagent usage.
|
|
186
|
+
raw_aggregates.append(
|
|
187
|
+
{
|
|
188
|
+
"receipt_id": receipt["receipt_id"],
|
|
189
|
+
"scope": "trial_agent_context",
|
|
190
|
+
"metrics": _aggregate(receipt["source_metadata"].get("usage")),
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
continue
|
|
194
|
+
external = read_external(trace)
|
|
195
|
+
if external is None:
|
|
196
|
+
_fail("coordination requires qualified source projections")
|
|
197
|
+
observed = read_observations(trace)
|
|
198
|
+
if observed:
|
|
199
|
+
relationships.extend(
|
|
200
|
+
{"run_id": trace.run_id, **row} for row in observed["relationships"]
|
|
201
|
+
)
|
|
202
|
+
per_trace.append({"run_id": trace.run_id, **_critical_path(trace, receipt)})
|
|
203
|
+
aggregate = receipt["source_metadata"].get("source_final_metrics")
|
|
204
|
+
if aggregate is not None:
|
|
205
|
+
raw_aggregates.append(
|
|
206
|
+
{
|
|
207
|
+
"receipt_id": receipt["receipt_id"],
|
|
208
|
+
"scope": "source_final_metrics_may_include_children",
|
|
209
|
+
"metrics": _aggregate(aggregate),
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
raw_aggregates.extend(
|
|
213
|
+
{
|
|
214
|
+
"receipt_id": receipt["receipt_id"],
|
|
215
|
+
"scope": "source_aggregate",
|
|
216
|
+
"metrics": _aggregate(item),
|
|
217
|
+
}
|
|
218
|
+
for item in receipt["source_metadata"].get("source_aggregates", [])
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
atif = source["format"] == "atif"
|
|
222
|
+
selected = (
|
|
223
|
+
[None] if atif else [event for event in trace.events if event.operation_kind == "agent"]
|
|
224
|
+
)
|
|
225
|
+
for event in selected:
|
|
226
|
+
actor_id = trace.run_id if event is None else f"{trace.run_id}/{event.event_id}"
|
|
227
|
+
aliases = event.metadata.get("source.omnigent", {}) if event else {}
|
|
228
|
+
interval = event_interval_ms(event) if event else None
|
|
229
|
+
if event and (
|
|
230
|
+
event.metadata.get("timing_provenance") != "external_reported"
|
|
231
|
+
or receipt["source_metadata"].get("conflicting_segments", 0)
|
|
232
|
+
):
|
|
233
|
+
interval = None
|
|
234
|
+
if atif and external["runtime_ms"] is not None:
|
|
235
|
+
start, end = timestamp_ms(trace.started_at), timestamp_ms(trace.ended_at)
|
|
236
|
+
if start is not None and end is not None and end >= start:
|
|
237
|
+
interval = (start, end)
|
|
238
|
+
agent = receipt["source_metadata"].get("agent") or {}
|
|
239
|
+
trial = outcomes.get((identity["job_id"], identity["trial_id"]))
|
|
240
|
+
status = (
|
|
241
|
+
receipt["outcome"]["execution_status"]
|
|
242
|
+
if event is None
|
|
243
|
+
else event.metadata.get("source_execution_status", "unknown")
|
|
244
|
+
)
|
|
245
|
+
actor = {
|
|
246
|
+
"actor_id": actor_id,
|
|
247
|
+
"run_id": trace.run_id,
|
|
248
|
+
"event_id": event.event_id if event else None,
|
|
249
|
+
"source_receipt_id": receipt["receipt_id"],
|
|
250
|
+
"source_identity": identity,
|
|
251
|
+
"agent_name": aliases.get("agent_name") or agent.get("name"),
|
|
252
|
+
"declared_role": aliases.get("agent_role")
|
|
253
|
+
or (agent.get("extra") or {}).get("role"),
|
|
254
|
+
"agent_id": aliases.get("agent_id"),
|
|
255
|
+
"parent_agent_id": aliases.get("parent_agent_id"),
|
|
256
|
+
"session_id": aliases.get("session_id") or identity["session_id"],
|
|
257
|
+
"parent_session_id": aliases.get("parent_session_id"),
|
|
258
|
+
"execution_status": status,
|
|
259
|
+
"runtime_ms": interval[1] - interval[0] if interval else None,
|
|
260
|
+
"timing_basis": "external_reported_interval" if interval else "unknown",
|
|
261
|
+
"task_outcome": trial["outcome"] if trial else receipt["outcome"],
|
|
262
|
+
"outcome_scope": "parent trial; child contribution not separately verified"
|
|
263
|
+
if trial
|
|
264
|
+
else "source receipt; role labels do not establish correctness",
|
|
265
|
+
}
|
|
266
|
+
if not isinstance(actor["declared_role"], str):
|
|
267
|
+
actor["declared_role"] = None
|
|
268
|
+
actors.append(actor)
|
|
269
|
+
actor_index[actor_id] = (actor, interval)
|
|
270
|
+
if atif:
|
|
271
|
+
key = (*_trajectory_scope(receipt), identity["trajectory_id"])
|
|
272
|
+
if key in trajectory_index:
|
|
273
|
+
_fail("ambiguous trajectory document identity")
|
|
274
|
+
trajectory_index[key] = actor_id
|
|
275
|
+
else:
|
|
276
|
+
event_actors[(trace.run_id, event.event_id)] = actor_id
|
|
277
|
+
models.extend(
|
|
278
|
+
(trace, event, receipt) for event in trace.events if event.operation_kind == "model"
|
|
279
|
+
)
|
|
280
|
+
tools.extend(
|
|
281
|
+
(trace, event, receipt) for event in trace.events if event.operation_kind == "tool"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
alias_index = defaultdict(list)
|
|
285
|
+
for actor in actors:
|
|
286
|
+
identity = actor["source_identity"]
|
|
287
|
+
receipt = receipt_by_run[actor["run_id"]]
|
|
288
|
+
namespace = (receipt["source"]["system"], identity["job_id"], identity["trial_id"])
|
|
289
|
+
for agent_id, session_id in {
|
|
290
|
+
(actor["agent_id"], actor["session_id"]),
|
|
291
|
+
(actor["agent_id"], None),
|
|
292
|
+
(None, actor["session_id"]),
|
|
293
|
+
}:
|
|
294
|
+
if agent_id is not None or session_id is not None:
|
|
295
|
+
alias_index[(*namespace, agent_id, session_id)].append(actor["actor_id"])
|
|
296
|
+
for actor in actors:
|
|
297
|
+
if actor["parent_agent_id"] is None and actor["parent_session_id"] is None:
|
|
298
|
+
continue
|
|
299
|
+
identity = actor["source_identity"]
|
|
300
|
+
receipt = receipt_by_run[actor["run_id"]]
|
|
301
|
+
matches = alias_index.get(
|
|
302
|
+
(
|
|
303
|
+
receipt["source"]["system"],
|
|
304
|
+
identity["job_id"],
|
|
305
|
+
identity["trial_id"],
|
|
306
|
+
actor["parent_agent_id"],
|
|
307
|
+
actor["parent_session_id"],
|
|
308
|
+
),
|
|
309
|
+
[],
|
|
310
|
+
)
|
|
311
|
+
resolved = len(matches) == 1 and matches[0] != actor["actor_id"]
|
|
312
|
+
relationships.append(
|
|
313
|
+
{
|
|
314
|
+
"kind": "declared_parent_identity",
|
|
315
|
+
"source_actor_id": actor["actor_id"],
|
|
316
|
+
"target_actor_id": matches[0] if resolved else None,
|
|
317
|
+
"target_count": len(matches),
|
|
318
|
+
"resolved": resolved,
|
|
319
|
+
"causal_edge": False,
|
|
320
|
+
"basis": "external_reported_identity; no dispatch or dependency boundary",
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
handoffs = []
|
|
325
|
+
for trace in traces:
|
|
326
|
+
receipt = receipt_by_run[trace.run_id]
|
|
327
|
+
if receipt["source"]["format"] == "atif":
|
|
328
|
+
for relation in receipt["relationships"]:
|
|
329
|
+
if relation["kind"] != "delegation":
|
|
330
|
+
continue
|
|
331
|
+
target = (
|
|
332
|
+
trajectory_index.get((*_trajectory_scope(receipt), relation["target_id"]))
|
|
333
|
+
if relation["resolved"]
|
|
334
|
+
else None
|
|
335
|
+
)
|
|
336
|
+
handoffs.append(
|
|
337
|
+
{
|
|
338
|
+
"parent_actor_id": trace.run_id,
|
|
339
|
+
"child_actor_id": target,
|
|
340
|
+
"target_id": relation["target_id"],
|
|
341
|
+
"basis": "external_reported_trajectory_delegation",
|
|
342
|
+
"causal_edge": False,
|
|
343
|
+
}
|
|
344
|
+
)
|
|
345
|
+
else:
|
|
346
|
+
events = {event.event_id: event for event in trace.events}
|
|
347
|
+
ancestor_cache = {}
|
|
348
|
+
|
|
349
|
+
def parent_actor(event_id):
|
|
350
|
+
path, seen, current = [], set(), event_id
|
|
351
|
+
result = None
|
|
352
|
+
while current is not None and current not in seen:
|
|
353
|
+
if current in ancestor_cache:
|
|
354
|
+
result = ancestor_cache[current]
|
|
355
|
+
break
|
|
356
|
+
found = event_actors.get((trace.run_id, current))
|
|
357
|
+
if found:
|
|
358
|
+
result = found
|
|
359
|
+
break
|
|
360
|
+
seen.add(current)
|
|
361
|
+
path.append(current)
|
|
362
|
+
ancestor = events.get(current)
|
|
363
|
+
current = ancestor.parent_id if ancestor else None
|
|
364
|
+
for key in path:
|
|
365
|
+
ancestor_cache[key] = result
|
|
366
|
+
return result
|
|
367
|
+
|
|
368
|
+
for event in trace.events:
|
|
369
|
+
child = event_actors.get((trace.run_id, event.event_id))
|
|
370
|
+
if child and event.parent_id:
|
|
371
|
+
parent = events.get(event.parent_id)
|
|
372
|
+
if parent:
|
|
373
|
+
handoffs.append(
|
|
374
|
+
{
|
|
375
|
+
"parent_actor_id": parent_actor(parent.event_id),
|
|
376
|
+
"parent_event_id": parent.event_id,
|
|
377
|
+
"child_actor_id": child,
|
|
378
|
+
"target_id": event.event_id,
|
|
379
|
+
"basis": "explicit_source_span_parent",
|
|
380
|
+
"causal_edge": True,
|
|
381
|
+
}
|
|
382
|
+
)
|
|
383
|
+
else:
|
|
384
|
+
relationships.append(
|
|
385
|
+
{
|
|
386
|
+
"run_id": trace.run_id,
|
|
387
|
+
"kind": "missing_span_parent",
|
|
388
|
+
"source_event_id": event.event_id,
|
|
389
|
+
"resolved": False,
|
|
390
|
+
"causal_edge": False,
|
|
391
|
+
}
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
child_ids = {row["child_actor_id"] for row in handoffs if row["child_actor_id"]}
|
|
395
|
+
parents = {row["parent_actor_id"] for row in handoffs if row["parent_actor_id"]}
|
|
396
|
+
for row in handoffs:
|
|
397
|
+
actor, interval = actor_index.get(row["child_actor_id"], (None, None))
|
|
398
|
+
row.update(
|
|
399
|
+
child_runtime_ms=actor["runtime_ms"] if actor else None,
|
|
400
|
+
handoff_wait_ms=None,
|
|
401
|
+
wait_missing_reason="no recorded dispatch/blocking-join interval; child runtime is not caller wait",
|
|
402
|
+
)
|
|
403
|
+
leaves = [pair for key, pair in actor_index.items() if key not in parents]
|
|
404
|
+
timed = [interval for _, interval in leaves if interval is not None]
|
|
405
|
+
overlap = _overlap(timed)
|
|
406
|
+
|
|
407
|
+
model_parents = {
|
|
408
|
+
(trace.run_id, event.parent_id) for trace, event, _ in models if event.parent_id
|
|
409
|
+
}
|
|
410
|
+
leaf_models = [
|
|
411
|
+
(trace, event, receipt)
|
|
412
|
+
for trace, event, receipt in models
|
|
413
|
+
if (trace.run_id, event.event_id) not in model_parents
|
|
414
|
+
]
|
|
415
|
+
known_usage = {}
|
|
416
|
+
for direction in ("input", "output"):
|
|
417
|
+
selected = [
|
|
418
|
+
getattr(event, direction + "_tokens")
|
|
419
|
+
for _, event, _ in leaf_models
|
|
420
|
+
if event.metadata.get(direction + "_tokens_available") is True
|
|
421
|
+
]
|
|
422
|
+
known_usage["known_" + direction + "_tokens"] = sum(selected) if selected else None
|
|
423
|
+
known_usage[direction + "_tokens"] = (
|
|
424
|
+
sum(selected) if leaf_models and len(selected) == len(models) else None
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
cache_values = []
|
|
428
|
+
for _, event, receipt in leaf_models:
|
|
429
|
+
cache = (
|
|
430
|
+
event.metadata.get("source_metrics", {}).get("cached_tokens")
|
|
431
|
+
if receipt["source"]["format"] == "atif"
|
|
432
|
+
else event.metadata.get("cached_input_tokens")
|
|
433
|
+
)
|
|
434
|
+
if type(cache) is int:
|
|
435
|
+
cache_values.append(cache)
|
|
436
|
+
known_usage["known_cached_input_tokens"] = sum(cache_values) if cache_values else None
|
|
437
|
+
known_usage["cached_input_tokens"] = (
|
|
438
|
+
sum(cache_values) if leaf_models and len(cache_values) == len(models) else None
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
duplicate_groups = defaultdict(list)
|
|
442
|
+
supported_tools = 0
|
|
443
|
+
for trace, event, receipt in tools:
|
|
444
|
+
arguments = event.metadata.get("source_arguments")
|
|
445
|
+
if receipt["source"]["format"] == "atif" and _captured_arguments(arguments):
|
|
446
|
+
supported_tools += 1
|
|
447
|
+
digest = sha256(canonical_json(arguments).encode()).hexdigest()
|
|
448
|
+
duplicate_groups[(*_scope(receipt), event.name, digest)].append(
|
|
449
|
+
{"run_id": trace.run_id, "event_id": event.event_id}
|
|
450
|
+
)
|
|
451
|
+
duplicates = [
|
|
452
|
+
{
|
|
453
|
+
"tool_name": key[-2],
|
|
454
|
+
"arguments_sha256": key[-1],
|
|
455
|
+
"spans": spans,
|
|
456
|
+
"basis": "identical_captured_arguments_same_named_tool_in_source_scope",
|
|
457
|
+
"confidence": "medium",
|
|
458
|
+
"safe_reuse": "unestablished",
|
|
459
|
+
"estimated_savings_ms": None,
|
|
460
|
+
"estimated_savings_usd": None,
|
|
461
|
+
}
|
|
462
|
+
for key, spans in sorted(duplicate_groups.items(), key=lambda item: str(item[0]))
|
|
463
|
+
if len(spans) > 1
|
|
464
|
+
]
|
|
465
|
+
known_duplicates = sum(len(row["spans"]) - 1 for row in duplicates)
|
|
466
|
+
reported_counts = [
|
|
467
|
+
read_external(trace)["reported_model_call_count"]
|
|
468
|
+
for trace in traces
|
|
469
|
+
if receipt_by_run[trace.run_id]["source"]["format"] != "harbor_trial"
|
|
470
|
+
]
|
|
471
|
+
unknown_children = sum(row["child_actor_id"] is None for row in handoffs)
|
|
472
|
+
return {
|
|
473
|
+
"schema_version": "1.0",
|
|
474
|
+
"source_receipt_ids": sorted(row["receipt_id"] for row in documents),
|
|
475
|
+
"actors": actors,
|
|
476
|
+
"handoffs": handoffs,
|
|
477
|
+
"relationships": relationships,
|
|
478
|
+
"handoff_count": len(handoffs),
|
|
479
|
+
"observed_child_count": len(child_ids),
|
|
480
|
+
"unknown_child_count": unknown_children,
|
|
481
|
+
"unknown_parent_reference_count": sum(
|
|
482
|
+
row.get("resolved") is False
|
|
483
|
+
and row["kind"]
|
|
484
|
+
in {"missing_span_parent", "declared_parent_identity", "ended_parent_reference"}
|
|
485
|
+
for row in relationships
|
|
486
|
+
),
|
|
487
|
+
"child_failure_count": sum(
|
|
488
|
+
actor_index[key][0]["execution_status"] in {"failed", "timed_out", "cancelled"}
|
|
489
|
+
for key in child_ids
|
|
490
|
+
),
|
|
491
|
+
"child_status_unknown_count": sum(
|
|
492
|
+
actor_index[key][0]["execution_status"] == "unknown" for key in child_ids
|
|
493
|
+
),
|
|
494
|
+
"handoff_wait_ms": None,
|
|
495
|
+
"handoff_wait_coverage": {"measured": 0, "declared": len(handoffs)},
|
|
496
|
+
"parallel_overlap_ms": overlap
|
|
497
|
+
if len(timed) == len(leaves) and not unknown_children
|
|
498
|
+
else None,
|
|
499
|
+
"known_parallel_overlap_ms": overlap,
|
|
500
|
+
"overlap_coverage": {
|
|
501
|
+
"timed_leaf_actors": len(timed),
|
|
502
|
+
"observed_leaf_actors": len(leaves),
|
|
503
|
+
"unknown_children": unknown_children,
|
|
504
|
+
},
|
|
505
|
+
"overlap_basis": "union of times with at least two recorded leaf-actor intervals; enclosing parent actors excluded; overlap does not prove independent work or synchronized source clocks",
|
|
506
|
+
"critical_path_ms": per_trace[0]["critical_path_ms"]
|
|
507
|
+
if len(per_trace) == 1 and not unknown_children and not relationships
|
|
508
|
+
else None,
|
|
509
|
+
"critical_path_by_trace": per_trace,
|
|
510
|
+
"model_call_count": sum(reported_counts)
|
|
511
|
+
if reported_counts and all(value is not None for value in reported_counts)
|
|
512
|
+
else None,
|
|
513
|
+
"model_call_count_basis": "reported multiplicity for recorded source blocks; complete executor coverage not established",
|
|
514
|
+
"observed_model_blocks": len(models),
|
|
515
|
+
"retry_count": None,
|
|
516
|
+
"usage": {
|
|
517
|
+
**known_usage,
|
|
518
|
+
"raw_inclusive_aggregates": raw_aggregates,
|
|
519
|
+
"aggregation_basis": "unique native source documents/spans, model leaves only; inclusive/root/trial aggregates retained separately and never added",
|
|
520
|
+
"coverage": "recorded_model_leaves_only",
|
|
521
|
+
"provider_billing_verified": False,
|
|
522
|
+
},
|
|
523
|
+
"possible_duplicate_work_count": known_duplicates
|
|
524
|
+
if tools and supported_tools == len(tools)
|
|
525
|
+
else None,
|
|
526
|
+
"known_possible_duplicate_work_count": known_duplicates,
|
|
527
|
+
"possible_duplicate_work": duplicates,
|
|
528
|
+
"duplicate_work_coverage": {
|
|
529
|
+
"captured_tool_inputs": supported_tools,
|
|
530
|
+
"observed_tools": len(tools),
|
|
531
|
+
},
|
|
532
|
+
"review_overhead_ms": None,
|
|
533
|
+
"review_quality_delta": None,
|
|
534
|
+
"review_missing_reason": "requires controlled reviewed-vs-unreviewed task pairs; reviewer labels do not establish a quality effect",
|
|
535
|
+
"limitations": [
|
|
536
|
+
"source hashes bind supplied bytes, not authenticity",
|
|
537
|
+
"missing child logs, timing, retries and contributions remain unknown",
|
|
538
|
+
"session IDs and span links correlate observations without creating causal dependencies",
|
|
539
|
+
"identical tool inputs can require independent verification or have changing state/side effects; investigate with the existing semantic redundancy and paired-intervention contracts",
|
|
540
|
+
],
|
|
541
|
+
"synthetic": any(trace.metadata.get("synthetic") is True for trace in traces),
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def write_coordination_report(traces, receipts, out: str | Path) -> Path:
|
|
546
|
+
from agentloop.html_report import coordination_to_html
|
|
547
|
+
|
|
548
|
+
report = summarize_coordination(traces, receipts)
|
|
549
|
+
root = Path(out)
|
|
550
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
551
|
+
root = root.resolve()
|
|
552
|
+
write_artifact(root, "coordination.json", (canonical_json(report) + "\n").encode())
|
|
553
|
+
write_artifact(root, "coordination.html", coordination_to_html(report).encode())
|
|
554
|
+
return root / "coordination.json"
|