diff --git a/documentation/specs/msbuild-apphost.md b/documentation/specs/msbuild-apphost.md
new file mode 100644
index 00000000000..3dcea07ce2f
--- /dev/null
+++ b/documentation/specs/msbuild-apphost.md
@@ -0,0 +1,196 @@
+# MSBuild App Host Support
+
+## Purpose
+
+Enable MSBuild to be invoked directly as a native executable (`MSBuild.exe` / `MSBuild`) instead of through `dotnet MSBuild.dll`, providing:
+
+- Better process identification (processes show as "MSBuild" not "dotnet")
+- Win32 manifest embedding support (**COM interop**)
+- Consistency with Roslyn compilers (`csc`, `vbc`) which already use app hosts
+- Simplified invocation model
+
+### Important consideration
+The .NET SDK currently invokes MSBuild in two modes:
+
+| Mode | Current Behavior | After App Host |
+|------|------------------|----------------|
+| **In-proc** | SDK loads `MSBuild.dll` directly | No change |
+| **Out-of-proc** | SDK launches `dotnet exec MSBuild.dll` | SDK will launch `MSBuild.exe` |
+
+The AppHost introduction does not break SDK integration since we are not modifying the in-proc flow. The SDK will continue to load `MSBuild.dll` directly for in-proc scenarios.
+
+**SDK out-of-proc consideration**: The SDK can be configured to run MSBuild out-of-proc today via `DOTNET_CLI_RUN_MSBUILD_OUTOFPROC`, and this pattern will likely become more common as AOT work progresses for CLI commands that wrap MSBuild invocations. When the SDK does launch MSBuild out-of-proc, it will use the new app host (`MSBuild.exe`) when available.
+
+### Critical: COM Manifest for Out-of-Proc Host Objects
+
+A key driver for this work is enabling **registration-free COM** for out-of-proc task host objects. Currently, when running via `dotnet.exe`, we cannot embed the required manifest declarations - and even if we could, it would be the wrong level of abstraction for `dotnet.exe` to contain MSBuild-specific COM interface definitions.
+
+**Background**: Remote host objects (e.g., for accessing unsaved file changes from VS) must be registered in the [Running Object Table (ROT)](https://docs.microsoft.com/windows/desktop/api/objidl/nn-objidl-irunningobjecttable). The `ITaskHost` interface requires registration-free COM configuration in the MSBuild executable manifest.
+
+**Required manifest additions for `MSBuild.exe.manifest`:**
+
+```xml
+
+
+
+
+
+
+
+```
+
+**Related interfaces:**
+- `ITaskHost` - **must be configured via MSBuild's manifest** (registration-free)
+This is part of the work for [allowing out-of-proc tasks to access unsaved changes](https://github.com/dotnet/project-system/issues/4406).
+
+## Background
+
+An **app host** is a small native executable that:
+1. Finds the .NET runtime
+2. Loads the CLR
+3. Calls the managed entry point (e.g., `MSBuild.dll`)
+
+It is functionally equivalent to `dotnet.exe MSBuild.dll`, but as a standalone executable.
+
+**Note**: The app host does NOT include .NET CLI functionality. (e.g. `MSBuild.exe nuget add` wouldn't work — those are CLI features, not app host features).
+
+### Reference Implementation
+
+Roslyn added app host support in [PR #80026](https://github.com/dotnet/roslyn/pull/80026).
+
+## Changes Required
+
+### 1. MSBuild Repository
+
+**Remove `UseAppHost=false` from `src/MSBuild/MSBuild.csproj`:**
+
+```xml
+
+false
+```
+
+The SDK will then produce both `MSBuild.dll` and `MSBuild.exe` (Windows) / `MSBuild` (Unix).
+
+### 2. Installer Repository (dotnet/dotnet VMR)
+The app host creation happens in the installer/layout targets, similar to how Roslyn app hosts are created (PR https://github.com/dotnet/dotnet/pull/3180).
+
+### 3. Node Launching Logic
+
+Update node provider to launch `MSBuild.exe` instead of `dotnet MSBuild.dll`:
+The path resolution logic remains the same, since MSBuild.exe will be shipped in every SDK version.
+
+### 4. Backward Compatibility (Critical)
+
+Because VS supports older SDKs, node launching must handle both scenarios:
+
+```csharp
+var appHostPath = Path.Combine(sdkPath, $"MSBuild{RuntimeHostInfo.ExeExtension}");
+
+if (File.Exists(appHostPath))
+{
+ // New: Use app host directly
+ return (appHostPath, arguments);
+}
+else
+{
+ // Fallback: Use dotnet (older SDKs)
+ return (dotnetPath, $"\"{msbuildDllPath}\" {arguments}");
+}
+```
+
+**Handshake consideration**: The packet version can be bumped to negotiate between old/new node launching during handshake.
+MSBuild knows how to handle it starting from https://github.com/dotnet/msbuild/pull/12753
+
+## Runtime Discovery (the problem is solved in Roslyn app host this way)
+
+### The Problem
+
+App hosts find the runtime by checking (in order):
+1. `DOTNET_ROOT_X64` / `DOTNET_ROOT_X86` / `DOTNET_ROOT_ARM64`
+2. `DOTNET_ROOT`
+3. Well-known locations (`C:\Program Files\dotnet`, etc.)
+
+When running under the SDK, the runtime may be in a non-standard location. The SDK sets `DOTNET_HOST_PATH` to indicate which `dotnet` it's using.
+
+### Solution
+
+Before launching an app host process, set `DOTNET_ROOT` in the `ProcessStartInfo.Environment`:
+```csharp
+// Derive DOTNET_ROOT from DOTNET_HOST_PATH
+var dotnetHostPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH");
+
+if (string.IsNullOrEmpty(dotnetHostPath))
+{
+ // DOTNET_HOST_PATH should always be set when running under the SDK.
+ // If not set, fail fast rather than guessing - this indicates an unexpected environment.
+ throw new InvalidOperationException("DOTNET_HOST_PATH is not set. Cannot determine runtime location.");
+}
+
+var dotnetRoot = Path.GetDirectoryName(dotnetHostPath);
+
+var startInfo = new ProcessStartInfo(appHostPath, arguments);
+
+// Set DOTNET_ROOT for the app host to find the runtime
+startInfo.Environment["DOTNET_ROOT"] = dotnetRoot;
+
+// Clear architecture-specific overrides that would take precedence over DOTNET_ROOT
+startInfo.Environment.Remove("DOTNET_ROOT_X64");
+startInfo.Environment.Remove("DOTNET_ROOT_X86");
+startInfo.Environment.Remove("DOTNET_ROOT_ARM64");
+```
+
+**Note**: Using `ProcessStartInfo.Environment` is thread-safe and scoped to the child process only, avoiding any need for locking or save/restore patterns on the parent process environment.
+
+### DOTNET_ROOT Propagation to Child Processes
+
+**Concern**: When MSBuild sets `DOTNET_ROOT` to launch a worker node, that environment variable propagates to any tools the worker node executes. This could change tool behavior if the tool relies on `DOTNET_ROOT` to find its runtime.
+
+**Solution**: The worker node should explicitly clear `DOTNET_ROOT` (and architecture-specific variants) after startup, restoring the original entry-point environment:
+
+```csharp
+// In OutOfProcNode.HandleNodeConfiguration, after setting BuildProcessEnvironment:
+
+// Clear DOTNET_ROOT variants that were set only for app host bootstrap.
+// These should not leak to tools executed by this worker node.
+// Only clear if NOT present in the original build process environment.
+string[] dotnetRootVars = ["DOTNET_ROOT", "DOTNET_ROOT_X64", "DOTNET_ROOT_X86", "DOTNET_ROOT_ARM64"];
+foreach (string varName in dotnetRootVars)
+{
+ if (!_buildParameters.BuildProcessEnvironment.ContainsKey(varName))
+ {
+ Environment.SetEnvironmentVariable(varName, null);
+ }
+}
+```
+
+**Why this works**:
+
+1. `BuildProcessEnvironment` captures the environment from the **entry-point process** (e.g. VS).
+2. If the entry-point had `DOTNET_ROOT` set, the worker should also have it (passed via `BuildProcessEnvironment`).
+3. If the entry-point did NOT have `DOTNET_ROOT`, it was only added for app host bootstrap and should be cleared.
+
+**Alternative considered**: We could modify `NodeLauncher` to not inherit the parent environment and explicitly pass only `BuildProcessEnvironment` + `DOTNET_ROOT`. However, this is a larger change and may break other scenarios where environment inheritance is expected.
+
+**Implementation note**: Add a comment in the node-launching code explaining why `DOTNET_ROOT` is set and that the worker will clear it:
+
+```csharp
+// Set DOTNET_ROOT for app host bootstrap only.
+// The worker node will clear this after startup if it wasn't in the original BuildProcessEnvironment.
+// See OutOfProcNode.HandleNodeConfiguration.
+startInfo.Environment["DOTNET_ROOT"] = dotnetRoot;
+```
+
+### Edge Cases
+
+| Issue | Solution |
+|-------|----------|
+| `DOTNET_HOST_PATH` not set | Fail with clear error. This should always be set by the SDK; if missing, it indicates an unexpected/unsupported environment. |
+| Architecture-specific vars override `DOTNET_ROOT` | Clear `DOTNET_ROOT_X64`, `DOTNET_ROOT_X86`, `DOTNET_ROOT_ARM64` in `ProcessStartInfo.Environment` (see code above) |
+| App host doesn't exist | Fall back to `dotnet MSBuild.dll` and **log a message** indicating fallback (e.g., for debugging older SDK scenarios) |
\ No newline at end of file