Group model calls in a trace
Scope a workflow or own a conversation across UI events.
withTrace groups model calls and owns the workflow's final status.
import FoundationModels
import LedgeKit
@available(iOS 26.0, macOS 26.0, *)
func summarizeAndReview(_ text: String, client: LedgeClient) async throws -> String {
let writer = try LedgeAgent(name: "summarizer", version: 1)
let reviewer = try LedgeAgent(name: "reviewer", version: 1)
return try await client.withTrace("summarize_and_review", version: 1, input: text) { trace in
let summary = try await trace.record(
LanguageModelSession(instructions: "Summarize the text."), agent: writer
).respond(to: text, recording: .init(input: text))
let review = try await trace.record(
LanguageModelSession(instructions: "Review the summary for clarity."), agent: reviewer
).respond(to: summary.content, recording: .init(input: summary.content))
return review.content
}
}Returning from the closure finishes the trace. Throwing records failure or cancellation and rethrows the original error. Application postprocessing can fail the trace without changing an already completed span's native result.
Conversations across UI events
Keep a manually owned trace when the workflow outlives one closure:
let trace = try await client.startTrace("conversation", version: 1)
let session = trace.record(nativeSession, agent: agent)
// Keep trace and session in the conversation owner and respond on each turn.
let response = try await session.respond(to: message)
// When the conversation ends:
await trace.finish()Call fail(error) or cancel() for those outcomes. The owner must finish only
after its model calls complete. Finishing early marks remaining spans interrupted;
later completion cannot overwrite the finalized trace. Starting a custom span on
a finished trace throws LedgeTraceError.alreadyFinished.
Parallel calls, retries, and tools
A trace contains a flat collection of spans. Each actual model attempt has its own span ID and timing. Retry a failed call normally to record another span in the trace.
Use separate native sessions for parallel requests and await them before finishing the trace. The timeline preserves overlap. Tools remain in native provider capture; a tool that calls another model can record that model through the same trace.
An empty trace is never uploaded. Interrupted work has no invented completion time. See delivery and recovery.