From b8315b8fcb95971375d835ac50931a200b0f4453 Mon Sep 17 00:00:00 2001 From: Mackenzie Zastrow Date: Wed, 28 Jan 2026 10:35:55 -0500 Subject: [PATCH 1/2] Add a decision for opt-in breaking changes --- team/DECISIONS.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/team/DECISIONS.md b/team/DECISIONS.md index 733564f2..41a3fcf3 100644 --- a/team/DECISIONS.md +++ b/team/DECISIONS.md @@ -99,3 +99,24 @@ The tradeoff is that `HookProvider` exposure can leak implementation details for - The capability requires responding to multiple distinct lifecycle events - Users need to customize which events to subscribe to or add callbacks beyond base class defaults + + +## Pay for Play: Opt-In Breaking Changes Are Acceptable + +**Date**: Jan 28, 2026 + +### Decision + +Small breaking changes that follow the "pay for play" principle are acceptable without a major version bump. Programs can call new APIs to access new features, but programs that choose not to do so are unaffected — old code continues to work as it did before. + +### Rationale + +Strict semver adherence can slow SDK development when the breaking change only affects users who explicitly adopt new functionality. If existing code paths remain unaffected, the practical impact on users is minimal. + +For example, converting a `TypedDict` to `total=False` is technically breaking if existing implementations don't provide the new optional members. However, if the only way to encounter those new members is by adding a new tool that uses the new format, the change is effectively "pay for play." Users who don't adopt the new tool never observe the break. + +This applies when the breaking change is gated behind new functionality — users who don't touch the new feature never see the break, and those who do will find the breakage more obvious since it's tied to something they just added. + +This doesn't apply when existing code breaks without any user action, or when the change affects default behavior. If someone upgrades and their code stops working with no obvious reason why, that's a bad experience we want to avoid. + +See also: [Raymond Chen on "pay for play" in API design](https://devblogs.microsoft.com/oldnewthing/20260127-00/?p=112018) From f52b1c1dceb5e791a0ddd89f38bf2f813a310b25 Mon Sep 17 00:00:00 2001 From: Mackenzie Zastrow Date: Wed, 28 Jan 2026 12:24:41 -0500 Subject: [PATCH 2/2] Clarify example --- team/DECISIONS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/team/DECISIONS.md b/team/DECISIONS.md index 41a3fcf3..cc9cecfa 100644 --- a/team/DECISIONS.md +++ b/team/DECISIONS.md @@ -113,7 +113,7 @@ Small breaking changes that follow the "pay for play" principle are acceptable w Strict semver adherence can slow SDK development when the breaking change only affects users who explicitly adopt new functionality. If existing code paths remain unaffected, the practical impact on users is minimal. -For example, converting a `TypedDict` to `total=False` is technically breaking if existing implementations don't provide the new optional members. However, if the only way to encounter those new members is by adding a new tool that uses the new format, the change is effectively "pay for play." Users who don't adopt the new tool never observe the break. +For example, converting a `TypedDict` to `total=False` is technically breaking change - code that creates instances of that `TypedDict` without the new field will still work, but code that *reads* from the `TypedDict` and expects the field to always be present would break. However, if the old field is only missing when using a new tool, users who don't adopt that tool never encounter the missing field. The break is "pay for play": you only see it if you opt into the new functionality. This applies when the breaking change is gated behind new functionality — users who don't touch the new feature never see the break, and those who do will find the breakage more obvious since it's tied to something they just added.