← All courses ← Course home

Lesson 05 of 05

Wire the cache into a session

Ask the cache first. On a miss, call LanguageModelSession. Then write the new pair. Keep one active Dynamic Profile if the session uses profiles.

Agent brief (llms.md)

Environment setup

Same Xcode 27 app as lesson 02. Add a LanguageModelSession backed by SystemLanguageModel for this first cache. You can swap in CoreAILanguageModel later if you ship a custom chat model. Confirm initializers in the installed SDK.

QuestionUser text
EmbedOn-device vector
EmbedOn-device vector
HitReturn stored answer
EmbedOn-device vector
Misssession.respond
WriteStore pair
import FoundationModels

// Illustrative session shape. Verify names in the installed SDK.
@MainActor
final class CachedAsk {
    let embedder: QuestionEmbedder
    let session: LanguageModelSession
    var rows: [CacheRow] = []
    var stats = CacheStats()
    var minimum: Float = 0.92

    func answer(_ question: String) async throws -> String {
        let vector = try await embedder.vector(for: question)
        stats.lookups += 1
        if let row = lookup(question: vector, rows: rows, minimum: minimum, now: .now) {
            stats.hits += 1
            return row.answer
        }
        stats.misses += 1
        let response = try await session.respond(to: question)
        let text = String(describing: response)
        rows.append(CacheRow(
            id: UUID(),
            question: question,
            answer: text,
            embedding: vector,
            createdAt: .now,
            expiresAt: .now.addingTimeInterval(30 * 60)
        ))
        return text
    }
}

If the session uses Dynamic Profiles, the cache still sits outside the profile body. Profiles pick instructions, tools, and the model. The cache decides whether to call the session at all. One Profile is active when you do generate. See Run an agent with a state machine for that rule.

Do not cache tool-using turns until you can key the row on tool results as well. A cached "next meeting" that ignored a calendar refresh is a product bug.

Sources: WWDC26 session 241 (usage and models), Foundation Models, and Core AI.

Done. You have an on-device semantic cache, a high threshold, and hit-rate counters. That is the EDGE FDE bar for this skill.

Key concepts

  • The cache sits before LanguageModelSession. A hit returns the stored answer.
  • A miss calls session.respond, then writes the pair.
  • Dynamic Profiles pick instructions and tools. The cache decides whether to call the session.
  • Do not cache tool-using turns until rows can key on tool results.

Takeaways

  • Ask the cache first on every question.
  • Tool turns need richer cache keys than question text alone.
  • You now have an on-device semantic cache, a high threshold, and hit-rate counters.