Skip to content

Correlate application spans and run summaries

Deterministic

Measure a custom validation phase, correlate concurrent application requests to AML runId values, and retrieve one portable summary per completed evaluation without a global latest-run value.

A shared runtime can execute several roots concurrently. Use application request context to associate the runtime’s public start event with the request that initiated it:

const requestContext = new AsyncLocalStorage<string>()
const runIdsByRequest = new Map<string, string>()
const summaries = createTraceSummaryCollector()
const runtime = new AmlRuntime({
onTraceError: (error, event) => reportTraceFailure(error, event),
trace: summaries.trace,
})
runtime.on("start", event => {
const requestId = requestContext.getStore()
if (requestId !== undefined) runIdsByRequest.set(requestId, event.runId)
})

This mapping remains explicit under overlap. It does not depend on trace arrival order or a mutable latest-run slot.

async function ReviewPhase({ candidates }: { readonly candidates: number }) {
return await withTraceSpan("review.validate", async () => `validated ${candidates}`)
}

AML measures the callback beneath the active component. It does not add the callback’s arguments, return value, or other application content to the trace.

async function evaluateRequest(requestId: string, candidates: number) {
try {
const value = await requestContext.run(requestId, async () =>
runtime.evaluate(<ReviewPhase candidates={candidates} />)
)
const runId = runIdsByRequest.get(requestId)
if (runId === undefined) throw new Error(`AML run identity was not captured for ${requestId}`)
const summary = summaries.forRun(runId)
if (summary === undefined) throw new Error(`AML summary was not completed for ${runId}`)
return { summary, value }
} finally {
const runId = runIdsByRequest.get(requestId)
if (runId !== undefined) summaries.deleteRun(runId)
runIdsByRequest.delete(requestId)
}
}
const [first, second] = await Promise.all([evaluateRequest("request-1", 3), evaluateRequest("request-2", 7)])

The collector retains completed summaries until deleteRun(runId). To answer whether a supplied capability was actually invoked, inspect summary.acpToolCalls.byName[providerCapabilityName] without storing raw ACP events. The aggregate keeps only initial call counts and exact provider-reported names; it does not retain arguments, results, updates, prompts, or model text. A name is a provider-contract identifier, not a portable backend or MCP-server identity.

summary.tools measures the different declarative AML <Tool> execution-span boundary. A call routed to an AML Tool may appear in both aggregates and is intentionally not deduplicated. providerUsage: [] explicitly means no provider usage was reported. If ACP reports usage, the entry keeps that provider-owned JSON shape without inferred tokens, model calls, cache data, cost, or billing meaning.

The maintained trace summary example contains the complete imports and runnable deterministic flow.