Correlate application spans and run summaries
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.
Runtime and request correlation
Section titled “Runtime and request correlation”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.
Time application work
Section titled “Time application work”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.
Retrieve and release the summary
Section titled “Retrieve and release the summary”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.
Complete source
Section titled “Complete source”The maintained trace summary example contains the complete imports and runnable deterministic flow.