diff --git a/eng/ci/release-kickoff.yml b/eng/ci/release-kickoff.yml
new file mode 100644
index 000000000..62807a741
--- /dev/null
+++ b/eng/ci/release-kickoff.yml
@@ -0,0 +1,206 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+# This pipeline automates the SDK release kickoff process.
+# It bumps package versions, generates a changelog update, creates a release branch,
+# and opens a pull request for the release.
+
+trigger: none
+pr: none
+
+parameters:
+ - name: bumpType
+ displayName: 'Version bump type'
+ type: string
+ default: 'minor'
+ values:
+ - major
+ - minor
+ - patch
+ - explicit
+
+ - name: explicitVersion
+ displayName: 'Explicit version (required if bump type is "explicit", format: X.Y.Z)'
+ type: string
+ default: ''
+
+ - name: versionSuffix
+ displayName: 'Version suffix (e.g., "preview", "rc.1"; leave empty for stable release)'
+ type: string
+ default: ''
+
+pool:
+ vmImage: 'ubuntu-latest'
+
+steps:
+ - checkout: self
+ persistCredentials: true
+ fetchDepth: 0
+
+ - task: UseDotNet@2
+ displayName: 'Install .NET SDK'
+ inputs:
+ packageType: 'sdk'
+ useGlobalJson: true
+
+ - task: UsePythonVersion@0
+ displayName: 'Use Python 3.x'
+ inputs:
+ versionSpec: '3.x'
+
+ - pwsh: |
+ $ErrorActionPreference = 'Stop'
+
+ $repoRoot = "$(Build.SourcesDirectory)"
+ $releasePropsPath = Join-Path $repoRoot 'eng/targets/Release.props'
+ $changelogPath = Join-Path $repoRoot 'CHANGELOG.md'
+ $changelogScript = Join-Path $repoRoot 'generate_changelog.py'
+
+ $bumpType = '${{ parameters.bumpType }}'
+ $explicitVersion = '${{ parameters.explicitVersion }}'
+ $versionSuffix = '${{ parameters.versionSuffix }}'
+
+ # --- Read current version ---
+ [xml]$props = Get-Content $releasePropsPath -Raw
+ $currentVersion = $props.Project.PropertyGroup.VersionPrefix.Trim()
+ Write-Host "Current version: $currentVersion"
+
+ # --- Compute new version ---
+ if ($bumpType -eq 'explicit') {
+ if (-not $explicitVersion) {
+ throw "explicitVersion parameter is required when bumpType is 'explicit'."
+ }
+ if ($explicitVersion -notmatch '^\d+\.\d+\.\d+$') {
+ throw "explicitVersion must be in the format 'X.Y.Z'. Got: '$explicitVersion'"
+ }
+ $newVersion = $explicitVersion
+ }
+ else {
+ $parts = $currentVersion.Split('.')
+ [int]$major = $parts[0]
+ [int]$minor = $parts[1]
+ [int]$patch = $parts[2]
+
+ switch ($bumpType) {
+ 'major' { $major++; $minor = 0; $patch = 0 }
+ 'minor' { $minor++; $patch = 0 }
+ 'patch' { $patch++ }
+ }
+ $newVersion = "$major.$minor.$patch"
+ }
+
+ $fullVersion = $newVersion
+ if ($versionSuffix) {
+ $fullVersion = "$newVersion-$versionSuffix"
+ }
+
+ Write-Host "New version: $newVersion"
+ Write-Host "Full version: $fullVersion"
+ Write-Host "##vso[task.setvariable variable=NewVersion]$newVersion"
+ Write-Host "##vso[task.setvariable variable=FullVersion]$fullVersion"
+ Write-Host "##vso[task.setvariable variable=VersionSuffix]$versionSuffix"
+
+ # --- Update Release.props ---
+ $content = Get-Content $releasePropsPath -Raw
+ $content = $content -replace '[^<]*', "$newVersion"
+ $content = $content -replace '[^<]*', "$versionSuffix"
+ Set-Content -Path $releasePropsPath -Value $content -NoNewline
+ Write-Host "Updated Release.props"
+
+ # --- Update CHANGELOG.md ---
+ Write-Host "Generating changelog..."
+ $changelogEntry = python $changelogScript --tag "v$fullVersion" 2>&1 | Out-String
+ Write-Host "Changelog generator output:"
+ Write-Host $changelogEntry
+
+ $existingChangelog = Get-Content $changelogPath -Raw
+ if ($existingChangelog -match '## Unreleased') {
+ # Insert the new version header after "## Unreleased" and a blank line
+ $updatedChangelog = $existingChangelog -replace '(## Unreleased\r?\n)', "`$1`n## v$fullVersion`n"
+ Set-Content -Path $changelogPath -Value $updatedChangelog -NoNewline
+ Write-Host "Updated CHANGELOG.md"
+ }
+ else {
+ Write-Warning "Could not find '## Unreleased' section in CHANGELOG.md."
+ }
+ displayName: 'Bump version and update changelog'
+
+ - pwsh: |
+ $ErrorActionPreference = 'Stop'
+
+ $fullVersion = "$(FullVersion)"
+ $branchName = "release/v$fullVersion"
+
+ git config user.email "azuredevops@microsoft.com"
+ git config user.name "Azure DevOps Pipeline"
+
+ git checkout -b $branchName
+ git add eng/targets/Release.props
+ git add CHANGELOG.md
+ git commit -m "Bump version to $fullVersion and update changelog"
+ git push origin $branchName
+
+ Write-Host "##vso[task.setvariable variable=ReleaseBranch]$branchName"
+ Write-Host "Release branch '$branchName' created and pushed."
+ displayName: 'Create and push release branch'
+
+ - pwsh: |
+ $ErrorActionPreference = 'Stop'
+
+ $fullVersion = "$(FullVersion)"
+ $newVersion = "$(NewVersion)"
+ $versionSuffix = "$(VersionSuffix)"
+ $branchName = "$(ReleaseBranch)"
+ $repo = "microsoft/durabletask-dotnet"
+
+ $prTitle = "Release v$fullVersion"
+ $prBody = @"
+ ## SDK Release v$fullVersion
+
+ This PR prepares the release of **v$fullVersion**.
+
+ ### Changes
+ - Bumped ``VersionPrefix`` to ``$newVersion`` in ``eng/targets/Release.props``
+ - Updated ``VersionSuffix`` to ``$versionSuffix``
+ - Updated ``CHANGELOG.md``
+
+ ### Release Checklist
+ - [ ] Verify version bump is correct
+ - [ ] Verify CHANGELOG.md entries are accurate
+ - [ ] Verify RELEASENOTES.md files are updated (if needed)
+ - [ ] Merge this PR
+ - [ ] Tag the release: ``git tag v$fullVersion``
+ - [ ] Kick off the [ADO release build](https://dev.azure.com/durabletaskframework/Durable%20Task%20Framework%20CI/_build?definitionId=29)
+ "@
+
+ # Create PR using the Azure DevOps REST API
+ $org = $env:SYSTEM_COLLECTIONURI
+ $project = $env:SYSTEM_TEAMPROJECT
+ $repoId = $env:BUILD_REPOSITORY_ID
+
+ $url = "${org}${project}/_apis/git/repositories/${repoId}/pullrequests?api-version=7.1"
+
+ $body = @{
+ sourceRefName = "refs/heads/$branchName"
+ targetRefName = "refs/heads/main"
+ title = $prTitle
+ description = $prBody
+ } | ConvertTo-Json -Depth 10
+
+ $headers = @{
+ 'Content-Type' = 'application/json'
+ 'Authorization' = "Bearer $(System.AccessToken)"
+ }
+
+ try {
+ $response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body
+ Write-Host "Pull request created: $($response.url)"
+ Write-Host "PR ID: $($response.pullRequestId)"
+ }
+ catch {
+ Write-Warning "Failed to create PR via ADO API: $_"
+ Write-Host "You can create the PR manually at: https://github.com/$repo/compare/main...$branchName"
+ }
+ displayName: 'Open release pull request'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
diff --git a/eng/scripts/Start-Release.ps1 b/eng/scripts/Start-Release.ps1
new file mode 100644
index 000000000..07ccd044e
--- /dev/null
+++ b/eng/scripts/Start-Release.ps1
@@ -0,0 +1,222 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+<#
+.SYNOPSIS
+ Bumps package versions, generates changelog, creates a release branch, and opens a PR.
+
+.DESCRIPTION
+ This script automates the SDK release kickoff process:
+ 1. Bumps the version in eng/targets/Release.props based on the bump type or explicit version.
+ 2. Runs generate_changelog.py to produce a changelog entry.
+ 3. Prepends the generated changelog entry to CHANGELOG.md.
+ 4. Creates a release branch and pushes it.
+ 5. Opens a pull request targeting main.
+
+.PARAMETER BumpType
+ The type of version bump: 'major', 'minor', 'patch', or 'explicit'.
+
+.PARAMETER ExplicitVersion
+ The explicit version to set (required when BumpType is 'explicit'). Format: 'X.Y.Z'.
+
+.PARAMETER VersionSuffix
+ Optional pre-release suffix (e.g., 'preview', 'rc.1'). Leave empty for stable releases.
+#>
+
+param(
+ [Parameter(Mandatory = $true)]
+ [ValidateSet('major', 'minor', 'patch', 'explicit')]
+ [string]$BumpType,
+
+ [Parameter(Mandatory = $false)]
+ [string]$ExplicitVersion = '',
+
+ [Parameter(Mandatory = $false)]
+ [string]$VersionSuffix = ''
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+$repoRoot = (Resolve-Path "$PSScriptRoot/../..").Path
+$releasePropsPath = Join-Path $repoRoot 'eng/targets/Release.props'
+$changelogPath = Join-Path $repoRoot 'CHANGELOG.md'
+$changelogScript = Join-Path $repoRoot 'generate_changelog.py'
+
+function Get-CurrentVersion {
+ [xml]$props = Get-Content $releasePropsPath -Raw
+ $versionPrefix = $props.Project.PropertyGroup.VersionPrefix
+ if (-not $versionPrefix) {
+ throw "Could not find VersionPrefix in $releasePropsPath"
+ }
+ return $versionPrefix.Trim()
+}
+
+function Get-BumpedVersion {
+ param(
+ [string]$CurrentVersion,
+ [string]$BumpType,
+ [string]$ExplicitVersion
+ )
+
+ if ($BumpType -eq 'explicit') {
+ if (-not $ExplicitVersion) {
+ throw "ExplicitVersion is required when BumpType is 'explicit'."
+ }
+ if ($ExplicitVersion -notmatch '^\d+\.\d+\.\d+$') {
+ throw "ExplicitVersion must be in the format 'X.Y.Z'. Got: '$ExplicitVersion'"
+ }
+ return $ExplicitVersion
+ }
+
+ $parts = $CurrentVersion.Split('.')
+ if ($parts.Count -ne 3) {
+ throw "Current version '$CurrentVersion' is not in expected X.Y.Z format."
+ }
+
+ [int]$major = $parts[0]
+ [int]$minor = $parts[1]
+ [int]$patch = $parts[2]
+
+ switch ($BumpType) {
+ 'major' { $major++; $minor = 0; $patch = 0 }
+ 'minor' { $minor++; $patch = 0 }
+ 'patch' { $patch++ }
+ }
+
+ return "$major.$minor.$patch"
+}
+
+function Set-Version {
+ param(
+ [string]$NewVersion,
+ [string]$Suffix
+ )
+
+ $content = Get-Content $releasePropsPath -Raw
+
+ # Update VersionPrefix
+ $content = $content -replace '[^<]*', "$NewVersion"
+
+ # Update VersionSuffix
+ $content = $content -replace '[^<]*', "$Suffix"
+
+ Set-Content -Path $releasePropsPath -Value $content -NoNewline
+ Write-Host "Updated $releasePropsPath -> VersionPrefix=$NewVersion, VersionSuffix=$Suffix"
+}
+
+function Update-Changelog {
+ param(
+ [string]$VersionTag
+ )
+
+ Write-Host "Generating changelog for tag '$VersionTag'..."
+
+ # Run the changelog generator
+ $changelogEntry = & python $changelogScript --tag $VersionTag 2>&1
+ if ($LASTEXITCODE -ne 0) {
+ Write-Warning "Changelog generation returned non-zero exit code. Output: $changelogEntry"
+ }
+
+ # Read the existing changelog
+ $existingChangelog = Get-Content $changelogPath -Raw
+
+ # Replace "## Unreleased" section with the new version entry
+ # The unreleased content gets moved under the new version heading
+ if ($existingChangelog -match '(?s)(## Unreleased\r?\n)(.*?)((?=\r?\n## )|$)') {
+ $unreleasedContent = $Matches[2].Trim()
+ $newSection = "## Unreleased`n`n## v$VersionTag`n"
+ if ($unreleasedContent) {
+ $newSection = "## Unreleased`n`n## v$VersionTag`n$unreleasedContent`n"
+ }
+ $updatedChangelog = $existingChangelog -replace '(?s)## Unreleased\r?\n.*?(?=(\r?\n## )|$)', $newSection
+ Set-Content -Path $changelogPath -Value $updatedChangelog -NoNewline
+ Write-Host "Updated CHANGELOG.md with version v$VersionTag"
+ }
+ else {
+ Write-Warning "Could not find '## Unreleased' section in CHANGELOG.md. Skipping changelog update."
+ }
+}
+
+# --- Main Script ---
+
+Write-Host "=== SDK Release Kickoff ==="
+Write-Host ""
+
+# Step 1: Compute new version
+$currentVersion = Get-CurrentVersion
+Write-Host "Current version: $currentVersion"
+
+$newVersion = Get-BumpedVersion -CurrentVersion $currentVersion -BumpType $BumpType -ExplicitVersion $ExplicitVersion
+Write-Host "New version: $newVersion"
+
+$fullVersion = $newVersion
+if ($VersionSuffix) {
+ $fullVersion = "$newVersion-$VersionSuffix"
+}
+Write-Host "Full version: $fullVersion"
+
+# Step 2: Create release branch
+$branchName = "release/v$fullVersion"
+Write-Host ""
+Write-Host "Creating release branch: $branchName"
+
+Push-Location $repoRoot
+try {
+ git checkout -b $branchName
+ if ($LASTEXITCODE -ne 0) { throw "Failed to create branch '$branchName'." }
+
+ # Step 3: Bump version in Release.props
+ Set-Version -NewVersion $newVersion -Suffix $VersionSuffix
+
+ # Step 4: Update CHANGELOG.md
+ Update-Changelog -VersionTag $fullVersion
+
+ # Step 5: Commit and push
+ git add eng/targets/Release.props
+ git add CHANGELOG.md
+ git commit -m "Bump version to $fullVersion and update changelog"
+ if ($LASTEXITCODE -ne 0) { throw "Failed to commit changes." }
+
+ git push origin $branchName
+ if ($LASTEXITCODE -ne 0) { throw "Failed to push branch '$branchName'." }
+
+ Write-Host ""
+ Write-Host "Release branch '$branchName' pushed successfully."
+
+ # Step 6: Open a PR using the GitHub CLI
+ $prTitle = "Release v$fullVersion"
+ $prBody = @"
+## SDK Release v$fullVersion
+
+This PR prepares the release of **v$fullVersion**.
+
+### Changes
+- Bumped ``VersionPrefix`` to ``$newVersion`` in ``eng/targets/Release.props``
+- Updated ``VersionSuffix`` to ``$VersionSuffix``
+- Updated ``CHANGELOG.md``
+
+### Release Checklist
+- [ ] Verify version bump is correct
+- [ ] Verify CHANGELOG.md entries are accurate
+- [ ] Verify RELEASENOTES.md files are updated (if needed)
+- [ ] Merge this PR
+- [ ] Tag the release: ``git tag v$fullVersion``
+- [ ] Kick off the [ADO release build](https://dev.azure.com/durabletaskframework/Durable%20Task%20Framework%20CI/_build?definitionId=29) targeting ``refs/tags/v$fullVersion``
+"@
+
+ Write-Host "Opening pull request..."
+ gh pr create --base main --head $branchName --title $prTitle --body $prBody
+ if ($LASTEXITCODE -ne 0) {
+ Write-Warning "Failed to create PR via 'gh'. You can create it manually at: https://github.com/microsoft/durabletask-dotnet/compare/main...$branchName"
+ }
+ else {
+ Write-Host "Pull request created successfully."
+ }
+}
+finally {
+ Pop-Location
+}
+
+Write-Host ""
+Write-Host "=== Release kickoff complete ==="