Environment setup
Same Xcode 27 app. Create the session with a Dynamic Profile. Confirm LanguageModelSession(profile:) in the installed SDK.
@MainActor
final class ContextAssembler {
let model: SystemLanguageModel
var session: LanguageModelSession?
func ask(question: String, slices: [ContextSlice], tools: [any Tool]) async throws -> String {
let budget = try await makeBudget(
model: model,
question: question,
instructions: "Answer using only the packed context."
)
let packed = pack(slices: slices, leftover: budget.leftover)
let used = packed.reduce(0) { $0 + $1.tokens }
precondition(used <= budget.leftover)
session = LanguageModelSession(profile: PackedProfile(pack: packed, tools: tools))
let response = try await session!.respond(to: question)
print("leftoverAfterPack:", budget.leftover - used, "slices:", packed.count)
return String(describing: response)
}
}
Print leftover tokens, slice ids, and response.usage if the SDK gives it. Compare a packed run with an unpacked dump on the same question. Packed should be smaller and, on a good pack, no worse on a short yes/no check.
If leftover is negative before packing, cut instructions first. If packing yields zero slices and the question needed files, that is a retrieval miss, not a packer win. Send the user to Search private files on the phone.
Sources: WWDC26 241, WWDC26 242, Foundation Models.
Key concepts
ContextAssemblerwires budget, rank, and Profile into oneask()function.- The pipeline is budget, rank, one active Profile, respond, measure.
- Create
LanguageModelSession(profile:)thensession.respond(to:). - Log leftover tokens, slice ids, and usage if available.
Takeaways
- Refuse an overfull pack before you call the model.
- Compare packed versus unpacked on the same question.
- Zero slices when files were needed is a retrieval miss, not a packer win.