2026 AI & Machine Learning
WWDC26 · 22 min · AI & Machine Learning
Build agentic app experiences with the Foundation Models framework
Learn how to take your intelligence features further with Foundation Models framework primitives for dynamic context and agentic workflows. We’ll walk through engineering shared context, setting up privacy boundaries, and managing key value caching. Discover how to orchestrate smooth handoffs between local and server models.
Watch at developer.apple.com ↗Chapters
- 0:00 — Introduction
- 2:47 — The example app and agents
- 3:47 — Declaring a dynamic profile
- 4:45 — Dynamic instructions
- 5:36 — Configuring models per phase
- 7:21 — Transcript management and history transforms
- 8:50 — Custom modifiers
- 9:39 — Lifecycle modifiers and session properties
- 12:52 — Orchestration: baton-pass
- 14:06 — Orchestration: phone-a-friend and skills
- 15:18 — Tool calling mode
- 17:12 — Transcript error handling
- 18:27 — Performance, accuracy, and evaluations
- 21:24 — Next steps
Code shown on screen · 17 snippets
DynamicInstructions
// DynamicInstructions
struct BrainstormFacilitator: DynamicInstructions {
var orchestrator: CraftOrchestrator
var body: some DynamicInstructions {
Instructions {
"You are a warm and friendly expert crafting brainstorm facilitator."
}
// Tools
GenerateProjectTitle()
// Conditionally include Origami knowledge
if orchestrator.techniques.contains(.origami) {
OrigamiExpert()
}
}
} DynamicProfile
// DynamicProfile
struct CraftProfile: LanguageModelSession.DynamicProfile {
var orchestrator: CraftOrchestrator
var body: some DynamicProfile {
switch orchestrator.mode {
case .brainstorming:
Profile { BrainstormFacilitator(orchestrator: orchestrator) }
.model(orchestrator.pccLanguageModel)
.temperature(1)
case .planning:
Profile { TutorialAuthor(orchestrator: orchestrator) }
.model(orchestrator.pccLanguageModel)
.reasoningLevel(.deep)
case .reviewing:
Profile { CraftCoach() }
.model(orchestrator.systemLanguageModel)
}
}
} Initialize your session with your dynamic profile
// Initialize your session with your dynamic profile
let session = LanguageModelSession(profile: CraftProfile(orchestrator: orchestrator)) Transcript management
// Transcript management
struct CraftProfile: LanguageModelSession.DynamicProfile {
var orchestrator: CraftOrchestrator
var body: some DynamicProfile {
switch orchestrator.mode {
case .reviewing:
Profile { CraftCoach() }
.model(orchestrator.systemLanguageModel)
.historyTransform { history in
// Update the history for your profile
guard let latestResponseIndex = lastResponseEntryIndex(history) else {
return history
}
let filteredHistory = history[0..<latestResponseIndex].filter { entry in
isToolCallsOrToolOutput(entry)
}
return filteredHistory + history[latestResponseIndex...]
}
}
}
} Custom modifiers
// Custom modifiers
struct DroppingToolCallsProfileModifier: LanguageModelSession.DynamicProfileModifier {
func body(content: Content) -> some DynamicProfile {
content
.historyTransform { history in
guard let latestResponseIndex = lastResponseEntryIndex(history) else {
return history
}
let filteredHistory = history[0..<latestResponseIndex].filter { entry in
isToolCallsOrToolOutput(entry)
}
return filteredHistory + history[latestResponseIndex...]
}
}
}
extension LanguageModelSession.DynamicProfile {
func droppingCompletedToolCalls() -> some DynamicProfile {
self.modifier(DroppingToolCallsProfileModifier())
}
} History management modifiers
// History management modifiers
import FoundationModelsUtilities
struct CraftProfile: LanguageModelSession.DynamicProfile {
var orchestrator: CraftOrchestrator
var body: some DynamicProfile {
switch orchestrator.mode {
case .reviewing:
Profile { CraftCoach() }
// Keep the most recent 10 entries
// after dropping finished tool calls
.rollingWindow(size: .entries(10))
.droppingCompletedToolCalls()
}
}
} Lifecycle modifiers
// Lifecycle modifiers
struct CraftProfile: LanguageModelSession.DynamicProfile {
(\.history) var history
var orchestrator: CraftOrchestrator
var body: some DynamicProfile {
switch orchestrator.mode {
case .planning:
Profile { TutorialAuthor(orchestrator: orchestrator) }
.model(orchestrator.pccLanguageModel)
.reasoningLevel(.deep)
.onResponse {
// Update history
if history.count > 50, let responseIndex = lastResponseIndex(history) {
history = history[responseIndex...]
}
}
}
}
} Declare a custom session property
// Session properties — declaration
extension SessionPropertyValues {
var summary: String?
} Read and write session properties in a profile
// Session properties
struct CraftProfile: LanguageModelSession.DynamicProfile {
(\.history) var history
(\.summary) var summary
var orchestrator: CraftOrchestrator
var body: some DynamicProfile {
switch orchestrator.mode {
case .planning:
Profile {
TutorialAuthor(orchestrator: orchestrator)
if let summary {
Instructions { "Summary: \(summary)" }
}
}
.onResponse {
if history.count > 50, let responseIndex = lastResponse(history.prefix(40)) {
summary = try await summarize(history[0..<responseIndex])
history = history[responseIndex...]
}
}
}
}
} Orchestration: baton-pass
// Baton-pass
struct CraftProfile: LanguageModelSession.DynamicProfile {
var orchestrator: CraftOrchestrator
var body: some DynamicProfile {
switch orchestrator.mode {
case .brainstorm:
Profile {
BrainstormInstructions()
BatonPassTool()
}
.onToolCall { orchestrator.mode = .tutorial }
.model(orchestrator.serverModel)
case .tutorial:
Profile {
TutorialInstructions()
BatonPassTool()
}
.onToolCall { orchestrator.mode = .brainstorm }
.model(orchestrator.systemModel)
}
}
} Orchestration: phone-a-friend
// Phone-a-friend
struct CraftProfile: LanguageModelSession.DynamicProfile {
var body: some DynamicProfile {
Profile {
BrainstormInstructions()
PhoneFriendTool(
name: "generate_title",
description: "Generate a creative project title",
profile: TitleProfile()
)
}
}
}
struct PhoneFriendTool<P: LanguageModelSession.DynamicProfile>: Tool {
func call(arguments: GeneratedContent) async throws -> String {
let session = LanguageModelSession(profile: profile())
let response = try await session.respond(to: arguments)
return response.content
}
} The skills pattern
// The skills pattern
struct CraftingSkills: LanguageModelSession.DynamicInstructions {
var activations: SkillActivations
var body: some DynamicInstructions {
Skills(activations: activations) {
Skill(
name: "origami_folds",
description: "Details about specific types of folds",
prompt: """
Valley Fold: Paper is folded toward you, creating a V-shaped crease
Mountain Fold: Paper is folded away from you, creating an inverted V
...
"""
)
Skill(...)
Skill(...)
}
}
} Tool calling mode
// Tool calling mode
public struct ToolCallingMode: Sendable {
public static let allowed: ToolCallingMode
public static let disallowed: ToolCallingMode
public static let required: ToolCallingMode
}
// Pass tool calling mode as a profile modifier
struct OrigamiExpert: LanguageModelSession.DynamicProfile {
var body: some LanguageModelSession.DynamicProfile {
Profile {
Instructions("You are an origami expert")
QueryOrigamiDatabaseTool()
ShowDirectionsTool()
}
.toolCallingMode(.required)
}
}
// Or pass it as a generation option
let response = try await session.respond(
to: "Write out the instructions for folding a paper crane.",
options: GenerationOptions(toolCallingMode: .required)
) Escaping a tool call loop
// Escaping a tool call loop
struct OrigamiExpert: LanguageModelSession.DynamicProfile {
let state: OrigamiAppState
var body: some LanguageModelSession.DynamicProfile {
Profile {
Instructions("Answer questions about how to fold origami")
QueryOrigamiDatabaseTool()
}
.toolCallingMode(state.queriedDatabase ? .disallowed : .required)
.onToolCall { state.queriedDatabase = true }
}
} Define a tool that throws an error
// Define a tool that throws an error
var output: String?
struct Arguments {
var answer: String
}
func call(arguments: Arguments) async throws -> Never {
output = arguments.answer
throw CancellationError()
}
} Set the transcript error handling policy
// Specify transcript behavior on a profile
struct OrigamiExpert: LanguageModelSession.DynamicProfile {
let state: OrigamiAppState
var body: some LanguageModelSession.DynamicProfile {
Profile {
Instructions("Answer questions about how to fold origami")
QueryOrigamiDatabaseTool()
}
.transcriptErrorHandlingPolicy(.preserveTranscript)
}
}
// Or specify it on a session
let session = LanguageModelSession()
session.transcriptErrorHandlingPolicy = .preserveTranscript
// Policy options
extension LanguageModelSession {
public struct TranscriptErrorHandlingPolicy: Sendable {
// Roll the transcript back to its previous state
public static let revertTranscript: TranscriptErrorHandlingPolicy
// Keep the transcript in state following an error
public static let preserveTranscript: TranscriptErrorHandlingPolicy
}
} Transcript mutation
// Transcript mutation
public final class LanguageModelSession: Sendable {
public var transcriptErrorHandlingPolicy: TranscriptErrorHandlingPolicy { get set }
// Transcript is now settable
public var transcript: Transcript { get set }
// But you must not modify it during a response!
public var isResponding: Bool { get }
}