Add an experiment field on the feature: off, or a pair of record ids with a bucket. The Dynamic Profile body reads the bucket and returns one Profile. The transcript stays. Only the instructions and tools change on the next prompt.
struct SupportProfile: LanguageModelSession.DynamicProfile {
var registry: PromptRegistry
var experiment: PromptExperiment
var body: some LanguageModelSession.DynamicProfile {
switch experiment.choice {
case .record(let id):
Profile { SupportCopy(record: registry.record(id: id)) }
case .control:
Profile { SupportCopy(record: registry.live!) }
}
}
}
mutating func rollback(to id: String) {
records = records.map { record in
var copy = record
if copy.id == id { copy.status = "live" }
else if copy.status == "live" { copy.status = "retired" }
return copy
}
}
Confirm experiment types in your own code. Apple does not ship A/B. You assign the bucket. Keep the assignment stable for a user so the session does not flip every prompt. A flip can invalidate the KV cache and raise TTFT. Measure that in Stream tokens and measure speed.
Do not roll back from a hallway opinion. Run Test AI behavior before you ship on both ids, then switch live. If a live prompt is harmful, roll back first and eval second.
Next, ship the file so the team can edit it.
Key concepts
- A/B is two record ids plus a stable user bucket. Apple does not ship A/B.
- Rollback flips status: the target id goes live, the former live is retired.
- Unstable bucket assignment can invalidate the KV cache and raise TTFT.
- Roll back a harmful live prompt first. Eval second for non-urgent changes.
Takeaways
- Rollback is a status change, not a new App Store build.
- Keep bucket assignment stable per user.
- Run evals on both ids before switching live for non-urgent changes.