You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add session properties and Foundation Models 27 HistoryView compatibility - #313
Add session-scoped properties to dynamic instructions and Tools, following the Foundation Models 27 contract while retaining OS 26 deployment support.
Review changes
Transcript.HistoryView is a value-type struct conforming to RandomAccessCollection, MutableCollection, RangeReplaceableCollection and ExpressibleByArrayLiteral, with a Hashable/Strideable Index and self-typed subsequences.
Request Tools explicitly carry their session-property owner and instruction-free history snapshot. Detached execution and native wrappers re-establish the binding without relying on inherited TaskLocals.
History is read-only while dynamic instructions or a Tool is active. Custom values remain writable and isolated between sessions.
Defaults are evaluated outside the storage lock and rechecked before storage. SessionPropertyKey.Value has no extra Sendable constraint.
Fresh validation (2026-10-19)
Merged upstream main 371b142 with a normal history-preserving merge.
CI=1 GIT_CONFIG_GLOBAL=/dev/null swift test: 815 tests in 85 suites passed.
New regression uses ordinary import AnyLanguageModel, without SPI or @testable, to name RequestContext, call the zero-argument resolver, read its existing fields, and compile the Tool-scoped wrapper. It failed to compile before the fix and passes afterward.
The full run includes detached/static/dynamic Tool and native-wrapper session-property regressions, plus both package/SDK compatibility sources.
Strict Swift format lint on changed files and git diff --check passed.
The per-process Git configuration bypass avoids this machine's missing MLX mirror; no global configuration changed. CI mode skips live model-weight/Apple Intelligence generation.
Profile composition is separate in #314. Session construction/runtime and new provider-facing hooks remain Compatibility SPI in #315 pending the outstanding construction decision. No published history was rewritten.
SessionPropertyKey.Value is missing Foundation Models 27's Sendable constraint. This allows a non-Sendable value to be stored in SessionPropertyValues, even though that container is exposed as @unchecked Sendable, and makes this protocol's public surface incompatible with the API it mirrors. Constrain the associated value type to Sendable.
Filter instruction entries from writable HistoryView
The public HistoryView is an unrestricted [Transcript.Entry], so this setter currently accepts .instructions entries even though history is documented and exposed as instruction-free. Such entries remain in session.transcript but disappear from the next history read, and can duplicate preserved static instructions. Filter instruction entries here just as the dynamic-session initializer does.
Document session mutation during request context resolution
Binding writable session properties makes resolvedRequestContext() observably mutate the session when dynamic instructions update a property (as the new revision test demonstrates), so its existing API documentation—“Calling this method doesn't change the session”—is now false. Update that contract to describe the permitted property mutation.
HistoryView is an unrestricted [Transcript.Entry], so replacing history with Array(otherSession.transcript) can persist that session's .instructions entries. The ordinary history getter hides them, but dynamic resolution snapshots the full transcript, exposing those entries through bound history and sending them alongside the newly resolved instructions. Filter .instructions out of incoming history before combining it with the preserved static-instructions prefix.
The task-local history is applied to every SessionPropertyValues instance, not just the bound session. While session A's dynamic instructions run, reading sessionB.properties.history returns A's snapshot, and replacing B's history incorrectly triggers the read-only precondition. Check SessionPropertyBinding.values === self before applying either the snapshot or the write restriction so unrelated sessions remain isolated.
Addressed both additional history findings from the latest review in 3ad28fb: task-local history is now scoped to its owning SessionPropertyValues instance, and incoming instruction entries are filtered before persistence. Added regressions for reading/writing a second session from a read-only instructions evaluation and replacing history from another session transcript. swift test --filter SessionPropertyTests passes 11 tests; strict format lint passes.
Thanks, @qoli, for all the work on this stack, and for keeping up with every round of review.
I'd like to get as much of this as we can into a 1.x release. The catch is that anything public in 1.x has to stay as it is until 2.0, so where Foundation Models 27 has an API, ours should have the same shape. A few changes would get us there:
Could Transcript.HistoryView be a struct, like Apple's, conforming to the same collection protocols (RandomAccessCollection, MutableCollection, RangeReplaceableCollection, ExpressibleByArrayLiteral)? As a typealias for [Entry], code can rely on it being an array, and then it can't change later without breaking that code.
The functions that only the providers call — resolvedRequestContext(including:options:), profileDidProduceEntries(_:requestContext:currentRoundEntries:), withProfileToolExecution(requestContext:currentRoundEntries:operation:), and the others like them — could go behind @_spi(Compatibility), the way Preserve attachments in tool outputs #312 does it.
One question: what happens when a tool that uses @SessionProperty runs with MLX, llama.cpp, or the system model? From reading the code, it looks like it might crash there.
qoli
changed the title
Add session properties to dynamic instructions
Add session properties and Foundation Models 27 HistoryView compatibility
Oct 17, 2026
Add session properties and Foundation Models 27 HistoryView compatibility #313: HistoryView is a struct with the SDK’s collection conformances, custom Index and self-typed slices. RequestContext/resolution is Compatibility SPI. I also found and fixed the Tool binding gap: request Tools now carry their owning session properties/history and re-establish that binding at call time. Native FoundationModels.Tool wrappers are tested from detached tasks for two sessions, without inherited TaskLocal values. MLX/llama.cpp code paths compile and relevant tests pass; I have not run live model-weight generation.
#349 does not add native Tool delegate/callback forwarding, so it stays separate from #297. Anthropic’s ordinary non-streaming, single-request Tool behavior is preserved; automatic continuation is profile-only. Upstream #343’s repeated-call behavior is preserved throughout.
The PR descriptions include dependency notes and isolated provider-review links. #317 remains draft: its retained public-construction example needs the construction contract settled before publication.
Integrated local verification on the dependency aggregate: 851 default tests; 1,017 with MLX/Llama/CoreML; 745 with AsyncHTTPClient. watchOS device build and watchOS Simulator build-for-testing also passed. Live weights / Apple Intelligence generation were not exercised. The current #313 workflow is awaiting maintainer approval, rather than reporting a code/test failure.
A final default build/test run also passed in a fresh SwiftPM scratch directory. Switching AsyncHTTPClient off in the reused .build had left importable AHC artifacts in the product search path (canImport(AsyncHTTPClient) in EventSource), causing inactive C-module resolution errors; isolation confirmed this was build-product contamination. No dependency source or lockfile changes were committed.
Thanks, @qoli. The split makes this much easier to follow, and HistoryView looks right now. A few things from going through it:
313:RequestContext and resolvedRequestContext() were already public in 1.0 — so models outside the package can read their inputs — and putting them behind SPI would break those models. Could you keep them public and put only the new overloads and hooks behind @_spi(Compatibility)? While you're there, Tool.SessionProperty could move into 313 from Add shared profile execution behind Compatibility SPI #315.
314: Apple's historyTransform doesn't take a @Sendable closure. Could ours match, so the same code compiles against both?
344, 345, 346, 348: the replacement helpers drop entries whose ID is already in the transcript, and the tool output ID is the provider's call ID. If a server reuses call IDs across turns — call_0 every time, say — a request can silently lose that round's tool outputs, and that's for everyone, not only profile users. Could you move one shared helper into Add shared profile execution behind Compatibility SPI #315, keep transcript + inFlight when there's no profile, and add a test with the same call ID in two rounds that checks the second request? In Integrate dynamic profiles with Anthropic Tool loops #346, the in-flight messages are also built and encoded when there's no profile.
348: making non-streaming Ollama continue after tool calls is probably the right fix, but it changes things for everyone using Ollama. Could you move it into its own PR, so we can look at it alongside how Integrate dynamic profiles with Anthropic Tool loops #346 handles Anthropic?
Add Foundation Models 27 dynamic profile composition APIs #314 at 982cc07 removes @sendable from historyTransform and its stored closure types. Equivalent non-Sendable function values compile against both AnyLanguageModel and the installed FoundationModels 27 SDK; an additional runtime test exercises a captured non-Sendable reference.
Both branches now include main 371b142 via normal merges. Fresh full SwiftPM runs passed: 815 tests / 85 suites for #313, and 822 / 86 for #314. The regressions were confirmed to fail to compile before their respective fixes. The remaining runtime/provider items are still being worked through separately.
Follow-up on item 4 of #313 (comment): the common mapper is now in #315, and #344/#345/#346/#348 preserve the ordinary transcript + inFlight path. New regressions reuse the same call ID across rounds and turns and check the continuation requests. Anthropic's ordinary nonstreaming branch no longer builds/encodes unused in-flight messages.
All provider heads have been normally merged with the updated dependencies and main 303f8a1, verified, and pushed. The detailed shared fix and per-PR validation are in #315 and the provider descriptions. #317 remains draft; its integrated checks pass 930 tests / 92 suites by default and 798 / 83 with AsyncHTTPClient from an isolated scratch build.
Items 1 and 3 remain covered by the earlier public-API/Tool.SessionProperty and non-Sendable historyTransform fixes. Item 2's public profile initializer and item 5's separate ordinary Ollama PR are still outstanding, not silently treated as addressed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add session-scoped properties to dynamic instructions and Tools, following the Foundation Models 27 contract while retaining OS 26 deployment support.
Review changes
Transcript.HistoryViewis a value-type struct conforming to RandomAccessCollection, MutableCollection, RangeReplaceableCollection and ExpressibleByArrayLiteral, with a Hashable/Strideable Index and self-typed subsequences.LanguageModelSession.RequestContextand zero-argumentresolvedRequestContext()APIs. New profile overloads/hooks remain Compatibility SPI in Add shared profile execution behind Compatibility SPI #315.Tool.SessionPropertynow belongs to this PR rather than Add shared profile execution behind Compatibility SPI #315.SessionPropertyKey.Valuehas no extra Sendable constraint.Fresh validation (2026-10-19)
371b142with a normal history-preserving merge.CI=1 GIT_CONFIG_GLOBAL=/dev/null swift test: 815 tests in 85 suites passed.import AnyLanguageModel, without SPI or @testable, to name RequestContext, call the zero-argument resolver, read its existing fields, and compile the Tool-scoped wrapper. It failed to compile before the fix and passes afterward.git diff --checkpassed.The per-process Git configuration bypass avoids this machine's missing MLX mirror; no global configuration changed. CI mode skips live model-weight/Apple Intelligence generation.
Profile composition is separate in #314. Session construction/runtime and new provider-facing hooks remain Compatibility SPI in #315 pending the outstanding construction decision. No published history was rewritten.