diff --git a/src/PowerShellEditorServices/Index.cs b/src/PowerShellEditorServices/Index.cs
new file mode 100644
index 000000000..078579aea
--- /dev/null
+++ b/src/PowerShellEditorServices/Index.cs
@@ -0,0 +1,159 @@
+// <auto-generated>
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// (with alterations)
+#if NET5_0_OR_GREATER
+[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Index))]
+#else
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace System
+{
+    /// <summary>Represent a type can be used to index a collection either from the start or the end.</summary>
+    /// <remarks>
+    /// Index is used by the C# compiler to support the new index syntax
+    /// <code>
+    /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ;
+    /// int lastElement = someArray[^1]; // lastElement = 5
+    /// </code>
+    /// </remarks>
+    internal readonly struct Index : IEquatable<Index>
+    {
+        private readonly int _value;
+
+        /// <summary>Construct an Index using a value and indicating if the index is from the start or from the end.</summary>
+        /// <param name="value">The index value. it has to be zero or positive number.</param>
+        /// <param name="fromEnd">Indicating if the index is from the start or from the end.</param>
+        /// <remarks>
+        /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element.
+        /// </remarks>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public Index(int value, bool fromEnd = false)
+        {
+            if (value < 0)
+            {
+                ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
+            }
+
+            if (fromEnd)
+                _value = ~value;
+            else
+                _value = value;
+        }
+
+        // The following private constructors mainly created for perf reason to avoid the checks
+        private Index(int value) => _value = value;
+
+        /// <summary>Create an Index pointing at first element.</summary>
+        public static Index Start => new Index(0);
+
+        /// <summary>Create an Index pointing at beyond last element.</summary>
+        public static Index End => new Index(~0);
+
+        /// <summary>Create an Index from the start at the position indicated by the value.</summary>
+        /// <param name="value">The index value from the start.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Index FromStart(int value)
+        {
+            if (value < 0)
+            {
+                ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
+            }
+
+            return new Index(value);
+        }
+
+        /// <summary>Create an Index from the end at the position indicated by the value.</summary>
+        /// <param name="value">The index value from the end.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Index FromEnd(int value)
+        {
+            if (value < 0)
+            {
+                ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
+            }
+
+            return new Index(~value);
+        }
+
+        /// <summary>Returns the index value.</summary>
+        public int Value
+        {
+            get
+            {
+                if (_value < 0)
+                    return ~_value;
+                else
+                    return _value;
+            }
+        }
+
+        /// <summary>Indicates whether the index is from the start or the end.</summary>
+        public bool IsFromEnd => _value < 0;
+
+        /// <summary>Calculate the offset from the start using the giving collection length.</summary>
+        /// <param name="length">The length of the collection that the Index will be used with. length has to be a positive value</param>
+        /// <remarks>
+        /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values.
+        /// we don't validate either the returned offset is greater than the input length.
+        /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and
+        /// then used to index a collection will get out of range exception which will be same affect as the validation.
+        /// </remarks>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public int GetOffset(int length)
+        {
+            int offset = _value;
+            if (IsFromEnd)
+            {
+                // offset = length - (~value)
+                // offset = length + (~(~value) + 1)
+                // offset = length + value + 1
+
+                offset += length + 1;
+            }
+            return offset;
+        }
+
+        /// <summary>Indicates whether the current Index object is equal to another object of the same type.</summary>
+        /// <param name="value">An object to compare with this object</param>
+        public override bool Equals([NotNullWhen(true)] object? value) => value is Index && _value == ((Index)value)._value;
+
+        /// <summary>Indicates whether the current Index object is equal to another Index object.</summary>
+        /// <param name="other">An object to compare with this object</param>
+        public bool Equals(Index other) => _value == other._value;
+
+        /// <summary>Returns the hash code for this instance.</summary>
+        public override int GetHashCode() => _value;
+
+        /// <summary>Converts integer number to an Index.</summary>
+        public static implicit operator Index(int value) => FromStart(value);
+
+        /// <summary>Converts the value of the current Index object to its equivalent string representation.</summary>
+        public override string ToString()
+        {
+            if (IsFromEnd)
+                return ToStringFromEnd();
+
+            return ((uint)Value).ToString();
+        }
+
+        private string ToStringFromEnd()
+        {
+            return '^' + Value.ToString();
+        }
+
+        internal static class ThrowHelper
+        {
+            [DoesNotReturn, MethodImpl(MethodImplOptions.NoInlining)]
+            public static void ThrowValueArgumentOutOfRange_NeedNonNegNumException()
+            {
+                throw new ArgumentOutOfRangeException(
+                    "Non-negative number required. (Parameter 'value')",
+                    "value");
+            }
+        }
+    }
+}
+#endif
diff --git a/src/PowerShellEditorServices/Nullable.cs b/src/PowerShellEditorServices/Nullable.cs
new file mode 100644
index 000000000..1481b7ba7
--- /dev/null
+++ b/src/PowerShellEditorServices/Nullable.cs
@@ -0,0 +1,139 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace System.Diagnostics.CodeAnalysis
+{
+    /// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
+    internal sealed class AllowNullAttribute : Attribute { }
+
+    /// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
+    internal sealed class DisallowNullAttribute : Attribute { }
+
+    /// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
+    internal sealed class MaybeNullAttribute : Attribute { }
+
+    /// <summary>Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns.</summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
+    internal sealed class NotNullAttribute : Attribute { }
+
+    /// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
+    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+    internal sealed class MaybeNullWhenAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the specified return value condition.</summary>
+        /// <param name="returnValue">
+        /// The return value condition. If the method returns this value, the associated parameter may be null.
+        /// </param>
+        public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
+
+        /// <summary>Gets the return value condition.</summary>
+        public bool ReturnValue { get; }
+    }
+
+    /// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
+    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+    internal sealed class NotNullWhenAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the specified return value condition.</summary>
+        /// <param name="returnValue">
+        /// The return value condition. If the method returns this value, the associated parameter will not be null.
+        /// </param>
+        public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
+
+        /// <summary>Gets the return value condition.</summary>
+        public bool ReturnValue { get; }
+    }
+
+    /// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
+    [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
+    internal sealed class NotNullIfNotNullAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the associated parameter name.</summary>
+        /// <param name="parameterName">
+        /// The associated parameter name.  The output will be non-null if the argument to the parameter specified is non-null.
+        /// </param>
+        public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
+
+        /// <summary>Gets the associated parameter name.</summary>
+        public string ParameterName { get; }
+    }
+
+    /// <summary>Applied to a method that will never return under any circumstance.</summary>
+    [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+    internal sealed class DoesNotReturnAttribute : Attribute { }
+
+    /// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
+    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+    internal sealed class DoesNotReturnIfAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the specified parameter value.</summary>
+        /// <param name="parameterValue">
+        /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
+        /// the associated parameter matches this value.
+        /// </param>
+        public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
+
+        /// <summary>Gets the condition parameter value.</summary>
+        public bool ParameterValue { get; }
+    }
+
+    /// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values.</summary>
+    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
+    internal sealed class MemberNotNullAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with a field or property member.</summary>
+        /// <param name="member">
+        /// The field or property member that is promised to be not-null.
+        /// </param>
+        public MemberNotNullAttribute(string member) => Members = new[] { member };
+
+        /// <summary>Initializes the attribute with the list of field and property members.</summary>
+        /// <param name="members">
+        /// The list of field and property members that are promised to be not-null.
+        /// </param>
+        public MemberNotNullAttribute(params string[] members) => Members = members;
+
+        /// <summary>Gets field or property member names.</summary>
+        public string[] Members { get; }
+    }
+
+    /// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition.</summary>
+    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
+    internal sealed class MemberNotNullWhenAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
+        /// <param name="returnValue">
+        /// The return value condition. If the method returns this value, the associated parameter will not be null.
+        /// </param>
+        /// <param name="member">
+        /// The field or property member that is promised to be not-null.
+        /// </param>
+        public MemberNotNullWhenAttribute(bool returnValue, string member)
+        {
+            ReturnValue = returnValue;
+            Members = new[] { member };
+        }
+
+        /// <summary>Initializes the attribute with the specified return value condition and list of field and property members.</summary>
+        /// <param name="returnValue">
+        /// The return value condition. If the method returns this value, the associated parameter will not be null.
+        /// </param>
+        /// <param name="members">
+        /// The list of field and property members that are promised to be not-null.
+        /// </param>
+        public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
+        {
+            ReturnValue = returnValue;
+            Members = members;
+        }
+
+        /// <summary>Gets the return value condition.</summary>
+        public bool ReturnValue { get; }
+
+        /// <summary>Gets field or property member names.</summary>
+        public string[] Members { get; }
+    }
+}
diff --git a/src/PowerShellEditorServices/Range.cs b/src/PowerShellEditorServices/Range.cs
new file mode 100644
index 000000000..ce0bcb2ac
--- /dev/null
+++ b/src/PowerShellEditorServices/Range.cs
@@ -0,0 +1,123 @@
+// <auto-generated>
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// (with alterations)
+#if NET5_0_OR_GREATER
+[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Range))]
+#else
+
+global using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
+
+#nullable enable
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace System
+{
+    /// <summary>Represent a range has start and end indexes.</summary>
+    /// <remarks>
+    /// Range is used by the C# compiler to support the range syntax.
+    /// <code>
+    /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 };
+    /// int[] subArray1 = someArray[0..2]; // { 1, 2 }
+    /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 }
+    /// </code>
+    /// </remarks>
+    internal readonly struct Range : IEquatable<Range>
+    {
+        /// <summary>Represent the inclusive start index of the Range.</summary>
+        public Index Start { get; }
+
+        /// <summary>Represent the exclusive end index of the Range.</summary>
+        public Index End { get; }
+
+        /// <summary>Construct a Range object using the start and end indexes.</summary>
+        /// <param name="start">Represent the inclusive start index of the range.</param>
+        /// <param name="end">Represent the exclusive end index of the range.</param>
+        public Range(Index start, Index end)
+        {
+            Start = start;
+            End = end;
+        }
+
+        /// <summary>Indicates whether the current Range object is equal to another object of the same type.</summary>
+        /// <param name="value">An object to compare with this object</param>
+        public override bool Equals([NotNullWhen(true)] object? value) =>
+            value is Range r &&
+            r.Start.Equals(Start) &&
+            r.End.Equals(End);
+
+        /// <summary>Indicates whether the current Range object is equal to another Range object.</summary>
+        /// <param name="other">An object to compare with this object</param>
+        public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End);
+
+        /// <summary>Returns the hash code for this instance.</summary>
+        public override int GetHashCode()
+        {
+            return (Start.GetHashCode(), End.GetHashCode()).GetHashCode();
+        }
+
+        /// <summary>Converts the value of the current Range object to its equivalent string representation.</summary>
+        public override string ToString()
+        {
+            return Start.ToString() + ".." + End.ToString();
+        }
+
+        /// <summary>Create a Range object starting from start index to the end of the collection.</summary>
+        public static Range StartAt(Index start) => new Range(start, Index.End);
+
+        /// <summary>Create a Range object starting from first element in the collection to the end Index.</summary>
+        public static Range EndAt(Index end) => new Range(Index.Start, end);
+
+        /// <summary>Create a Range object starting from first element to the end.</summary>
+        public static Range All => new Range(Index.Start, Index.End);
+
+        /// <summary>Calculate the start offset and length of range object using a collection length.</summary>
+        /// <param name="length">The length of the collection that the range will be used with. length has to be a positive value.</param>
+        /// <remarks>
+        /// For performance reason, we don't validate the input length parameter against negative values.
+        /// It is expected Range will be used with collections which always have non negative length/count.
+        /// We validate the range is inside the length scope though.
+        /// </remarks>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public (int Offset, int Length) GetOffsetAndLength(int length)
+        {
+            int start;
+            Index startIndex = Start;
+            if (startIndex.IsFromEnd)
+                start = length - startIndex.Value;
+            else
+                start = startIndex.Value;
+
+            int end;
+            Index endIndex = End;
+            if (endIndex.IsFromEnd)
+                end = length - endIndex.Value;
+            else
+                end = endIndex.Value;
+
+            if ((uint)end > (uint)length || (uint)start > (uint)end)
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
+            }
+
+            return (start, end - start);
+        }
+
+        private static class ExceptionArgument
+        {
+            public const string length = nameof(length);
+        }
+
+        private static class ThrowHelper
+        {
+            [DoesNotReturn, MethodImpl(MethodImplOptions.NoInlining)]
+            public static void ThrowArgumentOutOfRangeException(string parameterName)
+            {
+                throw new ArgumentOutOfRangeException(parameterName);
+            }
+        }
+    }
+}
+#endif
diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs
index 58d52bb3a..accbe9918 100644
--- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs
+++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs
@@ -86,6 +86,7 @@ public async Task StartAsync()
                         .AddLanguageProtocolLogging()
                         .SetMinimumLevel(_minimumLogLevel))
                     // TODO: Consider replacing all WithHandler with AddSingleton
+                    .WithHandler<ClientBreakpointHandler>()
                     .WithHandler<PsesWorkspaceSymbolsHandler>()
                     .WithHandler<PsesTextDocumentHandler>()
                     .WithHandler<GetVersionHandler>()
@@ -149,11 +150,15 @@ public async Task StartAsync()
                                 InitialWorkingDirectory = initializationOptions?.GetValue("initialWorkingDirectory")?.Value<string>()
                                     ?? workspaceService.WorkspaceFolders.FirstOrDefault()?.Uri.GetFileSystemPath()
                                     ?? Directory.GetCurrentDirectory(),
+                                SupportsBreakpointSync = initializationOptions?.GetValue("supportsBreakpointSync")?.Value<bool>()
+                                    ?? false,
                                 // If a shell integration script path is provided, that implies the feature is enabled.
                                 ShellIntegrationScript = initializationOptions?.GetValue("shellIntegrationScript")?.Value<string>()
                                     ?? "",
                             };
 
+                            languageServer.Services.GetService<BreakpointSyncService>().IsSupported = hostStartOptions.SupportsBreakpointSync;
+
                             workspaceService.InitialWorkingDirectory = hostStartOptions.InitialWorkingDirectory;
 
                             _psesHost = languageServer.Services.GetService<PsesInternalHost>();
diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs
index 82081c341..06d465719 100644
--- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs
+++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs
@@ -35,6 +35,9 @@ public static IServiceCollection AddPsesLanguageServices(
                     (provider) => provider.GetService<PsesInternalHost>().DebugContext)
                 .AddSingleton<EditorOperationsService>()
                 .AddSingleton<RemoteFileManagerService>()
+                .AddSingleton<BreakpointService>()
+                .AddSingleton<DebugStateService>()
+                .AddSingleton<BreakpointSyncService>()
                 .AddSingleton((provider) =>
                     {
                         ExtensionService extensionService = new(
@@ -59,18 +62,22 @@ public static IServiceCollection AddPsesDebugServices(
             PsesDebugServer psesDebugServer)
         {
             PsesInternalHost internalHost = languageServiceProvider.GetService<PsesInternalHost>();
+            DebugStateService debugStateService = languageServiceProvider.GetService<DebugStateService>();
+            BreakpointService breakpointService = languageServiceProvider.GetService<BreakpointService>();
+            BreakpointSyncService breakpointSyncService = languageServiceProvider.GetService<BreakpointSyncService>();
 
             return collection
                 .AddSingleton(internalHost)
                 .AddSingleton<IRunspaceContext>(internalHost)
                 .AddSingleton<IPowerShellDebugContext>(internalHost.DebugContext)
+                .AddSingleton(breakpointSyncService)
                 .AddSingleton(languageServiceProvider.GetService<IInternalPowerShellExecutionService>())
                 .AddSingleton(languageServiceProvider.GetService<WorkspaceService>())
                 .AddSingleton(languageServiceProvider.GetService<RemoteFileManagerService>())
                 .AddSingleton(psesDebugServer)
                 .AddSingleton<DebugService>()
-                .AddSingleton<BreakpointService>()
-                .AddSingleton<DebugStateService>()
+                .AddSingleton(breakpointService)
+                .AddSingleton(debugStateService)
                 .AddSingleton<DebugEventHandlerService>();
         }
     }
diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs
index 748e21c6d..ab483dfef 100644
--- a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs
+++ b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs
@@ -22,6 +22,7 @@ internal class BreakpointService
         private readonly IInternalPowerShellExecutionService _executionService;
         private readonly PsesInternalHost _editorServicesHost;
         private readonly DebugStateService _debugStateService;
+        private readonly BreakpointSyncService _breakpointSyncService;
 
         // TODO: This needs to be managed per nested session
         internal readonly Dictionary<string, HashSet<Breakpoint>> BreakpointsPerFile = new();
@@ -32,12 +33,14 @@ public BreakpointService(
             ILoggerFactory factory,
             IInternalPowerShellExecutionService executionService,
             PsesInternalHost editorServicesHost,
-            DebugStateService debugStateService)
+            DebugStateService debugStateService,
+            BreakpointSyncService breakpointSyncService)
         {
             _logger = factory.CreateLogger<BreakpointService>();
             _executionService = executionService;
             _editorServicesHost = editorServicesHost;
             _debugStateService = debugStateService;
+            _breakpointSyncService = breakpointSyncService;
         }
 
         public async Task<IReadOnlyList<Breakpoint>> GetBreakpointsAsync()
@@ -59,6 +62,23 @@ public async Task<IReadOnlyList<Breakpoint>> GetBreakpointsAsync()
 
         public async Task<IReadOnlyList<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IReadOnlyList<BreakpointDetails> breakpoints)
         {
+            if (_breakpointSyncService?.IsSupported is true)
+            {
+                // Since we're syncing breakpoints outside of debug configurations, if we can't find
+                // an existing breakpoint then mark it as unverified.
+                foreach (BreakpointDetails details in breakpoints)
+                {
+                    if (_breakpointSyncService.TryGetBreakpointByServerId(details.Id, out SyncedBreakpoint syncedBreakpoint))
+                    {
+                        continue;
+                    }
+
+                    details.Verified = false;
+                }
+
+                return breakpoints.ToArray();
+            }
+
             if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
             {
                 foreach (BreakpointDetails breakpointDetails in breakpoints)
@@ -227,6 +247,13 @@ public async Task<IReadOnlyList<CommandBreakpointDetails>> SetCommandBreakpoints
         /// </summary>
         public async Task RemoveAllBreakpointsAsync(string scriptPath = null)
         {
+            // Only need to remove all breakpoints if we're not able to sync outside of debug
+            // sessions.
+            if (_breakpointSyncService?.IsSupported is true)
+            {
+                return;
+            }
+
             try
             {
                 if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointSyncService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointSyncService.cs
new file mode 100644
index 000000000..67fc02834
--- /dev/null
+++ b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointSyncService.cs
@@ -0,0 +1,915 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Runspaces;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.PowerShell.EditorServices.Services;
+using Microsoft.PowerShell.EditorServices.Services.DebugAdapter;
+using Microsoft.PowerShell.EditorServices.Services.PowerShell;
+using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution;
+using Microsoft.PowerShell.EditorServices.Services.TextDocument;
+using Microsoft.PowerShell.EditorServices.Utility;
+using OmniSharp.Extensions.LanguageServer.Protocol;
+using OmniSharp.Extensions.LanguageServer.Protocol.Server;
+
+using Debugger = System.Management.Automation.Debugger;
+
+namespace Microsoft.PowerShell.EditorServices;
+
+internal record SyncedBreakpoint(
+    ClientBreakpoint Client,
+    Breakpoint Server);
+
+internal enum SyncedBreakpointKind
+{
+    Line,
+
+    Variable,
+
+    Command,
+}
+
+internal sealed class BreakpointSyncService
+{
+    private record BreakpointMap(
+        ConcurrentDictionary<string, SyncedBreakpoint> ByGuid,
+        ConcurrentDictionary<int, SyncedBreakpoint> ByPwshId)
+    {
+        public void Register(SyncedBreakpoint breakpoint)
+        {
+            ByGuid.AddOrUpdate(
+                breakpoint.Client.Id!,
+                breakpoint,
+                (_, _) => breakpoint);
+
+            ByPwshId.AddOrUpdate(
+                breakpoint.Server.Id,
+                breakpoint,
+                (_, _) => breakpoint);
+        }
+
+        public void Unregister(SyncedBreakpoint breakpoint)
+        {
+            ByGuid.TryRemove(breakpoint.Client.Id, out _);
+            ByPwshId.TryRemove(breakpoint.Server.Id, out _);
+        }
+    }
+
+    private record BreakpointTranslationInfo(
+        SyncedBreakpointKind Kind,
+        ScriptBlock? Action = null,
+        string? Name = null,
+        VariableAccessMode VariableMode = default,
+        string? FilePath = null,
+        int Line = 0,
+        int Column = 0);
+
+    internal record struct BreakpointHandle(SemaphoreSlim Target) : IDisposable
+    {
+        public void Dispose() => Target.Release();
+    }
+
+    private readonly BreakpointMap _map = new(new(), new());
+
+    private readonly ConditionalWeakTable<Runspace, BreakpointMap> _attachedRunspaceMap = new();
+
+    private readonly ConcurrentBag<SyncedBreakpoint> _pendingAdds = new();
+
+    private readonly ConcurrentBag<SyncedBreakpoint> _pendingRemovals = new();
+
+    private readonly SemaphoreSlim _breakpointMutateHandle = AsyncUtils.CreateSimpleLockingSemaphore();
+
+    private readonly IInternalPowerShellExecutionService _host;
+
+    private readonly ILogger<BreakpointSyncService> _logger;
+
+    private readonly DebugStateService _debugStateService;
+
+    private readonly ILanguageServerFacade _languageServer;
+
+    private bool? _isSupported;
+
+    public BreakpointSyncService(
+        IInternalPowerShellExecutionService host,
+        ILoggerFactory loggerFactory,
+        DebugStateService debugStateService,
+        ILanguageServerFacade languageServer)
+    {
+        _host = host;
+        _logger = loggerFactory.CreateLogger<BreakpointSyncService>();
+        _debugStateService = debugStateService;
+        _languageServer = languageServer;
+    }
+
+    public bool IsSupported
+    {
+        get => _isSupported ?? false;
+        set
+        {
+            Debug.Assert(_isSupported is null, "BreakpointSyncService.IsSupported should only be set once by initialize.");
+            _isSupported = value;
+        }
+    }
+
+    public bool IsMutatingBreakpoints
+    {
+        get
+        {
+            if (_breakpointMutateHandle.Wait(0))
+            {
+                _breakpointMutateHandle.Release();
+                return false;
+            }
+
+            return true;
+        }
+    }
+
+    private int? TargetRunspaceId => _debugStateService?.RunspaceId;
+
+    public async Task UpdatedByServerAsync(BreakpointUpdatedEventArgs eventArgs)
+    {
+        if (_map.ByPwshId.TryGetValue(eventArgs.Breakpoint.Id, out SyncedBreakpoint existing))
+        {
+            await ProcessExistingBreakpoint(eventArgs, existing).ConfigureAwait(false);
+            return;
+        }
+
+        // If we haven't told the client about the breakpoint yet then we can just ignore.
+        if (eventArgs.UpdateType is BreakpointUpdateType.Removed)
+        {
+            return;
+        }
+
+        SyncedBreakpoint newBreakpoint = CreateFromServerBreakpoint(eventArgs.Breakpoint);
+        string? id = await SendToClientAsync(newBreakpoint.Client, BreakpointUpdateType.Set)
+            .ConfigureAwait(false);
+
+        if (id is null)
+        {
+            LogBreakpointError(newBreakpoint, "Did not receive a breakpoint ID from the client.");
+        }
+
+        newBreakpoint.Client.Id = id;
+        RegisterBreakpoint(newBreakpoint);
+
+        async Task ProcessExistingBreakpoint(BreakpointUpdatedEventArgs eventArgs, SyncedBreakpoint existing)
+        {
+            if (eventArgs.UpdateType is BreakpointUpdateType.Removed)
+            {
+                UnregisterBreakpoint(existing);
+                await SendToClientAsync(existing.Client, BreakpointUpdateType.Removed).ConfigureAwait(false);
+                return;
+            }
+
+            if (eventArgs.UpdateType is BreakpointUpdateType.Enabled or BreakpointUpdateType.Disabled)
+            {
+                await SendToClientAsync(existing.Client, eventArgs.UpdateType).ConfigureAwait(false);
+                bool isActive = eventArgs.UpdateType is BreakpointUpdateType.Enabled;
+                SyncedBreakpoint newBreakpoint = existing with
+                {
+                    Client = existing.Client with
+                    {
+                        Enabled = isActive,
+                    },
+                };
+
+                UnregisterBreakpoint(existing);
+                RegisterBreakpoint(newBreakpoint);
+                return;
+            }
+
+            LogBreakpointError(
+                existing,
+                "Somehow we're syncing a new breakpoint that we've already sync'd. That's not supposed to happen.");
+        }
+    }
+
+    public IReadOnlyList<SyncedBreakpoint> GetSyncedBreakpoints() => _map.ByGuid.Values.ToArray();
+
+    public bool TryGetBreakpointByClientId(string id, [MaybeNullWhen(false)] out SyncedBreakpoint? syncedBreakpoint)
+        => _map.ByGuid.TryGetValue(id, out syncedBreakpoint);
+
+    public bool TryGetBreakpointByServerId(int id, [MaybeNullWhen(false)] out SyncedBreakpoint? syncedBreakpoint)
+        => _map.ByPwshId.TryGetValue(id, out syncedBreakpoint);
+
+    public void SyncServerAfterAttach()
+    {
+        if (!BreakpointApiUtils.SupportsBreakpointApis(_host.CurrentRunspace))
+        {
+            return;
+        }
+
+        bool ownsHandle = false;
+        try
+        {
+            ownsHandle = _breakpointMutateHandle.Wait(0);
+            Runspace runspace = _host.CurrentRunspace.Runspace;
+            Debugger debugger = runspace.Debugger;
+            foreach (Breakpoint existingBp in BreakpointApiUtils.GetBreakpointsDelegate(debugger, TargetRunspaceId))
+            {
+                BreakpointApiUtils.RemoveBreakpointDelegate(debugger, existingBp, TargetRunspaceId);
+            }
+
+            SyncedBreakpoint[] currentBreakpoints = _map.ByGuid.Values.ToArray();
+            BreakpointApiUtils.SetBreakpointsDelegate(
+                debugger,
+                currentBreakpoints.Select(sbp => sbp.Server),
+                TargetRunspaceId);
+
+            BreakpointMap map = GetMapForRunspace(runspace);
+            foreach (SyncedBreakpoint sbp in currentBreakpoints)
+            {
+                map.Register(sbp);
+            }
+        }
+        finally
+        {
+            if (ownsHandle)
+            {
+                _breakpointMutateHandle.Release();
+            }
+        }
+    }
+
+    public void SyncServerAfterRunspacePop()
+    {
+        if (!BreakpointApiUtils.SupportsBreakpointApis(_host.CurrentRunspace))
+        {
+            return;
+        }
+
+        bool ownsHandle = false;
+        try
+        {
+            ownsHandle = _breakpointMutateHandle.Wait(0);
+            Debugger debugger = _host.CurrentRunspace.Runspace.Debugger;
+            while (_pendingRemovals.TryTake(out SyncedBreakpoint sbp))
+            {
+                if (!_map.ByGuid.TryGetValue(sbp.Client.Id!, out SyncedBreakpoint existing))
+                {
+                    continue;
+                }
+
+                BreakpointApiUtils.RemoveBreakpointDelegate(debugger, existing.Server, null);
+                _map.Unregister(existing);
+            }
+
+            BreakpointApiUtils.SetBreakpointsDelegate(
+                debugger,
+                _pendingAdds.Select(sbp => sbp.Server),
+                null);
+
+            while (_pendingAdds.TryTake(out SyncedBreakpoint sbp))
+            {
+                _map.Register(sbp);
+            }
+        }
+        finally
+        {
+            if (ownsHandle)
+            {
+                _breakpointMutateHandle.Release();
+            }
+        }
+    }
+
+    public async Task UpdatedByClientAsync(
+        IReadOnlyList<ClientBreakpoint>? clientBreakpoints,
+        CancellationToken cancellationToken = default)
+    {
+        if (clientBreakpoints is null or { Count: 0 })
+        {
+            return;
+        }
+
+        using BreakpointHandle _ = await StartMutatingBreakpointsAsync(cancellationToken)
+            .ConfigureAwait(false);
+
+        List<(ClientBreakpoint Client, BreakpointTranslationInfo? Translation)>? toAdd = null;
+        List<SyncedBreakpoint>? toRemove = null;
+        List<(SyncedBreakpoint Breakpoint, bool Enabled)>? toSetActive = null;
+
+        foreach (ClientBreakpoint clientBreakpoint in clientBreakpoints)
+        {
+            if (!_map.ByGuid.TryGetValue(clientBreakpoint.Id, out SyncedBreakpoint existing))
+            {
+                (toAdd ??= new()).Add((clientBreakpoint, GetTranslationInfo(clientBreakpoint)));
+                continue;
+            }
+
+            if (clientBreakpoint == existing.Client)
+            {
+                continue;
+            }
+
+            // If the only thing that has changed is whether the breakpoint is enabled
+            // we can skip removing and re-adding.
+            if (clientBreakpoint.Enabled != existing.Client.Enabled
+                && clientBreakpoint with { Enabled = existing.Client.Enabled } != existing.Client)
+            {
+                (toSetActive ??= new()).Add((existing, clientBreakpoint.Enabled));
+                continue;
+            }
+
+            (toAdd ??= new()).Add((clientBreakpoint, GetTranslationInfo(clientBreakpoint)));
+            (toRemove ??= new()).Add(existing);
+        }
+
+        await ExecuteDelegateAsync(
+            "SyncUpdatedClientBreakpoints",
+            executionOptions: null,
+            (cancellationToken) =>
+            {
+                if (toRemove is not null and not { Count: 0 })
+                {
+                    foreach (SyncedBreakpoint bpToRemove in toRemove)
+                    {
+                        cancellationToken.ThrowIfCancellationRequested();
+                        UnsafeRemoveBreakpoint(
+                            bpToRemove,
+                            cancellationToken);
+                    }
+                }
+
+                if (toAdd is not null and not { Count: 0 })
+                {
+                    foreach ((ClientBreakpoint Client, BreakpointTranslationInfo? Translation) in toAdd)
+                    {
+                        cancellationToken.ThrowIfCancellationRequested();
+                        UnsafeCreateBreakpoint(
+                            Client,
+                            Translation,
+                            cancellationToken);
+                    }
+                }
+
+                if (toSetActive is not null and not { Count: 0 })
+                {
+                    foreach ((SyncedBreakpoint Breakpoint, bool Enabled) bpToSetActive in toSetActive)
+                    {
+                        UnsafeSetBreakpointActive(
+                            bpToSetActive.Enabled,
+                            bpToSetActive.Breakpoint.Server,
+                            cancellationToken);
+
+                        SyncedBreakpoint newBreakpoint = bpToSetActive.Breakpoint with
+                        {
+                            Client = bpToSetActive.Breakpoint.Client with
+                            {
+                                Enabled = bpToSetActive.Enabled,
+                            },
+                        };
+
+                        UnregisterBreakpoint(bpToSetActive.Breakpoint);
+                        RegisterBreakpoint(newBreakpoint);
+                    }
+                }
+            },
+            cancellationToken)
+            .ConfigureAwait(false);
+    }
+
+    public async Task RemovedFromClientAsync(
+        IReadOnlyList<ClientBreakpoint>? clientBreakpoints,
+        CancellationToken cancellationToken = default)
+    {
+        if (clientBreakpoints is null or { Count: 0 })
+        {
+            return;
+        }
+
+        using BreakpointHandle _ = await StartMutatingBreakpointsAsync(cancellationToken)
+            .ConfigureAwait(false);
+
+        List<SyncedBreakpoint> syncedBreakpoints = new(clientBreakpoints.Count);
+        foreach (ClientBreakpoint clientBreakpoint in clientBreakpoints)
+        {
+            // If we don't have a record of the breakpoint, we don't need to remove it.
+            if (_map.ByGuid.TryGetValue(clientBreakpoint.Id!, out SyncedBreakpoint? syncedBreakpoint))
+            {
+                syncedBreakpoints.Add(syncedBreakpoint!);
+            }
+        }
+
+        await ExecuteDelegateAsync(
+            "SyncRemovedClientBreakpoints",
+            executionOptions: null,
+            (cancellationToken) =>
+            {
+                foreach (SyncedBreakpoint syncedBreakpoint in syncedBreakpoints)
+                {
+                    cancellationToken.ThrowIfCancellationRequested();
+                    UnsafeRemoveBreakpoint(
+                        syncedBreakpoint,
+                        cancellationToken);
+                }
+            },
+            cancellationToken)
+            .ConfigureAwait(false);
+    }
+
+    public async Task FromClientAsync(
+        IReadOnlyList<ClientBreakpoint> clientBreakpoints,
+        CancellationToken cancellationToken = default)
+    {
+        using BreakpointHandle _ = await StartMutatingBreakpointsAsync(cancellationToken)
+            .ConfigureAwait(false);
+
+        List<(ClientBreakpoint Client, BreakpointTranslationInfo? Translation)> breakpointsToCreate = new(clientBreakpoints.Count);
+        foreach (ClientBreakpoint clientBreakpoint in clientBreakpoints)
+        {
+            breakpointsToCreate.Add((clientBreakpoint, GetTranslationInfo(clientBreakpoint)));
+        }
+
+        await ExecuteDelegateAsync(
+            "SyncNewClientBreakpoints",
+            executionOptions: null,
+            (cancellationToken) =>
+            {
+                foreach ((ClientBreakpoint Client, BreakpointTranslationInfo? Translation) in breakpointsToCreate)
+                {
+                    UnsafeCreateBreakpoint(
+                        Client,
+                        Translation,
+                        cancellationToken);
+                }
+            },
+            cancellationToken)
+            .ConfigureAwait(false);
+
+        return;
+    }
+
+    private void LogBreakpointError(SyncedBreakpoint breakpoint, string message)
+    {
+        // Switch to the new C# 11 string literal syntax once it makes it to stable.
+        _logger.LogError(
+            "{Message}\n" +
+            "    Server:\n" +
+            "        Id: {ServerId}\n" +
+            "        Enabled: {ServerEnabled}\n" +
+            "        Action: {Action}\n" +
+            "        Script: {Script}\n" +
+            "        Command: {Command}\n" +
+            "        Variable: {Variable}\n" +
+            "    Client:\n" +
+            "        Id: {ClientId}\n" +
+            "        Enabled: {ClientEnabled}\n" +
+            "        Condition: {Condition}\n" +
+            "        HitCondition: {HitCondition}\n" +
+            "        LogMessage: {LogMessage}\n" +
+            "        Location: {Location}\n" +
+            "        Function: {Function}\n",
+            message,
+            breakpoint.Server.Id,
+            breakpoint.Server.Enabled,
+            breakpoint.Server.Action,
+            breakpoint.Server.Script,
+            (breakpoint.Server as CommandBreakpoint)?.Command,
+            (breakpoint.Server as VariableBreakpoint)?.Variable,
+            breakpoint.Client.Id,
+            breakpoint.Client.Enabled,
+            breakpoint.Client.Condition,
+            breakpoint.Client.HitCondition,
+            breakpoint.Client.LogMessage,
+            breakpoint.Client.Location,
+            breakpoint.Client.FunctionName);
+    }
+
+    private async Task ExecuteDelegateAsync(
+            string representation,
+            ExecutionOptions? executionOptions,
+            Action<CancellationToken> action,
+            CancellationToken cancellationToken)
+    {
+        if (BreakpointApiUtils.SupportsBreakpointApis(_host.CurrentRunspace))
+        {
+            action(cancellationToken);
+            return;
+        }
+
+        await _host.ExecuteDelegateAsync(
+            representation,
+            executionOptions,
+            action,
+            cancellationToken)
+            .ConfigureAwait(false);
+    }
+
+    private static SyncedBreakpoint CreateFromServerBreakpoint(Breakpoint serverBreakpoint)
+    {
+        string? functionName = null;
+        ClientLocation? location = null;
+        if (serverBreakpoint is VariableBreakpoint vbp)
+        {
+            string mode = vbp.AccessMode switch
+            {
+                VariableAccessMode.Read => "R",
+                VariableAccessMode.Write => "W",
+                _ => "RW",
+            };
+
+            functionName = $"${vbp.Variable}!{mode}";
+        }
+        else if (serverBreakpoint is CommandBreakpoint cbp)
+        {
+            functionName = cbp.Command;
+        }
+        else if (serverBreakpoint is LineBreakpoint lbp)
+        {
+            location = new ClientLocation(
+                lbp.Script,
+                new Range(
+                    lbp.Line - 1,
+                    Math.Max(lbp.Column - 1, 0),
+                    lbp.Line - 1,
+                    Math.Max(lbp.Column - 1, 0)));
+        }
+
+        ClientBreakpoint clientBreakpoint = new(
+            serverBreakpoint.Enabled,
+            serverBreakpoint.Action?.ToString(),
+            null,
+            null,
+            location,
+            functionName);
+
+        SyncedBreakpoint syncedBreakpoint = new(clientBreakpoint, serverBreakpoint);
+        return syncedBreakpoint;
+    }
+
+    private async Task<string?> SendToClientAsync(
+        ClientBreakpoint clientBreakpoint,
+        BreakpointUpdateType updateType)
+    {
+        if (updateType is BreakpointUpdateType.Set)
+        {
+            if (_languageServer is null)
+            {
+                return null;
+            }
+
+            // We need to send new breakpoints across as a separate request so we can get the client
+            // ID back.
+            return await _languageServer.SendRequest(
+                "powerShell/setBreakpoint",
+                clientBreakpoint)
+                .Returning<string>(default)
+                .ConfigureAwait(false);
+        }
+
+        bool isUpdate = updateType is BreakpointUpdateType.Enabled or BreakpointUpdateType.Disabled;
+        bool isRemove = updateType is BreakpointUpdateType.Removed;
+        _languageServer?.SendNotification(
+            "powerShell/breakpointsChanged",
+            new ClientBreakpointsChangedEvents(
+                Array.Empty<ClientBreakpoint>(),
+                isRemove ? new[] { clientBreakpoint } : Array.Empty<ClientBreakpoint>(),
+                isUpdate ? new[] { clientBreakpoint } : Array.Empty<ClientBreakpoint>()));
+
+        return null;
+    }
+
+    private Breakpoint? UnsafeCreateBreakpoint(
+        BreakpointTranslationInfo translationInfo,
+        CancellationToken cancellationToken)
+    {
+        if (BreakpointApiUtils.SupportsBreakpointApis(_host.CurrentRunspace))
+        {
+            return translationInfo.Kind switch
+            {
+                SyncedBreakpointKind.Command => BreakpointApiUtils.SetCommandBreakpointDelegate(
+                    _host.CurrentRunspace.Runspace.Debugger,
+                    translationInfo.Name,
+                    translationInfo.Action,
+                    string.Empty,
+                    TargetRunspaceId),
+                SyncedBreakpointKind.Variable => BreakpointApiUtils.SetVariableBreakpointDelegate(
+                    _host.CurrentRunspace.Runspace.Debugger,
+                    translationInfo.Name,
+                    translationInfo.VariableMode,
+                    translationInfo.Action,
+                    string.Empty,
+                    TargetRunspaceId),
+                SyncedBreakpointKind.Line => BreakpointApiUtils.SetLineBreakpointDelegate(
+                    _host.CurrentRunspace.Runspace.Debugger,
+                    translationInfo.FilePath,
+                    translationInfo.Line,
+                    translationInfo.Column,
+                    translationInfo.Action,
+                    TargetRunspaceId),
+                _ => throw new ArgumentOutOfRangeException(nameof(translationInfo)),
+            };
+        }
+
+        PSCommand command = new PSCommand().AddCommand("Microsoft.PowerShell.Utility\\Set-PSBreakpoint");
+        _ = translationInfo.Kind switch
+        {
+            SyncedBreakpointKind.Command => command.AddParameter("Command", translationInfo.Name),
+            SyncedBreakpointKind.Variable => command
+                .AddParameter("Variable", translationInfo.Name)
+                .AddParameter("Mode", translationInfo.VariableMode),
+            SyncedBreakpointKind.Line => command
+                .AddParameter("Script", translationInfo.FilePath)
+                .AddParameter("Line", translationInfo.Line),
+            _ => throw new ArgumentOutOfRangeException(nameof(translationInfo)),
+        };
+
+        if (translationInfo is { Kind: SyncedBreakpointKind.Line, Column: > 0 })
+        {
+            command.AddParameter("Column", translationInfo.Column);
+        }
+
+        return _host.UnsafeInvokePSCommand<Breakpoint>(command, null, cancellationToken)
+            is IReadOnlyList<Breakpoint> bp and { Count: 1 }
+                ? bp[0]
+                : null;
+    }
+
+    private BreakpointTranslationInfo? GetTranslationInfo(ClientBreakpoint clientBreakpoint)
+    {
+        if (clientBreakpoint.Location?.Uri is DocumentUri uri
+            && !ScriptFile.IsUntitledPath(uri.ToString())
+            && !PathUtils.HasPowerShellScriptExtension(uri.GetFileSystemPath()))
+        {
+            return null;
+        }
+
+        ScriptBlock? actionScriptBlock = null;
+
+        // Check if this is a "conditional" line breakpoint.
+        if (!string.IsNullOrWhiteSpace(clientBreakpoint.Condition) ||
+            !string.IsNullOrWhiteSpace(clientBreakpoint.HitCondition) ||
+            !string.IsNullOrWhiteSpace(clientBreakpoint.LogMessage))
+        {
+            actionScriptBlock = BreakpointApiUtils.GetBreakpointActionScriptBlock(
+                clientBreakpoint.Condition,
+                clientBreakpoint.HitCondition,
+                clientBreakpoint.LogMessage,
+                out string errorMessage);
+
+            if (errorMessage is not null and not "")
+            {
+                _logger.LogError(
+                    "Unable to create breakpoint action ScriptBlock with error message: \"{Error}\"\n" +
+                    "    Condition: {Condition} \n" +
+                    "    HitCondition: {HitCondition}\n" +
+                    "    LogMessage: {LogMessage}\n",
+                    errorMessage,
+                    clientBreakpoint.Condition,
+                    clientBreakpoint.HitCondition,
+                    clientBreakpoint.LogMessage);
+                return null;
+            }
+        }
+
+        string? functionName = null;
+        string? variableName = null;
+        string? script = null;
+        int line = 0, column = 0;
+        VariableAccessMode mode = default;
+        SyncedBreakpointKind kind = default;
+
+        if (clientBreakpoint.FunctionName is not null)
+        {
+            ReadOnlySpan<char> functionSpan = clientBreakpoint.FunctionName.AsSpan();
+            if (functionSpan is { Length: > 1 } && functionSpan[0] is '$')
+            {
+                ReadOnlySpan<char> nameWithSuffix = functionSpan[1..];
+                mode = ParseSuffix(nameWithSuffix, out ReadOnlySpan<char> name);
+                variableName = name.ToString();
+                kind = SyncedBreakpointKind.Variable;
+            }
+            else
+            {
+                functionName = clientBreakpoint.FunctionName;
+                kind = SyncedBreakpointKind.Command;
+            }
+        }
+
+        if (clientBreakpoint.Location is not null)
+        {
+            kind = SyncedBreakpointKind.Line;
+            script = clientBreakpoint.Location.Uri.Scheme is "untitled"
+                ? clientBreakpoint.Location.Uri.ToString()
+                : clientBreakpoint.Location.Uri.GetFileSystemPath();
+            line = clientBreakpoint.Location.Range.Start.Line + 1;
+            column = clientBreakpoint.Location.Range.Start.Character is int c and not 0 ? c : 0;
+        }
+
+        return new BreakpointTranslationInfo(
+            kind,
+            actionScriptBlock,
+            kind is SyncedBreakpointKind.Variable ? variableName : functionName,
+            mode,
+            script,
+            line,
+            column);
+
+        static VariableAccessMode ParseSuffix(ReadOnlySpan<char> nameWithSuffix, out ReadOnlySpan<char> name)
+        {
+            // TODO: Simplify `if` logic when list patterns from C# 11 make it to stable.
+            //
+            // if (nameWithSuffix is [..ReadOnlySpan<char> rest, '!', 'R' or 'r', 'W' or 'w'])
+            if (nameWithSuffix is { Length: > 3 }
+                && nameWithSuffix[^1] is 'w' or 'W'
+                && nameWithSuffix[^2] is 'r' or 'R'
+                && nameWithSuffix[^3] is '!')
+            {
+                name = nameWithSuffix[0..^3];
+                return VariableAccessMode.ReadWrite;
+            }
+
+            // if (nameWithSuffix is [..ReadOnlySpan<char> rest1, '!', 'W' or 'w', 'R' or 'r'])
+            if (nameWithSuffix is { Length: > 3 }
+                && nameWithSuffix[^1] is 'r' or 'R'
+                && nameWithSuffix[^2] is 'w' or 'W'
+                && nameWithSuffix[^3] is '!')
+            {
+                name = nameWithSuffix[0..^3];
+                return VariableAccessMode.ReadWrite;
+            }
+
+            // if (nameWithSuffix is [..ReadOnlySpan<char> rest2, '!', 'R' or 'r'])
+            if (nameWithSuffix is { Length: > 2 }
+                && nameWithSuffix[^1] is 'r' or 'R'
+                && nameWithSuffix[^2] is '!')
+            {
+                name = nameWithSuffix[0..^2];
+                return VariableAccessMode.Read;
+            }
+
+            // if (nameWithSuffix is [..ReadOnlySpan<char> rest3, '!', 'W' or 'w'])
+            if (nameWithSuffix is { Length: > 2 }
+                && nameWithSuffix[^1] is 'w' or 'W'
+                && nameWithSuffix[^2] is '!')
+            {
+                name = nameWithSuffix[0..^2];
+                return VariableAccessMode.Write;
+            }
+
+            name = nameWithSuffix;
+            return VariableAccessMode.ReadWrite;
+        }
+    }
+
+    internal BreakpointHandle StartMutatingBreakpoints(CancellationToken cancellationToken = default)
+    {
+        _breakpointMutateHandle.Wait(cancellationToken);
+        return new BreakpointHandle(_breakpointMutateHandle);
+    }
+
+    private async Task<BreakpointHandle> StartMutatingBreakpointsAsync(CancellationToken cancellationToken = default)
+    {
+        await _breakpointMutateHandle.WaitAsync(cancellationToken).ConfigureAwait(false);
+        return new BreakpointHandle(_breakpointMutateHandle);
+    }
+
+    private void UnsafeRemoveBreakpoint(
+        SyncedBreakpoint breakpoint,
+        CancellationToken cancellationToken)
+    {
+        UnsafeRemoveBreakpoint(
+            breakpoint.Server,
+            cancellationToken);
+
+        UnregisterBreakpoint(breakpoint);
+    }
+
+    private void UnsafeRemoveBreakpoint(
+        Breakpoint breakpoint,
+        CancellationToken cancellationToken)
+    {
+        if (BreakpointApiUtils.SupportsBreakpointApis(_host.CurrentRunspace))
+        {
+            BreakpointApiUtils.RemoveBreakpointDelegate(
+                _host.CurrentRunspace.Runspace.Debugger,
+                breakpoint,
+                TargetRunspaceId);
+            return;
+        }
+
+        _host.UnsafeInvokePSCommand(
+            new PSCommand()
+                .AddCommand("Microsoft.PowerShell.Utility\\Remove-PSBreakpoint")
+                .AddParameter("Id", breakpoint.Id),
+            executionOptions: null,
+            cancellationToken);
+    }
+
+    private Breakpoint? UnsafeSetBreakpointActive(
+        bool enabled,
+        Breakpoint? breakpoint,
+        CancellationToken cancellationToken)
+    {
+        if (breakpoint is null)
+        {
+            return breakpoint;
+        }
+
+        if (BreakpointApiUtils.SupportsBreakpointApis(_host.CurrentRunspace))
+        {
+            if (enabled)
+            {
+                return BreakpointApiUtils.EnableBreakpointDelegate(
+                    _host.CurrentRunspace.Runspace.Debugger,
+                    breakpoint,
+                    TargetRunspaceId);
+            }
+
+            return BreakpointApiUtils.DisableBreakpointDelegate(
+                _host.CurrentRunspace.Runspace.Debugger,
+                breakpoint,
+                TargetRunspaceId);
+        }
+
+        string commandToInvoke = enabled
+            ? "Microsoft.PowerShell.Utility\\Enable-PSBreakpoint"
+            : "Microsoft.PowerShell.Utility\\Disable-PSBreakpoint";
+
+        _host.UnsafeInvokePSCommand(
+            new PSCommand()
+                .AddCommand(commandToInvoke)
+                .AddParameter("Id", breakpoint.Id),
+            executionOptions: null,
+            cancellationToken);
+
+        return breakpoint;
+    }
+
+    private SyncedBreakpoint? UnsafeCreateBreakpoint(
+        ClientBreakpoint client,
+        BreakpointTranslationInfo? translation,
+        CancellationToken cancellationToken)
+    {
+        if (translation is null or { Kind: SyncedBreakpointKind.Command, Name: null or "" })
+        {
+            return null;
+        }
+
+        Breakpoint? pwshBreakpoint = UnsafeCreateBreakpoint(
+            translation,
+            cancellationToken);
+
+        if (!client.Enabled)
+        {
+            pwshBreakpoint = UnsafeSetBreakpointActive(
+                false,
+                pwshBreakpoint,
+                cancellationToken);
+        }
+
+        if (pwshBreakpoint is null)
+        {
+            return null;
+        }
+
+        SyncedBreakpoint syncedBreakpoint = new(client, pwshBreakpoint);
+        RegisterBreakpoint(syncedBreakpoint);
+        return syncedBreakpoint;
+    }
+
+    private void RegisterBreakpoint(SyncedBreakpoint breakpoint)
+    {
+        BreakpointMap map = _map;
+        if (_host.IsRunspacePushed)
+        {
+            map = GetMapForRunspace(_host.CurrentRunspace.Runspace);
+            _pendingAdds.Add(breakpoint);
+        }
+
+        map.Register(breakpoint);
+    }
+
+    private BreakpointMap GetMapForRunspace(Runspace runspace)
+        => _attachedRunspaceMap.GetValue(
+                runspace,
+                _ => new BreakpointMap(
+                    new ConcurrentDictionary<string, SyncedBreakpoint>(),
+                    new ConcurrentDictionary<int, SyncedBreakpoint>()));
+
+    private void UnregisterBreakpoint(SyncedBreakpoint breakpoint)
+    {
+        BreakpointMap map = _map;
+        if (_host.IsRunspacePushed)
+        {
+            map = GetMapForRunspace(_host.CurrentRunspace.Runspace);
+            _pendingRemovals.Add(breakpoint);
+        }
+
+        map.Unregister(breakpoint);
+    }
+}
diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs
index 9736b3e85..706a9580b 100644
--- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs
+++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs
@@ -42,5 +42,20 @@ internal class DebugStateService
         internal int ReleaseSetBreakpointHandle() => _setBreakpointInProgressHandle.Release();
 
         internal Task WaitForSetBreakpointHandleAsync() => _setBreakpointInProgressHandle.WaitAsync();
+
+        internal void Reset()
+        {
+            NoDebug = false;
+            Arguments = null;
+            IsRemoteAttach = false;
+            RunspaceId = null;
+            IsAttachSession = false;
+            WaitingForAttach = false;
+            ScriptToLaunch = null;
+            ExecutionCompleted = false;
+            IsInteractiveDebugSession = false;
+            IsUsingTempIntegratedConsole = false;
+            ServerStarted = null;
+        }
     }
 }
diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointApiUtils.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointApiUtils.cs
index 7884fbda5..d95a9ddf2 100644
--- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointApiUtils.cs
+++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointApiUtils.cs
@@ -22,10 +22,18 @@ internal static class BreakpointApiUtils
 
         private static readonly Lazy<Func<Debugger, string, ScriptBlock, string, int?, CommandBreakpoint>> s_setCommandBreakpointLazy;
 
+        private static readonly Lazy<Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint>> s_setVariableBreakpointLazy;
+
         private static readonly Lazy<Func<Debugger, int?, List<Breakpoint>>> s_getBreakpointsLazy;
 
         private static readonly Lazy<Func<Debugger, Breakpoint, int?, bool>> s_removeBreakpointLazy;
 
+        private static readonly Lazy<Func<Debugger, Breakpoint, int?, Breakpoint>> s_disableBreakpointLazy;
+
+        private static readonly Lazy<Func<Debugger, Breakpoint, int?, Breakpoint>> s_enableBreakpointLazy;
+
+        private static readonly Lazy<Action<Debugger, IEnumerable<Breakpoint>, int?>> s_setBreakpointsLazy;
+
         private static readonly Version s_minimumBreakpointApiPowerShellVersion = new(7, 0, 0, 0);
 
         private static int breakpointHitCounter;
@@ -65,6 +73,17 @@ static BreakpointApiUtils()
                     setCommandBreakpointMethod);
             });
 
+            s_setVariableBreakpointLazy = new Lazy<Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint>>(() =>
+            {
+                Type[] setVariableBreakpointParameters = new[] { typeof(string), typeof(VariableAccessMode), typeof(ScriptBlock), typeof(string), typeof(int?) };
+                MethodInfo setVariableBreakpointMethod = typeof(Debugger).GetMethod("SetVariableBreakpoint", setVariableBreakpointParameters);
+
+                return (Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint>)Delegate.CreateDelegate(
+                    typeof(Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint>),
+                    firstArgument: null,
+                    setVariableBreakpointMethod);
+            });
+
             s_getBreakpointsLazy = new Lazy<Func<Debugger, int?, List<Breakpoint>>>(() =>
             {
                 Type[] getBreakpointsParameters = new[] { typeof(int?) };
@@ -86,19 +105,60 @@ static BreakpointApiUtils()
                     firstArgument: null,
                     removeBreakpointMethod);
             });
+
+            s_disableBreakpointLazy = new Lazy<Func<Debugger, Breakpoint, int?, Breakpoint>>(() =>
+            {
+                Type[] disableBreakpointParameters = new[] { typeof(Breakpoint), typeof(int?) };
+                MethodInfo disableBreakpointMethod = typeof(Debugger).GetMethod("DisableBreakpoint", disableBreakpointParameters);
+
+                return (Func<Debugger, Breakpoint, int?, Breakpoint>)Delegate.CreateDelegate(
+                    typeof(Func<Debugger, Breakpoint, int?, Breakpoint>),
+                    firstArgument: null,
+                    disableBreakpointMethod);
+            });
+
+            s_enableBreakpointLazy = new Lazy<Func<Debugger, Breakpoint, int?, Breakpoint>>(() =>
+            {
+                Type[] enableBreakpointParameters = new[] { typeof(Breakpoint), typeof(int?) };
+                MethodInfo enableBreakpointMethod = typeof(Debugger).GetMethod("EnableBreakpoint", enableBreakpointParameters);
+
+                return (Func<Debugger, Breakpoint, int?, Breakpoint>)Delegate.CreateDelegate(
+                    typeof(Func<Debugger, Breakpoint, int?, Breakpoint>),
+                    firstArgument: null,
+                    enableBreakpointMethod);
+            });
+
+            s_setBreakpointsLazy = new Lazy<Action<Debugger, IEnumerable<Breakpoint>, int?>>(() =>
+            {
+                Type[] setBreakpointsParameters = new[] { typeof(IEnumerable<Breakpoint>), typeof(int?) };
+                MethodInfo setBreakpointsMethod = typeof(Debugger).GetMethod("SetBreakpoints", setBreakpointsParameters);
+
+                return (Action<Debugger, IEnumerable<Breakpoint>, int?>)Delegate.CreateDelegate(
+                    typeof(Action<Debugger, IEnumerable<Breakpoint>, int?>),
+                    firstArgument: null,
+                    setBreakpointsMethod);
+            });
         }
 
         #endregion
 
         #region Private Static Properties
 
-        private static Func<Debugger, string, int, int, ScriptBlock, int?, LineBreakpoint> SetLineBreakpointDelegate => s_setLineBreakpointLazy.Value;
+        internal static Func<Debugger, string, int, int, ScriptBlock, int?, LineBreakpoint> SetLineBreakpointDelegate => s_setLineBreakpointLazy.Value;
+
+        internal static Func<Debugger, string, ScriptBlock, string, int?, CommandBreakpoint> SetCommandBreakpointDelegate => s_setCommandBreakpointLazy.Value;
+
+        internal static Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint> SetVariableBreakpointDelegate => s_setVariableBreakpointLazy.Value;
+
+        internal static Func<Debugger, int?, List<Breakpoint>> GetBreakpointsDelegate => s_getBreakpointsLazy.Value;
+
+        internal static Func<Debugger, Breakpoint, int?, bool> RemoveBreakpointDelegate => s_removeBreakpointLazy.Value;
 
-        private static Func<Debugger, string, ScriptBlock, string, int?, CommandBreakpoint> SetCommandBreakpointDelegate => s_setCommandBreakpointLazy.Value;
+        internal static Func<Debugger, Breakpoint, int?, Breakpoint> DisableBreakpointDelegate => s_disableBreakpointLazy.Value;
 
-        private static Func<Debugger, int?, List<Breakpoint>> GetBreakpointsDelegate => s_getBreakpointsLazy.Value;
+        internal static Func<Debugger, Breakpoint, int?, Breakpoint> EnableBreakpointDelegate => s_enableBreakpointLazy.Value;
 
-        private static Func<Debugger, Breakpoint, int?, bool> RemoveBreakpointDelegate => s_removeBreakpointLazy.Value;
+        internal static Action<Debugger, IEnumerable<Breakpoint>, int?> SetBreakpointsDelegate => s_setBreakpointsLazy.Value;
 
         #endregion
 
diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs
index 4c99ff747..454102e2e 100644
--- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs
+++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs
@@ -32,23 +32,46 @@ internal class BreakpointHandlers : ISetFunctionBreakpointsHandler, ISetBreakpoi
         private readonly DebugStateService _debugStateService;
         private readonly WorkspaceService _workspaceService;
         private readonly IRunspaceContext _runspaceContext;
+        private readonly BreakpointSyncService _breakpointSyncService;
 
         public BreakpointHandlers(
             ILoggerFactory loggerFactory,
             DebugService debugService,
             DebugStateService debugStateService,
             WorkspaceService workspaceService,
-            IRunspaceContext runspaceContext)
+            IRunspaceContext runspaceContext,
+            BreakpointSyncService breakpointSyncService)
         {
             _logger = loggerFactory.CreateLogger<BreakpointHandlers>();
             _debugService = debugService;
             _debugStateService = debugStateService;
             _workspaceService = workspaceService;
             _runspaceContext = runspaceContext;
+            _breakpointSyncService = breakpointSyncService;
         }
 
         public async Task<SetBreakpointsResponse> Handle(SetBreakpointsArguments request, CancellationToken cancellationToken)
         {
+            if (_breakpointSyncService.IsSupported)
+            {
+                // TODO: Find some way to actually verify these. Unfortunately the sync event comes
+                // through *after* the `SetBreakpoints` event so we can't just look at what synced
+                // breakpoints were successfully set.
+                return new SetBreakpointsResponse()
+                {
+                    Breakpoints = new Container<Breakpoint>(
+                        request.Breakpoints
+                            .Select(sbp => new Breakpoint()
+                            {
+                                Line = sbp.Line,
+                                Column = sbp.Column,
+                                Verified = true,
+                                Source = request.Source,
+                                Id = 0,
+                            })),
+                };
+            }
+
             if (!_workspaceService.TryGetFile(request.Source.Path, out ScriptFile scriptFile))
             {
                 string message = _debugStateService.NoDebug ? string.Empty : "Source file could not be accessed, breakpoint not set.";
@@ -125,6 +148,21 @@ await _debugService.SetLineBreakpointsAsync(
 
         public async Task<SetFunctionBreakpointsResponse> Handle(SetFunctionBreakpointsArguments request, CancellationToken cancellationToken)
         {
+            if (_breakpointSyncService.IsSupported)
+            {
+                return new SetFunctionBreakpointsResponse()
+                {
+                    Breakpoints = new Container<Breakpoint>(
+                        _breakpointSyncService.GetSyncedBreakpoints()
+                            .Where(sbp => sbp.Client.FunctionName is not null and not "")
+                            .Select(sbp => new Breakpoint()
+                            {
+                                Verified = true,
+                                Message = sbp.Client.FunctionName,
+                            })),
+                };
+            }
+
             IReadOnlyList<CommandBreakpointDetails> breakpointDetails = request.Breakpoints
                 .Select((funcBreakpoint) => CommandBreakpointDetails.Create(
                     funcBreakpoint.Name,
diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ClientBreakpointHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ClientBreakpointHandler.cs
new file mode 100644
index 000000000..1d70db41b
--- /dev/null
+++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ClientBreakpointHandler.cs
@@ -0,0 +1,82 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediatR;
+using Microsoft.Extensions.Logging;
+using OmniSharp.Extensions.JsonRpc;
+using OmniSharp.Extensions.LanguageServer.Protocol;
+using OmniSharp.Extensions.LanguageServer.Protocol.Models;
+using OmniSharp.Extensions.LanguageServer.Protocol.Serialization;
+
+namespace Microsoft.PowerShell.EditorServices;
+
+internal record ClientLocation(
+    DocumentUri Uri,
+    Range Range);
+
+internal record ClientBreakpoint(
+    bool Enabled,
+    [property: Optional] string? Condition = null,
+    [property: Optional] string? HitCondition = null,
+    [property: Optional] string? LogMessage = null,
+    [property: Optional] ClientLocation? Location = null,
+    [property: Optional] string? FunctionName = null)
+{
+    // The ID needs to come from the client, so if the breakpoint is originating on the server
+    // then we need to create an ID-less breakpoint to send over to the client.
+    public string? Id { get; set; }
+}
+
+internal record ClientBreakpointsChangedEvents(
+    Container<ClientBreakpoint> Added,
+    Container<ClientBreakpoint> Removed,
+    Container<ClientBreakpoint> Changed)
+    : IRequest, IBaseRequest;
+
+[Serial, Method("powerShell/breakpointsChanged", Direction.Bidirectional)]
+internal interface IClientBreakpointHandler : IJsonRpcNotificationHandler<ClientBreakpointsChangedEvents>
+{
+}
+
+internal sealed class ClientBreakpointHandler : IClientBreakpointHandler
+{
+    private readonly ILogger _logger;
+
+    private readonly BreakpointSyncService _breakpoints;
+
+    public ClientBreakpointHandler(
+        ILoggerFactory loggerFactory,
+        BreakpointSyncService breakpointSyncService)
+    {
+        _logger = loggerFactory.CreateLogger<ClientBreakpointHandler>();
+        _breakpoints = breakpointSyncService;
+    }
+
+    public async Task<Unit> Handle(
+        ClientBreakpointsChangedEvents request,
+        CancellationToken cancellationToken)
+    {
+        _logger.LogInformation("Starting breakpoint sync initiated by client.");
+        await _breakpoints.FromClientAsync(
+            request.Added.ToArray(),
+            cancellationToken)
+            .ConfigureAwait(false);
+
+        await _breakpoints.UpdatedByClientAsync(
+            request.Changed.ToArray(),
+            cancellationToken)
+            .ConfigureAwait(false);
+
+        await _breakpoints.RemovedFromClientAsync(
+            request.Removed.ToArray(),
+            cancellationToken)
+            .ConfigureAwait(false);
+
+        return new();
+    }
+}
diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs
index 97bd6cca7..29b9534f6 100644
--- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs
+++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs
@@ -96,6 +96,7 @@ internal class LaunchAndAttachHandler : ILaunchHandler<PsesLaunchRequestArgument
         private static readonly Version s_minVersionForCustomPipeName = new(6, 2);
         private readonly ILogger<LaunchAndAttachHandler> _logger;
         private readonly BreakpointService _breakpointService;
+        private readonly BreakpointSyncService _breakpointSyncService;
         private readonly DebugService _debugService;
         private readonly IRunspaceContext _runspaceContext;
         private readonly IInternalPowerShellExecutionService _executionService;
@@ -108,6 +109,7 @@ public LaunchAndAttachHandler(
             ILoggerFactory factory,
             IDebugAdapterServerFacade debugAdapterServer,
             BreakpointService breakpointService,
+            BreakpointSyncService breakpointSyncService,
             DebugEventHandlerService debugEventHandlerService,
             DebugService debugService,
             IRunspaceContext runspaceContext,
@@ -118,6 +120,7 @@ public LaunchAndAttachHandler(
             _logger = factory.CreateLogger<LaunchAndAttachHandler>();
             _debugAdapterServer = debugAdapterServer;
             _breakpointService = breakpointService;
+            _breakpointSyncService = breakpointSyncService;
             _debugEventHandlerService = debugEventHandlerService;
             _debugService = debugService;
             _runspaceContext = runspaceContext;
@@ -336,7 +339,15 @@ void RunspaceChangedHandler(object s, RunspaceChangedEventArgs _)
             debugRunspaceCmd.AddParameter("Id", request.RunspaceId);
 
             // Clear any existing breakpoints before proceeding
-            await _breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(continueOnCapturedContext: false);
+            if (_breakpointSyncService.IsSupported)
+            {
+                _breakpointSyncService.SyncServerAfterAttach();
+            }
+            else
+            {
+                await _breakpointService.RemoveAllBreakpointsAsync()
+                    .ConfigureAwait(continueOnCapturedContext: false);
+            }
 
             _debugStateService.WaitingForAttach = true;
             Task nonAwaitedTask = _executionService
@@ -499,6 +510,7 @@ await _executionService.ExecutePSCommandAsync(
 
             _debugService.IsClientAttached = false;
             _debugAdapterServer.SendNotification(EventNames.Terminated);
+            _debugStateService.Reset();
         }
     }
 }
diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs
index 7de16fddf..8fbe06914 100644
--- a/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs
+++ b/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs
@@ -9,6 +9,8 @@ internal struct HostStartOptions
 
         public string InitialWorkingDirectory { get; set; }
 
+        public bool SupportsBreakpointSync { get; set; }
+
         public string ShellIntegrationScript { get; set; }
-}
+    }
 }
diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs
index a2072b2d9..edcc85361 100644
--- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs
+++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs
@@ -9,6 +9,7 @@
 using System.Reflection;
 using System.Threading;
 using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Microsoft.PowerShell.EditorServices.Hosting;
 using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console;
@@ -112,6 +113,8 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns
 
         private bool _resettingRunspace;
 
+        private BreakpointSyncService _breakpointSyncService;
+
         static PsesInternalHost()
         {
             Type scriptDebuggerType = typeof(PSObject).Assembly
@@ -234,6 +237,9 @@ public PsesInternalHost(
 
         private bool ShouldExitExecutionLoop => _shouldExit || _shuttingDown != 0;
 
+        private BreakpointSyncService BreakpointSync
+            => _breakpointSyncService ??= _languageServer?.GetService<BreakpointSyncService>();
+
         public override void EnterNestedPrompt() => PushPowerShellAndRunLoop(
             CreateNestedPowerShell(CurrentRunspace),
             PowerShellFrameType.Nested | PowerShellFrameType.Repl);
@@ -538,6 +544,18 @@ public IReadOnlyList<TResult> InvokePSCommand<TResult>(PSCommand psCommand, Powe
             return task.ExecuteAndGetResult(cancellationToken);
         }
 
+        void IInternalPowerShellExecutionService.UnsafeInvokePSCommand(
+            PSCommand psCommand,
+            PowerShellExecutionOptions executionOptions,
+            CancellationToken cancellationToken)
+            => InvokePSCommand<PSObject>(psCommand, executionOptions, cancellationToken);
+
+        IReadOnlyList<TResult> IInternalPowerShellExecutionService.UnsafeInvokePSCommand<TResult>(
+            PSCommand psCommand,
+            PowerShellExecutionOptions executionOptions,
+            CancellationToken cancellationToken)
+            => InvokePSCommand<TResult>(psCommand, executionOptions, cancellationToken);
+
         public void InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) => InvokePSCommand<PSObject>(psCommand, executionOptions, cancellationToken);
 
         public TResult InvokePSDelegate<TResult>(string representation, ExecutionOptions executionOptions, Func<PowerShell, CancellationToken, TResult> func, CancellationToken cancellationToken)
@@ -784,6 +802,11 @@ private void PopPowerShell(RunspaceChangeAction runspaceChangeAction = RunspaceC
                                 previousRunspaceFrame.RunspaceInfo,
                                 newRunspaceFrame.RunspaceInfo));
                     }
+
+                    if (BreakpointSync?.IsSupported is true)
+                    {
+                        BreakpointSync.SyncServerAfterRunspacePop();
+                    }
                 }
             });
         }
@@ -1449,7 +1472,23 @@ void OnDebuggerStoppedImpl(object sender, DebuggerStopEventArgs debuggerStopEven
             }
         }
 
-        private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) => DebugContext.HandleBreakpointUpdated(breakpointUpdatedEventArgs);
+        private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs)
+        {
+            if (BreakpointSync is not BreakpointSyncService breakpointSyncService)
+            {
+                DebugContext.HandleBreakpointUpdated(breakpointUpdatedEventArgs);
+                return;
+            }
+
+            if (!breakpointSyncService.IsSupported || breakpointSyncService.IsMutatingBreakpoints)
+            {
+                DebugContext.HandleBreakpointUpdated(breakpointUpdatedEventArgs);
+                return;
+            }
+
+            _ = Task.Run(() => breakpointSyncService.UpdatedByServerAsync(breakpointUpdatedEventArgs));
+            DebugContext.HandleBreakpointUpdated(breakpointUpdatedEventArgs);
+        }
 
         private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs)
         {
diff --git a/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs
index 31a75728f..9bd09d736 100644
--- a/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs
+++ b/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs
@@ -51,8 +51,22 @@ Task ExecutePSCommandAsync(
         void CancelCurrentTask();
     }
 
-    internal interface IInternalPowerShellExecutionService : IPowerShellExecutionService
+    internal interface IInternalPowerShellExecutionService : IPowerShellExecutionService, IRunspaceContext
     {
         event Action<object, RunspaceChangedEventArgs> RunspaceChanged;
+
+        /// <summary>
+        /// Create and execute a <see cref="SynchronousPowerShellTask{TResult}" /> without queuing
+        /// the work for the pipeline thread. This method must only be invoked when the caller
+        /// has ensured that they are already running on the pipeline thread.
+        /// </summary>
+        void UnsafeInvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Create and execute a <see cref="SynchronousPowerShellTask{TResult}" /> without queuing
+        /// the work for the pipeline thread. This method must only be invoked when the caller
+        /// has ensured that they are already running on the pipeline thread.
+        /// </summary>
+        IReadOnlyList<TResult> UnsafeInvokePSCommand<TResult>(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken);
     }
 }
diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs
index c9232d7d5..9c5bf88d0 100644
--- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs
+++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs
@@ -6,5 +6,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace
     internal interface IRunspaceContext
     {
         IRunspaceInfo CurrentRunspace { get; }
+
+        bool IsRunspacePushed { get; }
     }
 }
diff --git a/src/PowerShellEditorServices/Utility/PathUtils.cs b/src/PowerShellEditorServices/Utility/PathUtils.cs
index 112e8b465..f744f987a 100644
--- a/src/PowerShellEditorServices/Utility/PathUtils.cs
+++ b/src/PowerShellEditorServices/Utility/PathUtils.cs
@@ -62,5 +62,36 @@ internal static string WildcardEscapePath(string path, bool escapeSpaces = false
             }
             return wildcardEscapedPath;
         }
+
+        internal static bool HasPowerShellScriptExtension(string fileNameOrPath)
+        {
+            if (fileNameOrPath is null or "")
+            {
+                return false;
+            }
+
+            ReadOnlySpan<char> pathSeparators = stackalloc char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
+            ReadOnlySpan<char> asSpan = fileNameOrPath.AsSpan().TrimEnd(pathSeparators);
+            int separatorIndex = asSpan.LastIndexOfAny(pathSeparators);
+            if (separatorIndex is not -1)
+            {
+                asSpan = asSpan[(separatorIndex + 1)..];
+            }
+
+            int dotIndex = asSpan.LastIndexOf('.');
+            if (dotIndex is -1)
+            {
+                return false;
+            }
+
+            ReadOnlySpan<char> extension = asSpan[(dotIndex + 1)..];
+            if (extension.IsEmpty)
+            {
+                return false;
+            }
+
+            return extension.Equals("psm1".AsSpan(), StringComparison.OrdinalIgnoreCase)
+                || extension.Equals("ps1".AsSpan(), StringComparison.OrdinalIgnoreCase);
+        }
     }
 }
diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs
index 05386d43a..d2c9abb09 100644
--- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs
+++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs
@@ -53,11 +53,13 @@ public async Task InitializeAsync()
             psesHost.DebugContext.EnableDebugMode();
             psesHost._readLineProvider.ReadLine = testReadLine;
 
+            DebugStateService debugStateService = new();
             breakpointService = new BreakpointService(
                 NullLoggerFactory.Instance,
                 psesHost,
                 psesHost,
-                new DebugStateService());
+                debugStateService,
+                breakpointSyncService: null);
 
             debugService = new DebugService(
                 psesHost,