diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 19be1a753..662cc31c5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -24,6 +24,18 @@ dotnet restore Nexo.LocalDevCore.slnf
dotnet build Nexo.LocalDevCore.slnf -v minimal
```
+## Solution filters, Makefile targets, and CI
+
+| Artifact | Typical use |
+| -------- | ----------- |
+| **`Nexo.sln`** | Full repository build — run locally after **`Nexo.Hosting`**, **`Nexo.Infrastructure`** Sdk surface, or registrar phase edits. |
+| **`Nexo.LocalDevCore.slnf`** | Faster slice (CLI + core tests); **`make restore-core`** / **`make build-core`**. |
+| **`Nexo.PrimeTime.slnf`** | Nine **`Nexo.Tests.*`** assemblies — **`make test-prime-time`** runs **`Category=ProdStyle`** across this filter; **`make test-prime-time-full`** runs the full test matrix after that gate. |
+
+**Cross-platform workflow:** `.github/workflows/cross-platform-tests.yml` triggers on changes under **`src/Nexo.Infrastructure/**`** (among other paths) and runs **`dotnet restore`** / **`dotnet build`** on **`Nexo.sln`** (implicit via repo root). **Prod-shaped Compose:** `.github/workflows/prod-dry-run-pr.yml` runs **`scripts/prod-dry-run.sh`** on PRs to **`master`**, **`main`**, and **`cursor/**`** branches.
+
+For Infrastructure Sdk / Hosting registration changes, prefer **`dotnet build Nexo.sln`** then **`make test-framework-prod-first`** or **`make test-prime-time`** when validating framework-wide behaviour (see also **`Makefile`** targets **`test-prod-style`**, **`ci-verify`**).
+
## Testing: xUnit vs. `UnitTestBase`
- **xUnit** suites (for example `Nexo.Tests.Infrastructure`) run with normal `dotnet test` filters.
diff --git a/Nexo.sln b/Nexo.sln
index 4f3f23a4f..959d530b8 100644
--- a/Nexo.sln
+++ b/Nexo.sln
@@ -65,6 +65,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexo.BackgroundAgents.HostR
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexo.Contracts", "src\Nexo.Contracts\Nexo.Contracts.csproj", "{F2A54F4C-34A8-49AC-A620-1B75EBA4424A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexo.Framework.Sdk", "src\Nexo.Framework.Sdk\Nexo.Framework.Sdk.csproj", "{5202644E-F8CC-47EA-92BA-22D18AC55C50}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexo.Runtime.Bundle", "src\Nexo.Runtime.Bundle\Nexo.Runtime.Bundle.csproj", "{DAF7CF6D-2BCB-411F-860D-2CF3EF396E70}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexo.Ingress.AwsSns", "src\Nexo.Ingress.AwsSns\Nexo.Ingress.AwsSns.csproj", "{7C104A42-14C9-4A59-9494-014D238DF354}"
@@ -445,6 +447,18 @@ Global
{F2A54F4C-34A8-49AC-A620-1B75EBA4424A}.Release|x64.Build.0 = Release|Any CPU
{F2A54F4C-34A8-49AC-A620-1B75EBA4424A}.Release|x86.ActiveCfg = Release|Any CPU
{F2A54F4C-34A8-49AC-A620-1B75EBA4424A}.Release|x86.Build.0 = Release|Any CPU
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50}.Debug|x64.Build.0 = Debug|Any CPU
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50}.Debug|x86.Build.0 = Debug|Any CPU
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50}.Release|x64.ActiveCfg = Release|Any CPU
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50}.Release|x64.Build.0 = Release|Any CPU
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50}.Release|x86.ActiveCfg = Release|Any CPU
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50}.Release|x86.Build.0 = Release|Any CPU
{DAF7CF6D-2BCB-411F-860D-2CF3EF396E70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DAF7CF6D-2BCB-411F-860D-2CF3EF396E70}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAF7CF6D-2BCB-411F-860D-2CF3EF396E70}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -539,6 +553,7 @@ Global
{9A6E02CA-56C8-4F7C-A1AE-6AB21D8F8AFB} = {9D4F8B1A-0B6E-4A3E-8A6A-0DE12C7C6E2F}
{C4A004BB-AFCC-4A69-8D36-61C2E9EEFD01} = {9D4F8B1A-0B6E-4A3E-8A6A-0DE12C7C6E2F}
{F2A54F4C-34A8-49AC-A620-1B75EBA4424A} = {9D4F8B1A-0B6E-4A3E-8A6A-0DE12C7C6E2F}
+ {5202644E-F8CC-47EA-92BA-22D18AC55C50} = {9D4F8B1A-0B6E-4A3E-8A6A-0DE12C7C6E2F}
{DAF7CF6D-2BCB-411F-860D-2CF3EF396E70} = {9D4F8B1A-0B6E-4A3E-8A6A-0DE12C7C6E2F}
{7C104A42-14C9-4A59-9494-014D238DF354} = {9D4F8B1A-0B6E-4A3E-8A6A-0DE12C7C6E2F}
{6FE29659-FF7F-4373-9B4F-637CE3D7386B} = {9D4F8B1A-0B6E-4A3E-8A6A-0DE12C7C6E2F}
diff --git a/application/src/Nexo.CLI/Nexo.CLI.csproj b/application/src/Nexo.CLI/Nexo.CLI.csproj
index e4f27efdd..593b5fde6 100644
--- a/application/src/Nexo.CLI/Nexo.CLI.csproj
+++ b/application/src/Nexo.CLI/Nexo.CLI.csproj
@@ -60,6 +60,10 @@
+
+
+
+
diff --git a/application/src/Nexo.Tests.CLI/Tests/Commands/ForgeRelatedCommandTests.cs b/application/src/Nexo.Tests.CLI/Tests/Commands/ForgeRelatedCommandTests.cs
index 776814cfc..797daefa5 100644
--- a/application/src/Nexo.Tests.CLI/Tests/Commands/ForgeRelatedCommandTests.cs
+++ b/application/src/Nexo.Tests.CLI/Tests/Commands/ForgeRelatedCommandTests.cs
@@ -13,24 +13,27 @@ namespace Nexo.Tests.CLI.Tests.Commands;
public sealed class ForgeRelatedCommandTests
{
[Fact(Timeout = 15000)]
- public void RuntimeStudioCommand_HasApplyTuneSubcommand()
+ public async Task RuntimeStudioCommand_HasApplyTuneSubcommand()
{
+ await Task.CompletedTask;
var cmd = new RuntimeStudioCommand();
var subcommands = cmd.Subcommands.Select(s => s.Name).ToList();
subcommands.Should().Contain("apply-tune");
}
[Fact(Timeout = 15000)]
- public void RuntimeStudioCommand_HasStatusSubcommand()
+ public async Task RuntimeStudioCommand_HasStatusSubcommand()
{
+ await Task.CompletedTask;
var cmd = new RuntimeStudioCommand();
var subcommands = cmd.Subcommands.Select(s => s.Name).ToList();
subcommands.Should().Contain("status");
}
[Fact(Timeout = 15000)]
- public void RuntimeStudioCommand_ApplyTune_HasExpectedOptions()
+ public async Task RuntimeStudioCommand_ApplyTune_HasExpectedOptions()
{
+ await Task.CompletedTask;
var cmd = new RuntimeStudioCommand();
var applyTune = cmd.Subcommands.Single(s => s.Name == "apply-tune");
var optionNames = applyTune.Options.Select(o => o.Name).ToList();
@@ -42,8 +45,9 @@ public void RuntimeStudioCommand_ApplyTune_HasExpectedOptions()
}
[Fact(Timeout = 15000)]
- public void RuntimeStudioCommand_Status_HasExpectedOptions()
+ public async Task RuntimeStudioCommand_Status_HasExpectedOptions()
{
+ await Task.CompletedTask;
var cmd = new RuntimeStudioCommand();
var status = cmd.Subcommands.Single(s => s.Name == "status");
var optionNames = status.Options.Select(o => o.Name).ToList();
@@ -53,8 +57,9 @@ public void RuntimeStudioCommand_Status_HasExpectedOptions()
}
[Fact(Timeout = 15000)]
- public void RuntimeStudioCommand_ParsesApplyTuneWithDryRun()
+ public async Task RuntimeStudioCommand_ParsesApplyTuneWithDryRun()
{
+ await Task.CompletedTask;
var cmd = new RuntimeStudioCommand();
var parseResult = cmd.Parse("apply-tune --dry-run --format-json");
@@ -62,8 +67,9 @@ public void RuntimeStudioCommand_ParsesApplyTuneWithDryRun()
}
[Fact(Timeout = 15000)]
- public void RuntimeStudioCommand_ParsesStatusWithJson()
+ public async Task RuntimeStudioCommand_ParsesStatusWithJson()
{
+ await Task.CompletedTask;
var cmd = new RuntimeStudioCommand();
var parseResult = cmd.Parse("status --format-json");
@@ -71,8 +77,9 @@ public void RuntimeStudioCommand_ParsesStatusWithJson()
}
[Fact(Timeout = 15000)]
- public void TuneApplier_Apply_RejectsFailedOptimizePayload()
+ public async Task TuneApplier_Apply_RejectsFailedOptimizePayload()
{
+ await Task.CompletedTask;
var tempDir = CreateTempDirectory();
try
{
@@ -96,8 +103,9 @@ public void TuneApplier_Apply_RejectsFailedOptimizePayload()
}
[Fact(Timeout = 15000)]
- public void TuneApplier_Apply_RejectsMissingModelProfileId()
+ public async Task TuneApplier_Apply_RejectsMissingModelProfileId()
{
+ await Task.CompletedTask;
var tempDir = CreateTempDirectory();
try
{
@@ -123,8 +131,9 @@ public void TuneApplier_Apply_RejectsMissingModelProfileId()
}
[Fact(Timeout = 15000)]
- public void TuneApplier_ResolveOllamaModelForLabAgent_ReturnsMixedProfileModel()
+ public async Task TuneApplier_ResolveOllamaModelForLabAgent_ReturnsMixedProfileModel()
{
+ await Task.CompletedTask;
var spec = WorkflowLabRuntimeSpec.Default();
var composition = spec.Compositions.First(c => c.Id == "hierarchy-squad");
var profile = spec.ModelProfiles.First(p => p.Id == "ollama-mixed");
@@ -139,8 +148,9 @@ public void TuneApplier_ResolveOllamaModelForLabAgent_ReturnsMixedProfileModel()
}
[Fact(Timeout = 15000)]
- public void TuneApplier_ResolveOllamaModelForLabAgent_FallsBackToDefault()
+ public async Task TuneApplier_ResolveOllamaModelForLabAgent_FallsBackToDefault()
{
+ await Task.CompletedTask;
var spec = WorkflowLabRuntimeSpec.Default();
var composition = spec.Compositions.First(c => c.Id == "hierarchy-squad");
var profile = spec.ModelProfiles.First(p => p.Id == "ollama-balanced");
@@ -151,15 +161,17 @@ public void TuneApplier_ResolveOllamaModelForLabAgent_FallsBackToDefault()
}
[Fact(Timeout = 15000)]
- public void AgentSetReader_ReturnsEmpty_ForMissingFile()
+ public async Task AgentSetReader_ReturnsEmpty_ForMissingFile()
{
+ await Task.CompletedTask;
var rows = RuntimeStudioAgentSetReader.TryListOllamaAgents("/nonexistent/path/agents.json");
rows.Should().BeEmpty();
}
[Fact(Timeout = 15000)]
- public void AgentSetReader_ParsesOllamaAgents_SortedById()
+ public async Task AgentSetReader_ParsesOllamaAgents_SortedById()
{
+ await Task.CompletedTask;
var tempDir = CreateTempDirectory();
try
{
@@ -191,8 +203,9 @@ public void AgentSetReader_ParsesOllamaAgents_SortedById()
}
[Fact(Timeout = 90000)]
- public void TuneApplier_Apply_DryRun_ReportsPlannedChanges()
+ public async Task TuneApplier_Apply_DryRun_ReportsPlannedChanges()
{
+ await Task.CompletedTask;
var tempDir = CreateTempDirectory();
try
{
@@ -243,8 +256,9 @@ public void TuneApplier_Apply_DryRun_ReportsPlannedChanges()
}
[Fact(Timeout = 15000)]
- public void WorkflowLabRuntimeSpec_Default_HasExpectedCompositions()
+ public async Task WorkflowLabRuntimeSpec_Default_HasExpectedCompositions()
{
+ await Task.CompletedTask;
var spec = WorkflowLabRuntimeSpec.Default();
spec.Compositions.Should().HaveCount(2);
diff --git a/application/src/Nexo.Tests.CLI/Tests/Commands/UnityDevCommandTests.cs b/application/src/Nexo.Tests.CLI/Tests/Commands/UnityDevCommandTests.cs
index 2f304c100..304bb0c05 100644
--- a/application/src/Nexo.Tests.CLI/Tests/Commands/UnityDevCommandTests.cs
+++ b/application/src/Nexo.Tests.CLI/Tests/Commands/UnityDevCommandTests.cs
@@ -10,15 +10,17 @@ namespace Nexo.Tests.CLI.Tests.Commands;
public sealed class UnityDevCommandTests
{
[Fact(Timeout = 15000)]
- public void ParseFiles_EmptyInput_ReturnsEmpty()
+ public async Task ParseFiles_EmptyInput_ReturnsEmpty()
{
+ await Task.CompletedTask;
UnityDevCommand.ParseFiles("").Should().BeEmpty();
UnityDevCommand.ParseFiles(" ").Should().BeEmpty();
}
[Fact(Timeout = 15000)]
- public void ParseFiles_SingleFile_ParsesCorrectly()
+ public async Task ParseFiles_SingleFile_ParsesCorrectly()
{
+ await Task.CompletedTask;
var input = @"// FILE: Assets/Scripts/Player.cs
using UnityEngine;
@@ -35,8 +37,9 @@ public class Player : MonoBehaviour
}
[Fact(Timeout = 15000)]
- public void ParseFiles_MultipleFiles_SplitsCorrectly()
+ public async Task ParseFiles_MultipleFiles_SplitsCorrectly()
{
+ await Task.CompletedTask;
var input = @"// FILE: Assets/Scripts/Weapons/IWeapon.cs
public interface IWeapon
{
@@ -67,8 +70,9 @@ public void Fire_DoesNotThrow() { }
}
[Fact(Timeout = 15000)]
- public void ParseFiles_IgnoresContentBeforeFirstMarker()
+ public async Task ParseFiles_IgnoresContentBeforeFirstMarker()
{
+ await Task.CompletedTask;
var input = @"Here is some preamble text from the LLM.
This should be ignored.
@@ -83,8 +87,9 @@ public class Foo { }";
}
[Fact(Timeout = 15000)]
- public void ParseFiles_HandlesBackslashPaths()
+ public async Task ParseFiles_HandlesBackslashPaths()
{
+ await Task.CompletedTask;
var input = @"// FILE: Assets\Scripts\Bar.cs
public class Bar { }";
var result = UnityDevCommand.ParseFiles(input);
@@ -94,8 +99,9 @@ public class Bar { }";
}
[Fact(Timeout = 15000)]
- public void ValidateProjectRoot_MissingDirectory_ReturnsFalse()
+ public async Task ValidateProjectRoot_MissingDirectory_ReturnsFalse()
{
+ await Task.CompletedTask;
var originalErr = Console.Error;
using var errWriter = new StringWriter();
Console.SetError(errWriter);
@@ -112,8 +118,9 @@ public void ValidateProjectRoot_MissingDirectory_ReturnsFalse()
}
[Fact(Timeout = 15000)]
- public void ValidateProjectRoot_MissingAssetsFolder_ReturnsFalse()
+ public async Task ValidateProjectRoot_MissingAssetsFolder_ReturnsFalse()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), $"nexo-unity-test-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDir);
try
@@ -139,8 +146,9 @@ public void ValidateProjectRoot_MissingAssetsFolder_ReturnsFalse()
}
[Fact(Timeout = 15000)]
- public void ValidateProjectRoot_ValidProject_ReturnsTrue()
+ public async Task ValidateProjectRoot_ValidProject_ReturnsTrue()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), $"nexo-unity-test-{Guid.NewGuid():N}");
Directory.CreateDirectory(Path.Combine(tempDir, "Assets"));
try
@@ -154,8 +162,9 @@ public void ValidateProjectRoot_ValidProject_ReturnsTrue()
}
[Fact(Timeout = 15000)]
- public void ValidateProjectRoot_Json_MissingDirectory_EmitsJsonError()
+ public async Task ValidateProjectRoot_Json_MissingDirectory_EmitsJsonError()
{
+ await Task.CompletedTask;
var originalOut = Console.Out;
using var outWriter = new StringWriter();
Console.SetOut(outWriter);
@@ -176,8 +185,9 @@ public void ValidateProjectRoot_Json_MissingDirectory_EmitsJsonError()
}
[Fact(Timeout = 15000)]
- public void WriteManifest_CreatesValidJsonManifest()
+ public async Task WriteManifest_CreatesValidJsonManifest()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), $"nexo-unity-manifest-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDir);
try
@@ -208,8 +218,9 @@ public void WriteManifest_CreatesValidJsonManifest()
}
[Fact(Timeout = 15000)]
- public void ExecuteList_EmptyProject_ReturnsZeroSystems()
+ public async Task ExecuteList_EmptyProject_ReturnsZeroSystems()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), $"nexo-unity-list-{Guid.NewGuid():N}");
Directory.CreateDirectory(Path.Combine(tempDir, "Assets"));
try
@@ -239,8 +250,9 @@ public void ExecuteList_EmptyProject_ReturnsZeroSystems()
}
[Fact(Timeout = 15000)]
- public void ExecuteList_WithGeneratedSystems_ListsThem()
+ public async Task ExecuteList_WithGeneratedSystems_ListsThem()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), $"nexo-unity-list-{Guid.NewGuid():N}");
var genDir = Path.Combine(tempDir, "Assets", "Scripts", "Generated", "Weapons");
Directory.CreateDirectory(genDir);
@@ -275,8 +287,9 @@ public void ExecuteList_WithGeneratedSystems_ListsThem()
}
[Fact(Timeout = 15000)]
- public void UnityDevCommand_HasExpectedSubcommands()
+ public async Task UnityDevCommand_HasExpectedSubcommands()
{
+ await Task.CompletedTask;
var cmd = new UnityDevCommand(() => null!);
var subNames = cmd.Subcommands.Select(s => s.Name).ToList();
subNames.Should().Contain("generate");
@@ -288,8 +301,9 @@ public void UnityDevCommand_HasExpectedSubcommands()
}
[Fact(Timeout = 15000)]
- public void Generate_Subcommand_HasExpectedOptions()
+ public async Task Generate_Subcommand_HasExpectedOptions()
{
+ await Task.CompletedTask;
var cmd = new UnityDevCommand(() => null!);
var gen = cmd.Subcommands.Single(s => s.Name == "generate");
var optNames = gen.Options.Select(o => o.Name).ToList();
@@ -301,8 +315,9 @@ public void Generate_Subcommand_HasExpectedOptions()
}
[Fact(Timeout = 15000)]
- public void Iterate_Subcommand_HasExpectedOptions()
+ public async Task Iterate_Subcommand_HasExpectedOptions()
{
+ await Task.CompletedTask;
var cmd = new UnityDevCommand(() => null!);
var iter = cmd.Subcommands.Single(s => s.Name == "iterate");
var optNames = iter.Options.Select(o => o.Name).ToList();
@@ -312,15 +327,17 @@ public void Iterate_Subcommand_HasExpectedOptions()
}
[Fact(Timeout = 15000)]
- public void Init_Subcommand_Exists()
+ public async Task Init_Subcommand_Exists()
{
+ await Task.CompletedTask;
var cmd = new UnityDevCommand(() => null!);
cmd.Subcommands.Should().Contain(s => s.Name == "init");
}
[Fact(Timeout = 15000)]
- public void ExecuteInit_CreatesProjectStructure()
+ public async Task ExecuteInit_CreatesProjectStructure()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), "nexo-unity-init-" + Guid.NewGuid().ToString("N")[..8]);
try
{
@@ -343,8 +360,9 @@ public void ExecuteInit_CreatesProjectStructure()
}
[Fact(Timeout = 15000)]
- public void ExecuteInit_CreatesManifestWithDependencies()
+ public async Task ExecuteInit_CreatesManifestWithDependencies()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), "nexo-unity-init-" + Guid.NewGuid().ToString("N")[..8]);
try
{
@@ -364,8 +382,9 @@ public void ExecuteInit_CreatesManifestWithDependencies()
}
[Fact(Timeout = 15000)]
- public void ExecuteInit_CreatesAssemblyDefinitions()
+ public async Task ExecuteInit_CreatesAssemblyDefinitions()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), "nexo-unity-init-" + Guid.NewGuid().ToString("N")[..8]);
try
{
@@ -389,8 +408,9 @@ public void ExecuteInit_CreatesAssemblyDefinitions()
}
[Fact(Timeout = 15000)]
- public void ExecuteInit_CreatesGitignore()
+ public async Task ExecuteInit_CreatesGitignore()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), "nexo-unity-init-" + Guid.NewGuid().ToString("N")[..8]);
try
{
@@ -409,8 +429,9 @@ public void ExecuteInit_CreatesGitignore()
}
[Fact(Timeout = 15000)]
- public void ExecuteInit_CreatesNexoConfig()
+ public async Task ExecuteInit_CreatesNexoConfig()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), "nexo-unity-init-" + Guid.NewGuid().ToString("N")[..8]);
try
{
@@ -428,8 +449,9 @@ public void ExecuteInit_CreatesNexoConfig()
}
[Fact(Timeout = 15000)]
- public void ExecuteInit_IdempotentOnExistingProject()
+ public async Task ExecuteInit_IdempotentOnExistingProject()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), "nexo-unity-init-" + Guid.NewGuid().ToString("N")[..8]);
try
{
@@ -448,8 +470,9 @@ public void ExecuteInit_IdempotentOnExistingProject()
}
[Fact(Timeout = 15000)]
- public void ExecuteInit_JsonOutput_ReturnsStructuredResult()
+ public async Task ExecuteInit_JsonOutput_ReturnsStructuredResult()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), "nexo-unity-init-" + Guid.NewGuid().ToString("N")[..8]);
try
{
@@ -469,8 +492,9 @@ public void ExecuteInit_JsonOutput_ReturnsStructuredResult()
}
[Fact(Timeout = 15000)]
- public void Assets_Subcommand_HasExpectedOptions()
+ public async Task Assets_Subcommand_HasExpectedOptions()
{
+ await Task.CompletedTask;
var cmd = new UnityDevCommand(() => null!);
var assets = cmd.Subcommands.Single(s => s.Name == "assets");
var optNames = assets.Options.Select(o => o.Name).ToList();
@@ -481,8 +505,9 @@ public void Assets_Subcommand_HasExpectedOptions()
}
[Fact(Timeout = 15000)]
- public void Qa_Subcommand_HasExpectedOptions()
+ public async Task Qa_Subcommand_HasExpectedOptions()
{
+ await Task.CompletedTask;
var cmd = new UnityDevCommand(() => null!);
var qa = cmd.Subcommands.Single(s => s.Name == "qa");
var optNames = qa.Options.Select(o => o.Name).ToList();
@@ -493,8 +518,9 @@ public void Qa_Subcommand_HasExpectedOptions()
}
[Fact(Timeout = 15000)]
- public void Fullstack_Subcommand_Exists()
+ public async Task Fullstack_Subcommand_Exists()
{
+ await Task.CompletedTask;
var cmd = new UnityDevCommand(() => null!);
var fullstack = cmd.Subcommands.SingleOrDefault(s => s.Name == "fullstack");
fullstack.Should().NotBeNull();
@@ -506,8 +532,9 @@ public void Fullstack_Subcommand_Exists()
}
[Fact(Timeout = 15000)]
- public void FindUnityEditor_ReturnsNullWhenNotInstalled()
+ public async Task FindUnityEditor_ReturnsNullWhenNotInstalled()
{
+ await Task.CompletedTask;
var result = UnityDevCommand.FindUnityEditor();
// On CI/test machines without Unity, this should return null (or a valid path if installed)
// We just verify it doesn't throw
@@ -515,8 +542,9 @@ public void FindUnityEditor_ReturnsNullWhenNotInstalled()
}
[Fact(Timeout = 15000)]
- public void BuildAssetPrompt_ContainsAssetTypeAndDescription()
+ public async Task BuildAssetPrompt_ContainsAssetTypeAndDescription()
{
+ await Task.CompletedTask;
var prompt = UnityDevCommand.BuildAssetPrompt("material", "shiny gold material");
prompt.Should().Contain("material");
prompt.Should().Contain("shiny gold material");
@@ -525,8 +553,9 @@ public void BuildAssetPrompt_ContainsAssetTypeAndDescription()
}
[Fact(Timeout = 15000)]
- public void BuildAssetPrompt_AllTypesProduceSchemaHints()
+ public async Task BuildAssetPrompt_AllTypesProduceSchemaHints()
{
+ await Task.CompletedTask;
var types = new[] { "material", "prefab", "scene", "audio", "animation", "soundbank", "animationset", "ui" };
foreach (var type in types)
{
@@ -536,14 +565,16 @@ public void BuildAssetPrompt_AllTypesProduceSchemaHints()
}
[Fact(Timeout = 15000)]
- public void Truncate_ShortText_ReturnsUnchanged()
+ public async Task Truncate_ShortText_ReturnsUnchanged()
{
+ await Task.CompletedTask;
UnityDevCommand.Truncate("hello", 100).Should().Be("hello");
}
[Fact(Timeout = 15000)]
- public void Truncate_LongText_IsTruncated()
+ public async Task Truncate_LongText_IsTruncated()
{
+ await Task.CompletedTask;
var long_text = new string('x', 200);
var result = UnityDevCommand.Truncate(long_text, 50);
result.Should().HaveLength(50 + "\n... (truncated)".Length);
@@ -551,15 +582,17 @@ public void Truncate_LongText_IsTruncated()
}
[Fact(Timeout = 15000)]
- public void Truncate_NullOrEmpty_ReturnsInput()
+ public async Task Truncate_NullOrEmpty_ReturnsInput()
{
+ await Task.CompletedTask;
UnityDevCommand.Truncate("", 10).Should().Be("");
UnityDevCommand.Truncate(null!, 10).Should().BeNull();
}
[Fact(Timeout = 15000)]
- public void BuildAssetPrompt_Animation_ContainsSchemaHints()
+ public async Task BuildAssetPrompt_Animation_ContainsSchemaHints()
{
+ await Task.CompletedTask;
var prompt = UnityDevCommand.BuildAssetPrompt("animation", "FPS character animator");
prompt.Should().Contain("AnimationDescriptor");
prompt.Should().Contain("state machine");
@@ -570,8 +603,9 @@ public void BuildAssetPrompt_Animation_ContainsSchemaHints()
}
[Fact(Timeout = 15000)]
- public void BuildAssetPrompt_SoundBank_ContainsSchemaHints()
+ public async Task BuildAssetPrompt_SoundBank_ContainsSchemaHints()
{
+ await Task.CompletedTask;
var prompt = UnityDevCommand.BuildAssetPrompt("soundbank", "weapon sounds");
prompt.Should().Contain("SoundBankDescriptor");
prompt.Should().Contain("SelectionMode");
@@ -581,8 +615,9 @@ public void BuildAssetPrompt_SoundBank_ContainsSchemaHints()
}
[Fact(Timeout = 15000)]
- public void BuildAssetPrompt_AnimationSet_ContainsSchemaHints()
+ public async Task BuildAssetPrompt_AnimationSet_ContainsSchemaHints()
{
+ await Task.CompletedTask;
var prompt = UnityDevCommand.BuildAssetPrompt("animationset", "FPS character locomotion");
prompt.Should().Contain("AnimationSetDescriptor");
prompt.Should().Contain("BlendTree");
@@ -592,8 +627,9 @@ public void BuildAssetPrompt_AnimationSet_ContainsSchemaHints()
}
[Fact(Timeout = 15000)]
- public void Animation_SoundBank_AnimationSet_AreValidAssetTypes()
+ public async Task Animation_SoundBank_AnimationSet_AreValidAssetTypes()
{
+ await Task.CompletedTask;
var prompt1 = UnityDevCommand.BuildAssetPrompt("animation", "test");
var prompt2 = UnityDevCommand.BuildAssetPrompt("soundbank", "test");
var prompt3 = UnityDevCommand.BuildAssetPrompt("animationset", "test");
@@ -604,8 +640,9 @@ public void Animation_SoundBank_AnimationSet_AreValidAssetTypes()
}
[Fact(Timeout = 15000)]
- public void AssetDescriptor_JsonRoundTrip_Material()
+ public async Task AssetDescriptor_JsonRoundTrip_Material()
{
+ await Task.CompletedTask;
var original = new Nexo.GameDomain.Assets.MaterialDescriptor
{
Id = "mat-1",
@@ -627,8 +664,9 @@ public void AssetDescriptor_JsonRoundTrip_Material()
}
[Fact(Timeout = 15000)]
- public void AssetDescriptor_JsonRoundTrip_Audio()
+ public async Task AssetDescriptor_JsonRoundTrip_Audio()
{
+ await Task.CompletedTask;
var original = new Nexo.GameDomain.Assets.AudioDescriptor
{
Id = "explosion",
@@ -663,8 +701,9 @@ public void AssetDescriptor_JsonRoundTrip_Audio()
}
[Fact(Timeout = 15000)]
- public void Pin_Subcommand_Exists()
+ public async Task Pin_Subcommand_Exists()
{
+ await Task.CompletedTask;
var cmd = new UnityDevCommand(() => null!);
var pin = cmd.Subcommands.SingleOrDefault(s => s.Name == "pin");
pin.Should().NotBeNull();
@@ -678,8 +717,9 @@ public void Pin_Subcommand_Exists()
}
[Fact(Timeout = 15000)]
- public void Compose_Subcommand_Exists()
+ public async Task Compose_Subcommand_Exists()
{
+ await Task.CompletedTask;
var cmd = new UnityDevCommand(() => null!);
var compose = cmd.Subcommands.SingleOrDefault(s => s.Name == "compose");
compose.Should().NotBeNull();
@@ -691,8 +731,9 @@ public void Compose_Subcommand_Exists()
}
[Fact(Timeout = 15000)]
- public void Generate_Subcommand_HasTemplateOption()
+ public async Task Generate_Subcommand_HasTemplateOption()
{
+ await Task.CompletedTask;
var cmd = new UnityDevCommand(() => null!);
var gen = cmd.Subcommands.Single(s => s.Name == "generate");
var optNames = gen.Options.Select(o => o.Name).ToList();
@@ -700,8 +741,9 @@ public void Generate_Subcommand_HasTemplateOption()
}
[Fact(Timeout = 15000)]
- public void ExecutePin_PinsField()
+ public async Task ExecutePin_PinsField()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), $"nexo-pin-test-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDir);
var descriptorFile = Path.Combine("weapons", "rifle.json");
@@ -727,8 +769,9 @@ public void ExecutePin_PinsField()
}
[Fact(Timeout = 15000)]
- public void ExecutePin_UnpinsField()
+ public async Task ExecutePin_UnpinsField()
{
+ await Task.CompletedTask;
var tempDir = Path.Combine(Path.GetTempPath(), $"nexo-pin-test-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDir);
var descriptorFile = Path.Combine("weapons", "rifle.json");
@@ -756,15 +799,17 @@ public void ExecutePin_UnpinsField()
}
[Fact(Timeout = 15000)]
- public void ExecutePin_NoValue_ReturnsError()
+ public async Task ExecutePin_NoValue_ReturnsError()
{
+ await Task.CompletedTask;
var code = UnityDevCommand.ExecutePin("/tmp", "file.json", "field", null, false, false);
code.Should().Be(1);
}
[Fact(Timeout = 15000)]
- public void BuildGeneratePrompt_InjectsConstraintFragment()
+ public async Task BuildGeneratePrompt_InjectsConstraintFragment()
{
+ await Task.CompletedTask;
var prompt = UnityDevCommand.BuildGeneratePrompt(
"weapon system", "Assets/Scripts/Generated", "Assets/Tests",
constraintFragment: "\nProject constraints:\n- Max 200 lines per file");
@@ -774,8 +819,9 @@ public void BuildGeneratePrompt_InjectsConstraintFragment()
}
[Fact(Timeout = 15000)]
- public void BuildGeneratePrompt_InjectsTemplateFragment()
+ public async Task BuildGeneratePrompt_InjectsTemplateFragment()
{
+ await Task.CompletedTask;
var prompt = UnityDevCommand.BuildGeneratePrompt(
"weapon system", "Assets/Scripts/Generated", "Assets/Tests",
templateFragment: "\nFixed values from template:\n name: Shotgun");
@@ -785,8 +831,9 @@ public void BuildGeneratePrompt_InjectsTemplateFragment()
}
[Fact(Timeout = 15000)]
- public void BuildGeneratePrompt_InjectsCompositionContext()
+ public async Task BuildGeneratePrompt_InjectsCompositionContext()
{
+ await Task.CompletedTask;
var prompt = UnityDevCommand.BuildGeneratePrompt(
"combat system", "Assets/Scripts/Generated", "Assets/Tests",
compositionContext: "Health system provides IDamageable interface");
diff --git a/docs/DocsIndex.md b/docs/DocsIndex.md
index 36da89388..11fa9b9bd 100644
--- a/docs/DocsIndex.md
+++ b/docs/DocsIndex.md
@@ -64,7 +64,7 @@ Documentation index for the Nexo platform. Start here to find what you need.
- `docs/FriendMeshPrefab.md` — prefab Docker Compose + env template for a small shared **Nexo.API** hub (friends / tailnet).
- `docs/MeshPhase8OperatorHardening.md` — **Mesh Phase 8:** discovery admission, trust alias, `nexo mesh hub` / `mesh director`, TLS example.
-- `docs/MeshVirtualLab.md` — **Virtual mesh lab:** two Nexo.API nodes in Docker + verify script (no extra hardware).
+- `docs/MeshVirtualLab.md` — **Virtual mesh lab:** two Nexo.API nodes in Docker + verify script (no extra hardware); **`scripts/bootstrap-cloud-mesh-lab.sh`** for Ubuntu/Debian cloud VMs.
- `docs/MeshAgentSetupCapabilityBreakdown.md` — mesh agent setup **tear sheet**: capability tiers, ports, and ops checklist mapped to mesh DI surfaces.
- `docs/TrustAndInformationArchitecture.md` — sanitization, audit, access boundaries.
- `docs/TailscaleAndNexo.md` — Tailscale + Nexo exposure profile, ACL guidance, advisory endpoint.
diff --git a/docs/IntegratorGuide.md b/docs/IntegratorGuide.md
index 842e03a73..a8c4ea0bf 100644
--- a/docs/IntegratorGuide.md
+++ b/docs/IntegratorGuide.md
@@ -4,7 +4,9 @@ This guide is for teams embedding Nexo, extending bricks, or hosting custom back
## Getting started with the Nexo SDK
-The managed SDK entry point is the `Nexo.Sdk` project (`src/Nexo.Sdk/Nexo.Sdk.csproj`). Add a project reference from your integrator assembly:
+The slim **HTTP client** package is the `Nexo.Sdk` project (`src/Nexo.Sdk/Nexo.Sdk.csproj`). Register it with **`AddNexoClientSdk(baseUrl, ...)`** (`Nexo.Sdk.Client`). The obsolete **`AddNexoSdk(string baseUrl, ...)`** name remains as a compat shim. For **host-side** brick/agent registration, use **`Nexo.Hosting.Sdk.AddNexoSdk`** (before `AddNexo`). See [`docs/architecture/SdkStructure.md`](architecture/SdkStructure.md).
+
+Add a project reference from your integrator assembly:
```xml
diff --git a/docs/MeshVirtualLab.md b/docs/MeshVirtualLab.md
index e6bae4006..3ca2501c7 100644
--- a/docs/MeshVirtualLab.md
+++ b/docs/MeshVirtualLab.md
@@ -31,6 +31,31 @@ docker compose -f docker-compose.mesh-lab.yml --env-file .env.mesh-lab up -d --b
Host URLs: **`http://127.0.0.1:18081`** (peer-a), **`http://127.0.0.1:18082`** (peer-b).
+## Cloud VM (one-shot bootstrap)
+
+This Cursor/workspace cannot provision cloud VMs or run Docker for you. On **any fresh Ubuntu/Debian VM** (AWS/GCP/Azure/Linode):
+
+1. **SSH** into the VM and install Git if needed.
+2. **Clone** this repository and `cd` to the repo root.
+3. Run:
+
+ ```bash
+ chmod +x scripts/bootstrap-cloud-mesh-lab.sh
+ ./scripts/bootstrap-cloud-mesh-lab.sh --install-docker
+ ```
+
+ `--install-docker` uses **`apt-get`** to install **`docker.io`** and **`docker-compose-v2`**, then creates **`.env.mesh-lab`** from **`docs/config/mesh-lab.env.example`** (random lab secrets), **`docker compose up --build`**, and **`scripts/mesh-lab-verify.sh`**.
+
+4. From your laptop, **tunnel** the peer ports if the VM has no public listener:
+
+ ```bash
+ ssh -L 18081:127.0.0.1:18081 -L 18082:127.0.0.1:18082 user@your-vm
+ ```
+
+ Then open **`http://127.0.0.1:18081`** / **`18082`** locally.
+
+If Docker is already installed, omit **`--install-docker`**. For non-apt Linux, install Docker + Compose v2 manually ([Docker Engine install](https://docs.docker.com/engine/install/)), then run **`./scripts/bootstrap-cloud-mesh-lab.sh`** without **`--install-docker`**.
+
## Workers + stress ramp
```bash
diff --git a/docs/architecture/README.md b/docs/architecture/README.md
index bbe6053cf..120670965 100644
--- a/docs/architecture/README.md
+++ b/docs/architecture/README.md
@@ -11,4 +11,7 @@ High-level maps of how Nexo is structured. For day-to-day commands, see the repo
| [Forge map adaptation](forge-map-adaptation.md) | `MapAdaptationPlanner`, dry-run pipeline, engine manifest JSON, and Forge persistence options. |
| [Forge map host integration](forge-map-host-integration.md) | Milestones M1–M6; terrain payload summaries; optional material **`IModel`** augmentation; tile cache; Unity/Godot package layouts. |
| [Aesthetic and engine adaptation](aesthetic-engine-adaptation.md) | Cross-engine `AestheticPack` fields, validation, Forge `apply-pack`, and shared Mapbox tile helpers. |
+| [SDK-style layout](SdkStructure.md) | Ports vs options vs builders; `Nexo.Hosting.Sdk` vs `Nexo.Sdk.Client`; folder conventions. |
+| [SDK migration plan (remaining gaps)](SdkMigrationPlan.md) | **Execution status** at top; **[Plan: close remaining gaps](#plan-close-remaining-gaps-post-migration)** (D1–D6: docs, sweep, consumers, optional `Sdk/Options`, hosting polish, CI clarity). |
+| **`Nexo.Framework.Sdk`** | Optional megaproject in `src/Nexo.Framework.Sdk/` — `AddNexoFramework` combines HTTP client + `AddNexo`. |
| [GitHub Actions trigger policy](../../.github/workflows/README.md) | Manual-first workflow policy (`workflow_dispatch`); tag-driven release automation unchanged. |
diff --git a/docs/architecture/SdkMigrationPlan.md b/docs/architecture/SdkMigrationPlan.md
new file mode 100644
index 000000000..a74394d8d
--- /dev/null
+++ b/docs/architecture/SdkMigrationPlan.md
@@ -0,0 +1,204 @@
+# Plan: close remaining SDK-style layout gaps
+
+## Execution status (branch)
+
+| Item | Status |
+| ---- | ------ |
+| **Track A — Hosting partials** | **`AddNexo`** → **`NexoKernelRegistrar.Register`**; **`NexoKernelRegistrationContext`** + **20 phase methods** in **`NexoKernelRegistrar.Phases.cs`** + **`NexoKernelRegistrar.Ephemeral.cs`** (`EphemeralModelsEnabled`). **`ModuleSelection`** in **`NexoKernelRegistrationModels.cs`**. **`NexoServiceCollectionExtensions.Deployment.cs`** — deployment profile helpers; **`NexoServiceCollectionExtensions.NodeCapabilityRuntime.cs`** — **`RegisterNodeCapabilityRuntime`**. |
+| **Track B — Infrastructure `Sdk/`** | **Done:** `*ServiceCollectionExtensions` under **`Feature/Sdk/Extensions/`** with **`Nexo.Infrastructure.Sdk.`** namespaces. Collision-safe: **`Nexo.Infrastructure.NodeCapabilityRuntime.Sdk`**, **`Nexo.Infrastructure.Execution.Sdk`**, **`Nexo.Infrastructure.Execution.Routing.Sdk`**, **`Nexo.Infrastructure.Mesh.Sdk`**. |
+| **Consumer projects** | **`GlobalUsings.Infrastructure.Sdk.cs`** in **`Nexo.Hosting`**; **`Nexo.CLI`** and **`Nexo.Tests.Infrastructure`** link it for Sdk extension resolution. |
+| **Non-goal — ports** | **`INexoSdkBuilder`** in **`Nexo.Infrastructure.Sdk.Ports`**. |
+| **Non-goal — megapackage** | **`Nexo.Framework.Sdk`** + **`AddNexoFramework`**. |
+
+---
+
+This document turns the “remaining gaps” from [`SdkStructure.md`](SdkStructure.md) into **ordered, low-risk work** with clear completion criteria. No calendar estimates — only **what must change**, **dependencies**, and **risk**.
+
+---
+
+## Goal
+
+1. **`Nexo.Hosting` — kernel registration**
+ Keep **`AddNexo`** as the public entry point while making wiring **navigable**: deployment/profile resolution stays in **`NexoServiceCollectionExtensions`** partials (**`Deployment`**, **`NodeCapabilityRuntime`**); all subsystem registration runs through **`NexoKernelRegistrar`** (**`Register`** → **`NexoKernelRegistrationContext`** → **`RegisterPhase01`–`RegisterPhase20`** in **`NexoKernelRegistrar.Phases.cs`**). Behavior and order remain unchanged.
+
+2. **`Nexo.Infrastructure`**
+ DI registration surface lives under **`Feature/Sdk/Extensions/`** with **`Nexo.Infrastructure.Sdk.*`** extension namespaces (collision-safe **`Nexo.Infrastructure..Sdk`** where needed). Implementation types stay under **`Nexo.Infrastructure.`**. Optional physical **`Sdk/Options/`** groups option types without renaming namespaces (see **`Pipelines/Sdk/Options/`** pilot).
+
+---
+
+## Principle (non-negotiable)
+
+- **Sdk extension namespaces** — `*ServiceCollectionExtensions` for DI use **`Nexo.Infrastructure.Sdk.*`** (see [`SdkStructure.md`](SdkStructure.md)). Application/runtime types keep existing **`Nexo.Infrastructure.`** namespaces. Consumer apps may use **`GlobalUsings.Infrastructure.Sdk.cs`** (or explicit `using` lines) to bring extension methods into scope.
+- **One mechanical theme per PR** — easier review, bisection, and rollback.
+- **`dotnet build Nexo.sln` + relevant `dotnet test` filters** after each merge.
+
+---
+
+## Track A — Hosting composition (supersedes old “slice AddNexo into many partials” plan)
+
+### A.1 Files (current)
+
+| File | Role |
+| ---- | ---- |
+| `NexoServiceCollectionExtensions.cs` | **`AddNexo`**, **`AddNexoProfile`** → builds **`NexoKernelRegistrationContext`** and calls **`NexoKernelRegistrar.Register`**. |
+| `NexoServiceCollectionExtensions.Deployment.cs` | **`ResolveDeploymentProfile`**, **`GetModuleSelection`**, **`ResolveStrictMode`**, **`ParseBooleanEnvironmentVariable`** (internal). |
+| `NexoServiceCollectionExtensions.NodeCapabilityRuntime.cs` | **`RegisterNodeCapabilityRuntime`** (internal) — NCR + model artifact catalog wiring when NCR is enabled. |
+| `NexoKernelRegistrationModels.cs` | **`ModuleSelection`**, **`NexoKernelRegistrationContext`**. |
+| `NexoKernelRegistrar.cs` | **`Register`** dispatches phases **01–20**. |
+| `NexoKernelRegistrar.Phases.cs` | One private method per **`// ──`** section (kernel subsystems). |
+| `NexoKernelRegistrar.Ephemeral.cs` | **`EphemeralModelsEnabled()`** shared by ephemeral lifecycle + trust phases. |
+
+### A.2 Completion criteria
+
+- **Zero** behavior change vs monolithic registration: same order, env vars, and deployment-profile gates.
+- Reviewers can open **`NexoKernelRegistrar.Phases.cs`** and jump by section comment.
+
+### A.3 Risks
+
+- **Merge conflicts** if many branches touch **`NexoKernelRegistrar.Phases.cs`** or **`NexoServiceCollectionExtensions`** entry points — coordinate kernel wiring changes in focused PRs.
+
+---
+
+## Track B — Infrastructure SDK folders (incremental)
+
+### B.1 Convention (repeat per area)
+
+Under each **top-level feature folder** (e.g. `Observation/`, `NodeCapabilityRuntime/`, `Pipelines/`):
+
+```
+Feature/
+ Sdk/
+ Options/ # optional — registration-related *Options.cs (namespaces often unchanged)
+ Extensions/ # *ServiceCollectionExtensions.cs — DI registration surface
+ ... existing impl files ...
+```
+
+- **DI extension classes** (`*ServiceCollectionExtensions`) use **`Nexo.Infrastructure.Sdk.`** (or **`Nexo.Infrastructure..Sdk`** when the simple name collides with runtime types).
+- **Implementation types** (stores, adapters, domain-ish infrastructure services) keep **`Nexo.Infrastructure.`** (or deeper sub-namespaces). Physical folder may differ from namespace by design.
+- **Do not** force every type into `Sdk/` — only **registration entry points** and, optionally, **options bags** under **`Sdk/Options/`**.
+
+### B.2 Suggested order (dependency / churn)
+
+| Phase | Area | Rationale |
+| ----- | ---- | ----------- |
+| **B.2.1** | **NodeCapabilityRuntime** | Already has `NodeCapabilityRuntimeOptions.cs` + `*ServiceCollectionExtensions.cs`; small, validates pattern. |
+| **B.2.2** | **Observation** | `Observation`-related extensions + options grouped; touches Forge-adjacent tests. |
+| **B.2.3** | **Pipelines** | Many `Pipeline*` options + `PipelineServiceCollectionExtensions`; high readability win. |
+| **B.2.4** | **Persistence / Database** | `PersistenceServiceCollectionExtensions`, `DatabaseServiceCollectionExtensions`, Ephemeral options. |
+| **B.2.5** | **Adaptation** | `AdaptationServiceCollectionExtensions`, `AdaptationBrickOptions`. |
+| **B.2.6** | **Trust** | `TrustServiceCollectionExtensions`, boundary/gate options. |
+| **B.2.7** | **Execution** (routing, mesh bricks) | Larger; split only extension entry files first, leave adapters in place. |
+| **B.2.8** | **Remaining** `*ServiceCollectionExtensions.cs` at Infrastructure root | Sweep or fold into nearest feature `Sdk/Extensions`. |
+
+Lower phases can proceed in parallel **only** if they touch disjoint paths (separate PRs).
+
+### B.3 Completion criteria (per phase)
+
+- Types compile with **stable public namespaces** (extensions moved to **`Nexo.Infrastructure.Sdk.*`** as agreed; options moved physically may keep **`Nexo.Infrastructure.`** namespace).
+- No new public API unless explicitly intended (prefer moves only).
+- **`dotnet build Nexo.sln`** + targeted tests when touching DI.
+
+### B.4 Risks
+
+- **Glob imports / IDE** — developers rely on path; communicate in [`SdkStructure.md`](SdkStructure.md) when a phase completes.
+- **Copy-assemblies / test harness** — `Nexo.Tests.Infrastructure` copies assemblies; confirm **no hard-coded paths** to old locations (usually unaffected).
+
+---
+
+## Track C — Documentation sync
+
+After each Track A/B milestone:
+
+- One-line note under **Folder conventions** in [`SdkStructure.md`](SdkStructure.md) listing completed areas (optional table).
+- No duplicate prose — link to this plan for “what’s left.”
+
+---
+
+## Definition of “done” for the overall initiative
+
+This initiative is **functionally complete** for kernel DI and Infrastructure Sdk extensions (see **Execution status** above). Remaining work is **documentation alignment**, **optional layout polish**, **consumer ergonomics**, and **CI clarity** — tracked in **[Plan: close remaining gaps](#plan-close-remaining-gaps-post-migration)** below.
+
+Historical bullets (superseded where noted):
+
+- **Track A — achieved differently:** `AddNexo` delegates to **`NexoKernelRegistrar`** with phase partials (`NexoKernelRegistrar.Phases.cs`), not multiple `NexoServiceCollectionExtensions.*` partial files. Navigation goal is met via registrar phases + `Deployment` partial.
+- **Track B — DI extensions:** `*ServiceCollectionExtensions` live under **`Feature/Sdk/Extensions/`** with **`Nexo.Infrastructure.Sdk.*`** (and collision-safe `*.Sdk` namespaces). Optional **`Sdk/Options`** physical grouping remains incremental.
+- CI green on **`Nexo.sln`**; **`Nexo.LocalDevCore.slnf`** / **`Nexo.PrimeTime.slnf`** as documented in repo CI / contributor docs (see closing plan).
+
+---
+
+## Plan: close remaining gaps (post-migration)
+
+Ordered for **low risk** first; each phase can be its own PR.
+
+### Phase D1 — Documentation alignment (required)
+
+| Step | Action | Done when |
+| ---- | ------ | --------- |
+| D1.1 | Rewrite **Goal**, **Track A §A.1–A.2**, and **Definition of done** in this file so they describe **`NexoKernelRegistrar`** + **`NexoKernelRegistrationContext`** + **`NexoKernelRegistrar.Phases.cs`**, not hypothetical `NexoServiceCollectionExtensions.AddNexo.*` partials. | Text matches repo; no contradictory inventory tables. |
+| D1.2 | Fix **Track B §B.1**: state that **DI extension types** use **`Nexo.Infrastructure.Sdk.*`** (and **`Nexo.Infrastructure..Sdk`** where collision-safe), while **implementation types** remain **`Nexo.Infrastructure.`**. Remove “keep namespace on moved files” if it implies zero namespace change for extensions. | Single coherent rule for extensions vs runtime types. |
+| D1.3 | Add a short **“Completed areas”** table to [`SdkStructure.md`](SdkStructure.md) (folders + extension namespace pattern), or a bullet list linking to feature folders under **`Sdk/Extensions/`**. | Readers see what’s migrated without reading git history. |
+
+### Phase D2 — Mechanical repo sweep (required)
+
+| Step | Action | Done when |
+| ---- | ------ | --------- |
+| D2.1 | Search for **`*ServiceCollectionExtensions.cs`** outside **`**/Sdk/Extensions/`** under `src/Nexo.Infrastructure`. Either move stragglers into **`Sdk/Extensions/`** or document why they stay (e.g. generated, exceptional). | No unexplained duplicates at old paths. |
+| D2.2 | Confirm **Observation** and other pilots still compile and tests touching DI registration pass (narrow filters acceptable). | `dotnet build Nexo.sln` green. |
+
+### Phase D3 — Consumer ergonomics (recommended)
+
+| Step | Action | Done when |
+| ---- | ------ | --------- |
+| D3.1 | Audit **`*.csproj`** files that **reference `Nexo.Infrastructure`** and call Sdk extension methods **without** going through **`AddNexo`**. For each: add **``** (same pattern as CLI / Tests.Infrastructure) **or** explicit **`using Nexo.Infrastructure.Sdk.*`** in a single `Usings.cs`. | No CS1061 surprises when adding new Sdk namespaces to Hosting’s global-usings file. |
+| D3.2 | Document the **recommended pattern** in [`SdkStructure.md`](SdkStructure.md) (“link Hosting `GlobalUsings.Infrastructure.Sdk.cs` vs explicit usings”). | Contributors have a default choice. |
+
+### Phase D4 — Optional `Sdk/Options` layout (incremental, descoping allowed)
+
+| Step | Action | Done when |
+| ---- | ------ | --------- |
+| D4.1 | Pick **one** feature (e.g. **Pipelines** or **NodeCapabilityRuntime**) and move **registration-related option types** into **`Feature/Sdk/Options/`** without changing **public type names** or namespaces unless deliberate. | Pattern validated; tests/build green. |
+| D4.2 | Repeat per feature **only** where readability wins; otherwise list **explicitly descoped** areas in this plan. | No forced churn for marginal benefit. |
+
+**D4.1 pilot (done):** `PipelineExecutionOptions`, `PipelinePersistenceOptions`, and `PipelineExecutionAdapterOptions` live under **`src/Nexo.Infrastructure/Pipelines/Sdk/Options/`**; namespaces remain **`Nexo.Infrastructure.Pipelines`**.
+
+**D4.2 descoped (for now):** bulk moves for NodeCapabilityRuntime options, Trust, Adaptation, Persistence DB options — defer until a feature owner requests clearer separation.
+
+### Phase D5 — Hosting polish (optional)
+
+| Step | Action | Done when |
+| ---- | ------ | --------- |
+| D5.1 | Extract **`RegisterNodeCapabilityRuntime`** into a dedicated **`NexoServiceCollectionExtensions.NodeCapabilityRuntime.cs`** partial **or** leave as-is with a one-line comment pointing to **`NexoKernelRegistrar`** phase 01. | Clear ownership of NCR registration story. |
+| D5.2 | Optionally deduplicate **`ephemeralModels`** computation between **`RegisterPhase14_EphemeralLifecycle`** and **`RegisterPhase15_TrustProviderFactory3wayBranching`** via a private static helper or a small value on **`NexoKernelRegistrationContext`** (only if behavior stays identical). | One env-read path or documented equivalence. |
+
+### Phase D6 — CI / “definition of done” clarity (recommended)
+
+| Step | Action | Done when |
+| ---- | ------ | --------- |
+| D6.1 | Align **contributor / CI docs** (e.g. `.github` workflows, `CONTRIBUTING.md` if present) with which solution filters run on PRs: **`Nexo.sln`**, **`Nexo.LocalDevCore.slnf`**, **`Nexo.PrimeTime.slnf`**. | Expectations match automation. |
+| D6.2 | If **`PrimeTime`** is PR-gated, note **minimum test command** for SDK-touching PRs in one place. | Authors know what to run locally. |
+
+### Risks and mitigations
+
+- **D3 wide linking** — Linking global usings into many projects can hide missing imports; mitigation: keep Hosting file as **single source of truth** and review link list when adding Sdk namespaces.
+- **D4 options moves** — Namespace or folder churn can break analyzers; mitigation: **one feature per PR**, namespace-stable moves only.
+
+---
+
+### Phase D — execution checklist
+
+| Phase | Summary |
+| ----- | ------- |
+| **D1** | Done — Goal / Track A / Track B §B.1 aligned with **`NexoKernelRegistrar`** and Sdk extension namespaces; **`SdkStructure.md`** lists completed areas and consumer guidance. |
+| **D2** | Done — All **`src/Nexo.Infrastructure/**/*ServiceCollectionExtensions*.cs`** files live under **`Sdk/Extensions/`**; **`dotnet build Nexo.sln`** verified. |
+| **D3** | Done — Audit documented in **`SdkStructure.md`**: **`GlobalUsings.Infrastructure.Sdk.cs`** linked from **CLI** and **Tests.Infrastructure**; **`AddNexo`** hosts (**e.g. Nexo.API**) need no separate Sdk usings; projects that only reference Infrastructure **types** skip the link. |
+| **D4** | Done — **`Pipelines/Sdk/Options/`** pilot; further option-folder moves descoped in **D4.2** above. |
+| **D5** | Done — **`NexoServiceCollectionExtensions.NodeCapabilityRuntime.cs`**; shared **`EphemeralModelsEnabled()`** in **`NexoKernelRegistrar.Ephemeral.cs`**. |
+| **D6** | Done — **`CONTRIBUTING.md`** — solution filters, **PrimeTime** / **LocalDevCore**, **Makefile** targets, workflow pointers. |
+
+---
+
+## Explicit non-goals (unless product asks)
+
+- **Mass-renaming** of **implementation** types from **`Nexo.Infrastructure.`** to **`Nexo.Infrastructure.Sdk.*`** (breaking; extension **classes** already use Sdk-style namespaces by design).
+- Introducing a **single mega-package** that re-exports all extensions (maintenance burden).
+- Moving **port interfaces** out of `Nexo.Core.Application` into Infrastructure.
diff --git a/docs/architecture/SdkStructure.md b/docs/architecture/SdkStructure.md
new file mode 100644
index 000000000..c6b93023b
--- /dev/null
+++ b/docs/architecture/SdkStructure.md
@@ -0,0 +1,81 @@
+# SDK-style layout in the Nexo codebase
+
+This repository uses a consistent **SDK composition** model so features stay **extensible** (interfaces + options + default implementations) and **discoverable** (grouped folders and entry types).
+
+## Layers
+
+| Layer | Responsibility | Typical contents |
+| ----- | -------------- | ---------------- |
+| **Ports** (`Nexo.Core.Application.*.Ports`, plus **`Nexo.Infrastructure.Sdk.Ports`** for host SDK surface) | Contracts (`interface`), commands/queries, DTOs | Stable integration surface |
+| **Options** | Immutable-style configuration (`record` / `class` with init-only props) | Bound from DI / env / config sections |
+| **Infrastructure** | Default adapters implementing ports | Swappable in tests or overrides |
+| **Hosting SDK** (`Nexo.Hosting.Sdk`) | Kernel composition for bricks, agents, cards | `AddNexoSdk`, `NexoSdkOptions`, `HostNexoSdkBuilder` |
+| **HTTP client SDK** (`Nexo.Sdk` NuGet, namespace `Nexo.Sdk.Client`) | Slim remote API client (`AddNexoClientSdk`, `NexoClientSdkBuilder`) | Unity / embedded callers |
+
+## Folder conventions (physical)
+
+These conventions usually **preserve** existing namespaces for bulk moves; **new** SDK folders may introduce **`Nexo.Infrastructure.Sdk.*`** namespaces where called out below.
+
+- **`Sdk/Options/`** — option bags and enums tied to registration (`NexoHostingOptions`, deployment profile, host SDK options).
+- **`Sdk/Builders/`** — fluent builders implementing port interfaces (`HostNexoSdkBuilder` implements `INexoSdkBuilder`).
+- **`Sdk/Extensions/`** — `*ServiceCollectionExtensions`, OpenTelemetry hooks, etc.
+- **`Observation/Sdk/Extensions/`** — DI extensions use namespace **`Nexo.Infrastructure.Sdk.Observation`** (`AddObservationCore`, `AddObservationInfrastructure`).
+- **Other feature areas** — same physical layout; namespaces follow **`Nexo.Infrastructure.Sdk.`** unless a **name collision** with runtime types forces **`Nexo.Infrastructure..Sdk`** (see **`NodeCapabilityRuntime`**, **`Execution`**, **`Execution.Routing`**, **`Mesh`**).
+
+### Mechanical sweep (`*ServiceCollectionExtensions`)
+
+Every **`Nexo.Infrastructure`** DI extension file is under **`Feature/Sdk/Extensions/`** (filename pattern **`*ServiceCollectionExtensions*.cs`**). There are no parallel copies under legacy paths.
+
+### Completed areas (DI extensions)
+
+Extension entry points and namespaces (collision-safe variants where noted):
+
+| Feature folder | Extension namespace |
+| -------------- | ------------------- |
+| **Adaptation** (`Adaptation/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.Adaptation` |
+| **Analysis** (`Analysis/BrickAnalyzer/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.Analysis` |
+| **Composition** (`Composition/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.Composition` |
+| **Execution** (`Execution/Sdk/Extensions/`) | `Nexo.Infrastructure.Execution.Sdk` |
+| **Execution/Routing** (`Execution/Routing/Sdk/Extensions/`) | `Nexo.Infrastructure.Execution.Routing.Sdk` |
+| **Maintenance** (`Maintenance/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.Maintenance` |
+| **Mesh** (`Mesh/Sdk/Extensions/`) | `Nexo.Infrastructure.Mesh.Sdk` |
+| **ModelArtifacts** (`ModelArtifacts/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.ModelArtifacts` |
+| **NodeCapabilityRuntime** (`NodeCapabilityRuntime/Sdk/Extensions/`) | `Nexo.Infrastructure.NodeCapabilityRuntime.Sdk` |
+| **Observation** (`Observation/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.Observation` |
+| **ParallelTesting** (`ParallelTesting/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.ParallelTesting` |
+| **Persistence** (`Persistence/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.Persistence` |
+| **Pipelines** (`Pipelines/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.Pipelines` |
+| **Rollback** (`Rollback/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.Rollback` |
+| **SelfContext** (`SelfContext/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.SelfContext` |
+| **SelfImprovement** (`SelfImprovement/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.SelfImprovement` |
+| **Trust** (`Trust/Sdk/Extensions/`) | `Nexo.Infrastructure.Sdk.Trust` |
+
+### Optional `Sdk/Options` pilot
+
+**Pipelines:** **`PipelineExecutionOptions`**, **`PipelinePersistenceOptions`**, **`PipelineExecutionAdapterOptions`** live under **`Pipelines/Sdk/Options/`** with namespaces unchanged (**`Nexo.Infrastructure.Pipelines`**).
+
+### Bringing Sdk extensions into scope
+
+Extension methods require their namespace in scope.
+
+| Approach | When to use |
+| -------- | ----------- |
+| **Link `src/Nexo.Hosting/GlobalUsings.Infrastructure.Sdk.cs`** from your `.csproj` (``) | Projects that **wire `IServiceCollection` manually** and call Infrastructure Sdk extensions (**same pattern as `Nexo.CLI`** and **`Nexo.Tests.Infrastructure`**). Keeps one source of truth when Hosting adds Sdk namespaces. |
+| **Explicit `using Nexo.Infrastructure.Sdk.*`** (or feature-specific Sdk namespaces) | Libraries that **cannot** reference Hosting paths; small surface area. |
+| **Neither** | Apps that only call **`services.AddNexo(...)`** (**e.g. `Nexo.API`**) — kernel registration pulls in dependencies; no Infrastructure Sdk `using` needed for typical **`Program.cs`**. |
+| **Types only** | Projects like **`Nexo.Bricks.Owasp`** that reference Infrastructure for **adapters / types** but not registration extensions — **no** global Sdk usings file. |
+
+**`src/Nexo.Hosting/GlobalUsings.Infrastructure.Sdk.cs`** lists **`global using`** lines for Sdk namespaces used by the host.
+
+## Naming
+
+- **`HostNexoSdkBuilder`** — in-process Nexo kernel registration (before `AddNexo`).
+- **`NexoClientSdkBuilder`** — NuGet client package configuration (`Nexo.Sdk`).
+- Legacy aliases (`[Obsolete]`) remain until the next major bump.
+
+## Composition order
+
+1. Optional: `services.AddNexoSdk(...)` (host SDK — bricks/agents).
+2. `services.AddNexo(...)` (kernel).
+
+Remote-only apps use **`AddNexoClientSdk`** instead of `AddNexo`.
diff --git a/docs/bricks/unknown.md b/docs/bricks/unknown.md
index 57a5a9129..9a0e96d4c 100644
--- a/docs/bricks/unknown.md
+++ b/docs/bricks/unknown.md
@@ -2,18 +2,18 @@
## Behavior
-Adapted from promotion 00ba68f2bf7d453baf4e01540f18688d.
+Adapted from promotion f13a77ebd79a40fa81b5db46c16548b0.
-**Last updated:** 2026-04-16
+**Last updated:** 2026-05-09
## Changelog
```markdown
# Changelog
-## 2026-04-15 – 2026-04-16
+## 2026-05-08 – 2026-05-09
-- **unknown**: Fixed EmptyCatch in EmptyCatch.cs (2026-04-16 01:42)
+- **unknown**: Fixed EmptyCatch in EmptyCatch.cs (2026-05-09 20:57)
```
diff --git a/docs/sdk.md b/docs/sdk.md
index 582812e47..bea572b04 100644
--- a/docs/sdk.md
+++ b/docs/sdk.md
@@ -11,9 +11,10 @@ Use the host surface when embedding Nexo into your own service. Use the client s
### Stable
-- `Nexo.Hosting.Sdk` (`AddNexoSdk(Action)` on `IServiceCollection`)
-- `Nexo.Core.Application.Sdk.Ports.INexoSdkBuilder`
-- `Nexo.Sdk` + `Nexo.Client` (`INexoClient`, `AddNexoSdk(baseUrl, ...)`)
+- `Nexo.Hosting.Sdk` (`AddNexoSdk(Action)` on `IServiceCollection`; builder implementation `HostNexoSdkBuilder`)
+- `Nexo.Infrastructure.Sdk.Ports.INexoSdkBuilder`
+- `Nexo.Sdk.Client` (`AddNexoClientSdk(baseUrl, ...)`, `NexoClientSdkBuilder`) + `Nexo.Client` (`INexoClient`)
+- Obsolete compat: `AddNexoSdk(baseUrl, ...)` / `NexoSdkBuilder` on the client package (same assembly as `Nexo.Sdk`)
### Deprecated
diff --git a/scripts/bootstrap-cloud-mesh-lab.sh b/scripts/bootstrap-cloud-mesh-lab.sh
new file mode 100755
index 000000000..d5addaed6
--- /dev/null
+++ b/scripts/bootstrap-cloud-mesh-lab.sh
@@ -0,0 +1,127 @@
+#!/usr/bin/env bash
+# Bootstrap a Linux host (e.g. cloud VM) for the virtual mesh lab: Docker + Compose,
+# env file, build/up, verify. Run from the Nexo repository root after clone.
+#
+# Usage:
+# chmod +x scripts/bootstrap-cloud-mesh-lab.sh
+# ./scripts/bootstrap-cloud-mesh-lab.sh # create .env.mesh-lab if missing, up + verify
+# ./scripts/bootstrap-cloud-mesh-lab.sh --install-docker # apt-only: install docker.io + compose v2
+#
+# See docs/MeshVirtualLab.md
+
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+cd "${REPO_ROOT}"
+
+INSTALL_DOCKER=false
+SKIP_VERIFY=false
+
+usage() {
+ echo "Usage: $0 [--install-docker] [--skip-verify]"
+ echo " --install-docker On Debian/Ubuntu: apt-get install docker.io docker-compose-v2 (requires sudo)."
+ echo " --skip-verify Only docker compose up (no mesh-lab-verify.sh)."
+}
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --install-docker) INSTALL_DOCKER=true ;;
+ --skip-verify) SKIP_VERIFY=true ;;
+ -h|--help) usage; exit 0 ;;
+ *) echo "Unknown option: $1" >&2; usage; exit 1 ;;
+ esac
+ shift
+done
+
+run_sudo() {
+ if [[ "${EUID}" -eq 0 ]]; then
+ "$@"
+ elif command -v sudo >/dev/null 2>&1; then
+ sudo "$@"
+ else
+ echo "Root or sudo required for: $*" >&2
+ exit 1
+ fi
+}
+
+install_docker_apt() {
+ if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
+ echo "Docker and docker compose already available."
+ return 0
+ fi
+ if ! command -v apt-get >/dev/null 2>&1; then
+ echo "This script only auto-installs Docker on apt-based systems. Install Docker Engine + Compose v2 manually:" >&2
+ echo " https://docs.docker.com/engine/install/" >&2
+ exit 1
+ fi
+ echo "Installing docker.io and docker-compose-v2 (Ubuntu/Debian)..."
+ run_sudo apt-get update
+ run_sudo apt-get install -y docker.io docker-compose-v2
+ if command -v systemctl >/dev/null 2>&1; then
+ run_sudo systemctl enable --now docker || true
+ fi
+}
+
+ensure_compose() {
+ if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
+ return 0
+ fi
+ echo "Docker Compose v2 is required ('docker compose version')." >&2
+ echo " Debian/Ubuntu: sudo apt-get install -y docker.io docker-compose-v2" >&2
+ echo " Or run this script with: --install-docker" >&2
+ exit 1
+}
+
+ensure_env_file() {
+ local example="${REPO_ROOT}/docs/config/mesh-lab.env.example"
+ local target="${REPO_ROOT}/.env.mesh-lab"
+ if [[ -f "${target}" ]]; then
+ echo "Using existing ${target}"
+ return 0
+ fi
+ if [[ ! -f "${example}" ]]; then
+ echo "Missing ${example}" >&2
+ exit 1
+ fi
+ cp "${example}" "${target}"
+ # Non-cryptographic placeholders for a throwaway lab (override for real secrets).
+ local k b w
+ k="$(openssl rand -hex 16 2>/dev/null || printf '%s' "lab-key-change-me")"
+ b="$(openssl rand -hex 16 2>/dev/null || printf '%s' "lab-bearer-change-me")"
+ w="$(openssl rand -hex 12 2>/dev/null || printf '%s' "lab-basic-change-me")"
+ sed -i.bak \
+ -e "s/^Nexo__Security__ApiKey=.*/Nexo__Security__ApiKey=${k}/" \
+ -e "s/^Nexo__Security__PeerB__BearerToken=.*/Nexo__Security__PeerB__BearerToken=${b}/" \
+ -e "s/^Nexo__Security__Worker__BasicAuthPassword=.*/Nexo__Security__Worker__BasicAuthPassword=${w}/" \
+ "${target}" && rm -f "${target}.bak"
+ echo "Created ${target} with random lab secrets (review before production use)."
+}
+
+main() {
+ if [[ "${INSTALL_DOCKER}" == "true" ]]; then
+ install_docker_apt
+ fi
+ ensure_compose
+
+ ensure_env_file
+
+ echo "Building and starting mesh lab (docker-compose.mesh-lab.yml)..."
+ docker compose -f docker-compose.mesh-lab.yml --env-file .env.mesh-lab up -d --build
+
+ if [[ "${SKIP_VERIFY}" == "true" ]]; then
+ echo "Skip verify (--skip-verify). Peers: http://127.0.0.1:18081 and :18082 on this host."
+ exit 0
+ fi
+
+ echo "Running mesh-lab-verify.sh ..."
+ bash "${REPO_ROOT}/scripts/mesh-lab-verify.sh" "${REPO_ROOT}/.env.mesh-lab"
+
+ echo ""
+ echo "Mesh lab is up. On this machine:"
+ echo " peer-a: http://127.0.0.1:18081"
+ echo " peer-b: http://127.0.0.1:18082"
+ echo "SSH tunnel from laptop: ssh -L 18081:127.0.0.1:18081 -L 18082:127.0.0.1:18082 user@vm"
+ echo "Stop: docker compose -f docker-compose.mesh-lab.yml --env-file .env.mesh-lab down -v"
+}
+
+main
diff --git a/scripts/fix-xunit-sync-timeout-tests.py b/scripts/fix-xunit-sync-timeout-tests.py
new file mode 100644
index 000000000..2011ddedd
--- /dev/null
+++ b/scripts/fix-xunit-sync-timeout-tests.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+"""Convert sync [Fact|Theory(Timeout)] tests to async Task + await Task.CompletedTask (xUnit 2.9+)."""
+
+from __future__ import annotations
+
+import re
+import sys
+from pathlib import Path
+
+# After [Fact(Timeout)] / [Theory(Timeout)], the next method may be separated by
+# other attributes (e.g. [InlineData]).
+_METHOD_START = re.compile(
+ r"^(\s*)public\s+(async\s+)?(void|Task)\s+(\w+)\s*\("
+)
+_ATTR_WITH_TIMEOUT = re.compile(r"^\s*\[(Fact|Theory)\(Timeout")
+
+
+def _next_nonempty_line_index(lines: list[str], start: int) -> int | None:
+ j = start
+ while j < len(lines):
+ if lines[j].strip():
+ return j
+ j += 1
+ return None
+
+
+def _find_open_brace_line(lines: list[str], sig_start: int) -> tuple[int, bool]:
+ """Return (line_index_of_brace, brace_only_line). Walks multi-line signatures."""
+ idx = sig_start
+ depth = lines[idx].count("(") - lines[idx].count(")")
+ while idx < len(lines) and depth > 0:
+ idx += 1
+ if idx >= len(lines):
+ break
+ depth += lines[idx].count("(") - lines[idx].count(")")
+ scan_from = idx
+ for k in range(scan_from, min(scan_from + 10, len(lines))):
+ ln = lines[k]
+ if "{" not in ln:
+ continue
+ stripped = ln.strip()
+ if stripped == "{":
+ return k, True
+ if "{" in ln:
+ return k, stripped.endswith("{") and ln.rstrip().endswith("{")
+ return sig_start, False
+
+
+def _insert_after_open_brace(lines: list[str], brace_idx: int, brace_only: bool) -> None:
+ ln = lines[brace_idx]
+ indent_match = re.match(r"^(\s*)", ln)
+ base_indent = indent_match.group(1) if indent_match else ""
+
+ if brace_only and ln.strip() == "{":
+ inner = base_indent + " "
+ insert_at = brace_idx + 1
+ ni = _next_nonempty_line_index(lines, insert_at)
+ if ni is not None:
+ nxs = lines[ni].lstrip()
+ if nxs.startswith(("await ", "return ", "throw ")):
+ return
+ lines.insert(insert_at, f"{inner}await Task.CompletedTask;")
+ return
+
+ # Brace on same line as signature or closing paren
+ pos = ln.find("{")
+ if pos < 0:
+ return
+ after = ln[pos + 1 :]
+ inner = base_indent + " "
+ if after.strip():
+ rest_stripped = after.lstrip()
+ if rest_stripped.startswith(("await ", "return ", "throw ")):
+ return
+ lines[brace_idx] = ln[: pos + 1]
+ lines.insert(brace_idx + 1, f"{inner}await Task.CompletedTask;")
+ if after.strip():
+ lines.insert(brace_idx + 2, after)
+ return
+
+ inner_indent = base_indent + " "
+ insert_at = brace_idx + 1
+ ni = _next_nonempty_line_index(lines, insert_at)
+ if ni is not None:
+ nxs = lines[ni].lstrip()
+ if nxs.startswith(("await ", "return ", "throw ")):
+ return
+ lines.insert(insert_at, f"{inner_indent}await Task.CompletedTask;")
+
+
+def _find_method_after_timeout(lines: list[str], timeout_attr_idx: int) -> int | None:
+ """First line that starts a public instance method (void or Task). Skips attributes."""
+ j = timeout_attr_idx + 1
+ while j < len(lines):
+ raw = lines[j]
+ stripped = raw.strip()
+ if stripped.startswith("public ") and "(" in raw:
+ return j
+ # Skip attributes and trivia (comments only — careful: block comments rare)
+ if stripped.startswith("//") or stripped.startswith("#"):
+ j += 1
+ continue
+ if stripped.startswith("["):
+ j += 1
+ continue
+ if not stripped:
+ j += 1
+ continue
+ # Hit something else (e.g. closing brace of previous method) — stop
+ break
+ return None
+
+
+def process_file(text: str) -> tuple[str, int]:
+ lines = text.split("\n")
+ converted = 0
+ i = 0
+ while i < len(lines):
+ line = lines[i]
+ if not _ATTR_WITH_TIMEOUT.match(line):
+ i += 1
+ continue
+
+ attr_idx = i
+ meth_idx = _find_method_after_timeout(lines, attr_idx)
+ if meth_idx is None:
+ i += 1
+ continue
+
+ sig_line = lines[meth_idx]
+ m = _METHOD_START.match(sig_line)
+ if not m:
+ i += 1
+ continue
+
+ if m.group(2): # async
+ i += 1
+ continue
+
+ ret = m.group(3)
+ if ret != "void":
+ i += 1
+ continue
+
+ lines[meth_idx] = sig_line.replace("public void ", "public async Task ", 1)
+ converted += 1
+
+ brace_idx, brace_only = _find_open_brace_line(lines, meth_idx)
+ _insert_after_open_brace(lines, brace_idx, brace_only)
+
+ i = attr_idx + 1
+
+ return "\n".join(lines), converted
+
+
+def main() -> int:
+ root = Path(__file__).resolve().parents[1] / "src"
+ if not root.is_dir():
+ print(f"Missing {root}", file=sys.stderr)
+ return 1
+ total_files = 0
+ total_methods = 0
+ for path in sorted(root.rglob("*.cs")):
+ raw = path.read_text(encoding="utf-8")
+ if "public void" not in raw:
+ continue
+ if "[Fact(Timeout" not in raw and "[Theory(Timeout" not in raw:
+ continue
+ fixed, n = process_file(raw)
+ if n == 0:
+ continue
+ path.write_text(fixed, encoding="utf-8")
+ total_files += 1
+ total_methods += n
+ print(f"{path.relative_to(root.parents[1])}: {n} method(s)")
+ print(f"Files updated: {total_files}; methods converted: {total_methods}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/src/Nexo.BackgroundAgents/ServiceCollectionExtensions.cs b/src/Nexo.BackgroundAgents/ServiceCollectionExtensions.cs
index e7748df55..0dc7fb54d 100644
--- a/src/Nexo.BackgroundAgents/ServiceCollectionExtensions.cs
+++ b/src/Nexo.BackgroundAgents/ServiceCollectionExtensions.cs
@@ -23,6 +23,7 @@
using Nexo.Core.Application.Trust.Ports;
using Nexo.Infrastructure.Observation;
using Nexo.Infrastructure.Trust;
+using Nexo.Infrastructure.Sdk.Trust;
using Nexo.Orchestration.Agents;
namespace Nexo.BackgroundAgents;
diff --git a/src/Nexo.Framework.Sdk/Nexo.Framework.Sdk.csproj b/src/Nexo.Framework.Sdk/Nexo.Framework.Sdk.csproj
new file mode 100644
index 000000000..c44b65114
--- /dev/null
+++ b/src/Nexo.Framework.Sdk/Nexo.Framework.Sdk.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ 12.0
+ enable
+ enable
+ Nexo.Framework.Sdk
+ Optional megapackage-style composition: HTTP client + Nexo kernel registration entry points.
+ false
+
+
+
+
+
+
+
+
diff --git a/src/Nexo.Framework.Sdk/NexoFrameworkServiceCollectionExtensions.cs b/src/Nexo.Framework.Sdk/NexoFrameworkServiceCollectionExtensions.cs
new file mode 100644
index 000000000..3da92aac4
--- /dev/null
+++ b/src/Nexo.Framework.Sdk/NexoFrameworkServiceCollectionExtensions.cs
@@ -0,0 +1,47 @@
+using Microsoft.Extensions.DependencyInjection;
+using Nexo.Hosting;
+
+namespace Nexo.Framework.Sdk;
+
+///
+/// Single visible option bag for apps that want client + host wiring without importing multiple extension namespaces.
+///
+public sealed class NexoFrameworkOptions
+{
+ ///
+ /// When set, registers AddNexoClientSdk from Nexo.Sdk.Client.
+ /// Remote-only apps can skip .
+ ///
+ public string? RemoteApiBaseUrl { get; set; }
+
+ /// Forwarded to Hosting AddNexo.
+ public Action? ConfigureHost { get; set; }
+
+ /// When true (default), registers the full Nexo kernel via AddNexo.
+ public bool RegisterKernel { get; set; } = true;
+}
+
+///
+/// Aggregates stable Nexo integration extension methods for hybrid or quick bootstrap scenarios.
+///
+public static class NexoFrameworkServiceCollectionExtensions
+{
+ ///
+ /// Optionally registers the HTTP Nexo client and/or the in-process kernel.
+ ///
+ public static IServiceCollection AddNexoFramework(
+ this IServiceCollection services,
+ Action? configure = null)
+ {
+ var opt = new NexoFrameworkOptions();
+ configure?.Invoke(opt);
+
+ if (!string.IsNullOrWhiteSpace(opt.RemoteApiBaseUrl))
+ Nexo.Sdk.Client.NexoClientSdkServiceCollectionExtensions.AddNexoClientSdk(services, opt.RemoteApiBaseUrl);
+
+ if (opt.RegisterKernel)
+ Nexo.Hosting.NexoServiceCollectionExtensions.AddNexo(services, opt.ConfigureHost);
+
+ return services;
+ }
+}
diff --git a/src/Nexo.Hosting/GlobalUsings.Infrastructure.Sdk.cs b/src/Nexo.Hosting/GlobalUsings.Infrastructure.Sdk.cs
new file mode 100644
index 000000000..3c5777955
--- /dev/null
+++ b/src/Nexo.Hosting/GlobalUsings.Infrastructure.Sdk.cs
@@ -0,0 +1,17 @@
+// Infrastructure DI extension surface area (Sdk namespaces).
+global using Nexo.Infrastructure.Execution.Routing.Sdk;
+global using Nexo.Infrastructure.Execution.Sdk;
+global using Nexo.Infrastructure.Mesh.Sdk;
+global using Nexo.Infrastructure.NodeCapabilityRuntime.Sdk;
+global using Nexo.Infrastructure.Sdk.Adaptation;
+global using Nexo.Infrastructure.Sdk.Analysis;
+global using Nexo.Infrastructure.Sdk.Composition;
+global using Nexo.Infrastructure.Sdk.Maintenance;
+global using Nexo.Infrastructure.Sdk.ModelArtifacts;
+global using Nexo.Infrastructure.Sdk.Observation;
+global using Nexo.Infrastructure.Sdk.ParallelTesting;
+global using Nexo.Infrastructure.Sdk.Persistence;
+global using Nexo.Infrastructure.Sdk.Pipelines;
+global using Nexo.Infrastructure.Sdk.SelfContext;
+global using Nexo.Infrastructure.Sdk.SelfImprovement;
+global using Nexo.Infrastructure.Sdk.Trust;
diff --git a/src/Nexo.Hosting/NexoKernelRegistrar.Ephemeral.cs b/src/Nexo.Hosting/NexoKernelRegistrar.Ephemeral.cs
new file mode 100644
index 000000000..1d3e3f65e
--- /dev/null
+++ b/src/Nexo.Hosting/NexoKernelRegistrar.Ephemeral.cs
@@ -0,0 +1,13 @@
+namespace Nexo.Hosting;
+
+internal static partial class NexoKernelRegistrar
+{
+ ///
+ /// Mirrors NEXO_EPHEMERAL / NEXO_EPHEMERAL_MODELS handling used by ephemeral lifecycle and trust wiring.
+ ///
+ private static bool EphemeralModelsEnabled()
+ {
+ var ephemeralAll = string.Equals(Environment.GetEnvironmentVariable("NEXO_EPHEMERAL"), "1", StringComparison.OrdinalIgnoreCase);
+ return ephemeralAll || string.Equals(Environment.GetEnvironmentVariable("NEXO_EPHEMERAL_MODELS"), "1", StringComparison.OrdinalIgnoreCase);
+ }
+}
diff --git a/src/Nexo.Hosting/NexoKernelRegistrar.Phases.cs b/src/Nexo.Hosting/NexoKernelRegistrar.Phases.cs
new file mode 100644
index 000000000..cabb085ef
--- /dev/null
+++ b/src/Nexo.Hosting/NexoKernelRegistrar.Phases.cs
@@ -0,0 +1,673 @@
+using FluentValidation;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Http;
+using Nexo.Abstractions.Routing;
+using Nexo.Abstractions.Transport;
+using Nexo.BackgroundAgents;
+using Nexo.BackgroundAgents.Trust;
+using Nexo.Core.Application.Adaptation.Ports;
+using Nexo.Core.Application.Analysis.UseCases.AnalyzeCode;
+using Nexo.Core.Application.Common.Ports;
+using Nexo.Core.Application.Common.Services;
+using Nexo.Core.Application.Copilot.Ports;
+using Nexo.Core.Application.Ephemeral.Ports;
+using Nexo.Core.Application.Knowledge.Ports;
+using Nexo.Core.Application.Observation.Ports;
+using Nexo.Core.Application.Paths;
+using Nexo.Core.Application.Testing.UseCases.RunTests;
+using Nexo.Core.Application.Trust.Ports;
+using Nexo.Core.Application.Validation.UseCases.RunValidation;
+using Nexo.Contracts;
+using Nexo.Infrastructure;
+using Nexo.Infrastructure.Environments;
+using Nexo.Infrastructure.Fleet;
+using Nexo.Infrastructure.Copilot;
+using Nexo.Infrastructure.Execution;
+using Nexo.Infrastructure.Execution.Ephemeral;
+using Nexo.Infrastructure.Execution.LoadPolicy;
+using Nexo.Infrastructure.Execution.Routing;
+using Nexo.Infrastructure.Knowledge;
+using Nexo.Infrastructure.Maintenance;
+using Nexo.Infrastructure.ModelArtifacts;
+using Nexo.Infrastructure.NodeCapabilityRuntime;
+using Nexo.Infrastructure.Persistence;
+using Nexo.Infrastructure.Persistence.Ephemeral;
+using Nexo.Infrastructure.Pipelines;
+using Nexo.Orchestration;
+using Nexo.Orchestration.Models;
+using Nexo.Orchestration.Transport;
+using Nexo.Runtime;
+using Nexo.Runtime.Routing;
+using Nexo.Transport.Grpc;
+
+namespace Nexo.Hosting;
+
+internal static partial class NexoKernelRegistrar
+{
+ private static void RegisterPhase01_ConfigurationNodeCapabilityRuntime(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Configuration & Node Capability Runtime ────────────────────
+ // Environment variables are the primary config source; appsettings
+ // is intentionally NOT loaded here so that containerised deployments
+ // stay 12-factor compliant. RemoteCapabilitiesOptions binds from
+ // the "Nexo:RemoteCapabilities" section for RunPod/cloud routing.
+ services.AddOptions()
+ .Bind(configuration.GetSection("Nexo:RemoteCapabilities"));
+ if (modules.IncludeNodeCapabilityRuntime)
+ {
+ services.AddRunPodCapabilityRouting(configuration);
+ NexoServiceCollectionExtensions.RegisterNodeCapabilityRuntime(services, configuration);
+ }
+
+ }
+
+ private static void RegisterPhase02_CQRSMediatRFluentValidation(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── CQRS (MediatR) & FluentValidation ─────────────────────────
+ // MediatR handlers from both the Analysis and Testing assemblies
+ // are registered in one pass. The ValidationBehavior pipeline
+ // behavior runs FluentValidation before each handler, so
+ // validators must also be registered here.
+ services.AddMediatR(cfg =>
+ {
+ cfg.RegisterServicesFromAssembly(typeof(AnalyzeCodeCommand).Assembly);
+ cfg.RegisterServicesFromAssembly(typeof(RunTestsCommand).Assembly);
+ });
+
+ services.TryAddSingleton();
+
+ services.AddValidatorsFromAssembly(typeof(AnalyzeCodeValidator).Assembly);
+ services.AddTransient(typeof(MediatR.IPipelineBehavior<,>), typeof(Nexo.Core.Application.Behaviors.IngressLoggingPipelineBehavior<,>));
+ services.AddTransient(typeof(MediatR.IPipelineBehavior<,>), typeof(Nexo.Core.Application.Behaviors.ValidationBehavior<,>));
+ services.TryAddSingleton();
+
+ }
+
+ private static void RegisterPhase03_ConfigurationServiceAdapter(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Configuration service adapter ──────────────────────────────
+ // Bridges the domain-level IConfigurationService port to the
+ // infrastructure adapter. Strict mode controls whether config
+ // warnings escalate to hard failures (useful in CI pipelines).
+ services.AddSingleton(sp =>
+ {
+ var logger = sp.GetRequiredService>();
+ var strictMode = sp.GetService();
+ return new Nexo.Infrastructure.Configuration.ConfigurationServiceAdapter(logger, strictMode?.ShouldFailOnConfigurationWarnings ?? false);
+ });
+
+ }
+
+ private static void RegisterPhase04_LoopKernelDecoratorChain(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Loop kernel (decorator chain) ──────────────────────────────
+ // The loop kernel runs brick-level iterations. It is composed via
+ // the decorator pattern:
+ // SequentialLoopKernel (always present — baseline)
+ // → ParallelLoopKernel (if NEXO_LOOP_PARALLEL=1)
+ // → InstrumentedLoopKernel (if NEXO_LOOP_INSTRUMENT=1)
+ //
+ // NEXO_LOOP_PARALLEL ("1"): wraps in a parallelising decorator for
+ // concurrent brick evaluation; useful on multi-core servers.
+ // NEXO_LOOP_INSTRUMENT ("1"): adds timing/counter telemetry around
+ // each loop iteration; adds overhead, meant for dev profiling.
+ services.AddSingleton(sp =>
+ {
+ ILoopKernel k = new SequentialLoopKernel();
+ var enableParallel = string.Equals(Environment.GetEnvironmentVariable("NEXO_LOOP_PARALLEL"), "1", StringComparison.OrdinalIgnoreCase);
+ if (enableParallel)
+ {
+ k = new ParallelLoopKernel(k);
+ }
+
+ var instrument = string.Equals(Environment.GetEnvironmentVariable("NEXO_LOOP_INSTRUMENT"), "1", StringComparison.OrdinalIgnoreCase);
+ if (instrument)
+ {
+ k = new InstrumentedLoopKernel(k, sp.GetRequiredService>());
+ }
+
+ return k;
+ });
+
+ }
+
+ private static void RegisterPhase05_OrchestrationTransport(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Orchestration & transport ──────────────────────────────────
+ // Orchestration is always registered (it owns the runtime spec
+ // accessor used by the model decorator chain). Transport is
+ // optional: when present it registers gRPC channels plus the
+ // dual in-process / gRPC agent transport pair used for peer
+ // communication. See Nexo.Transport.Grpc for channel config.
+ services.AddNexoOrchestration();
+ if (modules.IncludeRuntimeTransport)
+ {
+ services.AddOptions();
+ services.AddOptions();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.AddNexoRuntimeTransport();
+ }
+
+ }
+
+ private static void RegisterPhase06_Persistence(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Persistence ────────────────────────────────────────────────
+ if (modules.IncludePersistence)
+ {
+ services.AddNexoPersistence();
+ services.AddPostgresIsolatedDatabaseProvisioner();
+ }
+
+ }
+
+ private static void RegisterPhase07_Adaptation(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Adaptation ─────────────────────────────────────────────────
+ // Pattern store path is forwarded so the adaptation layer knows
+ // where to persist learned patterns on disk.
+ if (modules.IncludeAdaptation)
+ {
+ services.AddAdaptationInfrastructure(options.PatternStorePath);
+ }
+
+ if (modules.IncludeAdaptation)
+ {
+ services.AddNexoFederatedBrickMesh(configuration);
+ }
+
+ }
+
+ private static void RegisterPhase08_CopilotTaskStore(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Copilot task store ──────────────────────────────────────────
+ // LiteDB file is co-located with the pattern store directory
+ // (or the repo root as fallback) to keep all Nexo-generated
+ // state in one discoverable location.
+ var copilotTasksBasePath = !string.IsNullOrEmpty(options.PatternStorePath)
+ ? Path.GetDirectoryName(options.PatternStorePath) ?? "."
+ : RepoPathResolver.FindRepoRoot();
+ var copilotTasksDbPath = Path.Combine(copilotTasksBasePath, "nexo-copilot-tasks.db");
+ services.TryAddSingleton(_ => new LiteDbCopilotTaskStore(copilotTasksDbPath));
+
+ }
+
+ private static void RegisterPhase09_KnowledgeQueryService(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Knowledge query service ────────────────────────────────────
+ // Aggregates adaptation logs, pattern store, and (optionally)
+ // user-knowledge logs into a single query façade. Falls back to
+ // an in-memory knowledge log when the trust module is absent.
+ services.TryAddSingleton(sp =>
+ {
+ var adaptationLog = sp.GetRequiredService();
+ var patternStore = sp.GetRequiredService();
+ var userKnowledgeStore = sp.GetService()
+ ?? new Nexo.Infrastructure.Trust.InMemoryUserKnowledgeLogStore();
+ return new KnowledgeQueryService(adaptationLog, patternStore, userKnowledgeStore);
+ });
+
+ }
+
+ private static void RegisterPhase10_PipelineComposition(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Pipeline composition ───────────────────────────────────────
+ if (modules.IncludePipelineComposition)
+ {
+ services.AddPipelineCompositionLayer();
+ }
+
+ }
+
+ private static void RegisterPhase11_BackgroundAgentsRAG(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Background agents & RAG ────────────────────────────────────
+ if (modules.IncludeBackgroundAgents)
+ {
+ services.AddBackgroundAgents(registerHostedService: options.RegisterBackgroundAgentHostedService);
+ }
+
+ if (modules.IncludeBackgroundAgentRag)
+ {
+ services.AddBackgroundAgentsRAG();
+ }
+
+ }
+
+ private static void RegisterPhase12_ObservationPipeline(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Observation pipeline ───────────────────────────────────────
+ // Captures runtime telemetry and persists it alongside patterns.
+ // NEXO_OBSERVATION_FAIL_OPEN ("1" / "true"): when set, store I/O
+ // errors are swallowed instead of failing the pipeline — safe
+ // for edge nodes with unreliable storage.
+ if (modules.IncludeObservationPipeline && !options.DisableObservationPipeline)
+ {
+ var repoRoot = RepoPathResolver.FindRepoRoot();
+ var observationFailOpen = options.ObservationFailOpen ?? NexoServiceCollectionExtensions.ParseBooleanEnvironmentVariable("NEXO_OBSERVATION_FAIL_OPEN");
+ services.AddObservationPipeline(opts =>
+ {
+ opts.RepoRoot = repoRoot;
+ opts.StorePath = options.PatternStorePath ?? "nexo-patterns.db";
+ opts.FailOpenOnStoreErrors = observationFailOpen;
+ }, registerHostedService: options.RegisterBackgroundAgentHostedService);
+ }
+
+ // Mock web-search provider is registered as a fallback so
+ // background agents can be instantiated even when no real
+ // provider is configured.
+ if (modules.IncludeBackgroundAgents)
+ {
+ services.TryAddSingleton();
+ }
+
+ }
+
+ private static void RegisterPhase13_ModelDecoratorChain(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Model decorator chain ──────────────────────────────────────
+ // The IModel abstraction is built as a three-layer decorator:
+ //
+ // 1. ProviderBackedModel – delegates to IProviderFactory
+ // 2. HotSwappableModel – allows runtime model switching
+ // without restarting the host
+ // 3. OrchestrationRuntimeModelDecorator
+ // – injects orchestration-level
+ // spec overrides (temperature,
+ // token limits, etc.) per-call
+ //
+ // HotSwappableModel is registered as a concrete singleton so that
+ // administrative endpoints can resolve it directly for hot-swap
+ // operations, while IModel always returns the fully decorated chain.
+ services.AddSingleton(sp =>
+ {
+ var providerFactory = sp.GetRequiredService();
+ var providerBacked = new Nexo.Infrastructure.Execution.Models.ProviderBackedModel(
+ providerFactory,
+ sp.GetRequiredService>());
+ return new Nexo.Infrastructure.Execution.Models.HotSwappableModel(
+ providerBacked,
+ sp.GetRequiredService>());
+ });
+
+ services.AddSingleton(sp =>
+ {
+ var accessor = sp.GetRequiredService();
+ var inner = sp.GetRequiredService();
+ return new Nexo.Orchestration.Models.OrchestrationRuntimeModelDecorator(
+ inner,
+ accessor,
+ sp.GetRequiredService>());
+ });
+
+ }
+
+ private static void RegisterPhase14_EphemeralLifecycle(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Ephemeral lifecycle ────────────────────────────────────────
+ // "Ephemeral" means Nexo can spin up and tear down backing
+ // resources on demand (Ollama models, Postgres databases).
+ //
+ // NEXO_EPHEMERAL ("1"): master switch — enables ALL ephemeral
+ // subsystems.
+ // NEXO_EPHEMERAL_MODELS ("1"): enables only ephemeral model
+ // lifecycle (Ollama pull/remove) without affecting databases.
+ // NEXO_EPHEMERAL_DB ("postgres"): enables ephemeral Postgres
+ // database creation; only takes effect when persistence is on.
+ if (EphemeralModelsEnabled())
+ {
+ services.AddSingleton();
+ }
+
+ var ephemeralDb = Environment.GetEnvironmentVariable("NEXO_EPHEMERAL_DB")?.Trim();
+ if (modules.IncludePersistence && string.Equals(ephemeralDb, "postgres", StringComparison.OrdinalIgnoreCase))
+ {
+ services.AddSingleton();
+ }
+
+ }
+
+ private static void RegisterPhase15_TrustProviderFactory3wayBranching(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Trust & provider factory (3-way branching) ─────────────────
+ // The provider factory is the gateway through which every LLM
+ // call flows. Three mutually-exclusive wiring paths exist:
+ //
+ // Path A — Adaptive load-balancing (NEXO_LOAD_PREFERENCE set):
+ // ProviderFactory → (optional SanitizingProviderFactory if
+ // trust is on) → AdaptiveProviderFactory.
+ // Load policy is driven by NEXO_LOAD_PREFERENCE value.
+ //
+ // Path B — Trust without adaptive (NEXO_TRUST_ENABLED=1,
+ // no load pref):
+ // Trust module registers its own SanitizingProviderFactory
+ // via AddTrustServices (skipProviderRegistration: false).
+ //
+ // Path C — Plain (neither trust nor adaptive):
+ // Bare ProviderFactory is registered directly.
+ //
+ // NEXO_TRUST_ENABLED ("1"): activates the sanitization proxy
+ // that scrubs PII before LLM calls leave the trust boundary.
+ // NEXO_LOAD_PREFERENCE (string, e.g. "latency" / "cost"):
+ // activates adaptive load balancing and selects the policy.
+ var ephemeralModels = EphemeralModelsEnabled();
+ var trustEnabledByConfig = options.TrustEnabled ?? string.Equals(Environment.GetEnvironmentVariable("NEXO_TRUST_ENABLED"), "1", StringComparison.OrdinalIgnoreCase);
+ var trustEnabled = modules.IncludeTrustServices && trustEnabledByConfig;
+ var loadPref = Environment.GetEnvironmentVariable("NEXO_LOAD_PREFERENCE")?.Trim();
+ var useAdaptive = options.UseAdaptiveLoadBalancing ?? !string.IsNullOrEmpty(loadPref);
+
+ if (modules.IncludeTrustServices)
+ {
+ services.AddTrustServices(useSanitizingProviderFactory: trustEnabled, ephemeralLifecycle: ephemeralModels, skipProviderRegistration: useAdaptive);
+ }
+
+ // Path A: adaptive load-balancing wraps everything
+ if (useAdaptive)
+ {
+ services.AddSingleton(sp =>
+ {
+ var logger = sp.GetRequiredService>();
+ var lifecycle = sp.GetService();
+ return new ProviderFactory(logger, lifecycle);
+ });
+ services.TryAddSingleton();
+ services.AddSingleton(sp =>
+ {
+ var pf = sp.GetRequiredService();
+ Nexo.Infrastructure.Execution.IProviderFactory inner = trustEnabled
+ ? new SanitizingProviderFactory(pf, sp.GetRequiredService(),
+ sp.GetRequiredService>())
+ : pf;
+ var policy = sp.GetRequiredService();
+ var logger = sp.GetService>();
+ return new AdaptiveProviderFactory(inner, policy, logger);
+ });
+ }
+ // Path C: plain provider (Path B is handled inside AddTrustServices)
+ else if (!trustEnabled)
+ {
+ services.AddSingleton(sp =>
+ {
+ var logger = sp.GetRequiredService>();
+ var lifecycle = sp.GetService();
+ return new ProviderFactory(logger, lifecycle);
+ });
+ }
+
+ }
+
+ private static void RegisterPhase16_ExecutionCoreWorkflow(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Execution core & workflow ──────────────────────────────────
+ services.AddSingleton();
+ services.AddMapDataProviderRouting();
+
+ // Workflow integrations (PDF export, webhooks, DB read/write,
+ // cluster store) are only available in Full/Server profiles.
+ // WorkflowExecutor resolves them as optional dependencies so it
+ // can still execute pure in-memory workflows without them.
+ if (modules.IncludeWorkflowIntegrations)
+ {
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ }
+
+ // Semantic cache, behavior registry, step mode, and behavior
+ // executor form the brick execution pipeline. TryAddSingleton
+ // is used so that test hosts or SDK consumers can substitute
+ // any of these before calling AddNexo.
+ services.TryAddSingleton(sp =>
+ new Nexo.Infrastructure.Execution.SemanticCache(sp.GetRequiredService>()));
+ services.TryAddSingleton(_ =>
+ new Nexo.Infrastructure.Execution.BehaviorRegistry(Array.Empty()));
+ services.TryAddSingleton(sp =>
+ new Nexo.Infrastructure.Execution.StepExecutionModeStore(
+ null,
+ sp.GetService>()));
+ services.TryAddSingleton(sp =>
+ new Nexo.Infrastructure.Execution.BehaviorExecutor(
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService>(),
+ sp.GetService(),
+ sp.GetService(),
+ sp.GetService()));
+ // Agent registry is populated from SDK-provided AgentCards; if
+ // none are supplied the registry starts empty and agents can be
+ // registered later at runtime.
+ services.TryAddSingleton(sp =>
+ {
+ var sdkOptions = sp.GetService();
+ var cards = sdkOptions?.AgentCards?.ToList() ?? new List();
+ return new Nexo.Infrastructure.Execution.AgentRegistry(cards);
+ });
+
+ }
+
+ private static void RegisterPhase17_WorkflowExecutor(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Workflow executor ──────────────────────────────────────────
+ // Scoped because a single workflow execution may accumulate
+ // state (e.g. cluster affinity) that should not leak across
+ // independent request scopes.
+ services.AddScoped(sp =>
+ new Nexo.Core.Application.Workflows.WorkflowExecutor(
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService>(),
+ pdfExporter: sp.GetService(),
+ webhookClient: sp.GetService(),
+ databaseReader: sp.GetService(),
+ databaseWriter: sp.GetService(),
+ clusterStore: sp.GetService()));
+
+ services.AddScoped();
+ services.AddScoped();
+
+ }
+
+ private static void RegisterPhase18_AnalysisValidation(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Analysis & validation ──────────────────────────────────────
+ // Both services use a caching decorator (CachedAnalysis/
+ // CachedValidation) to avoid re-running expensive analysis
+ // or parsing when the same input appears within a scope.
+ services.AddScoped(sp =>
+ {
+ var inner = new Nexo.Infrastructure.Analysis.Adapters.AnalysisServiceAdapter(
+ sp.GetRequiredService>(),
+ sp.GetRequiredService());
+ var cache = sp.GetRequiredService();
+ var logger = sp.GetRequiredService>();
+ return new Nexo.Infrastructure.Analysis.Adapters.CachedAnalysisServiceAdapter(inner, cache, logger);
+ });
+
+ services.AddScoped(sp =>
+ {
+ var inner = new Nexo.Infrastructure.Validation.Adapters.ValidationServiceAdapter(
+ sp.GetRequiredService>(),
+ sp.GetRequiredService());
+ var cache = sp.GetRequiredService();
+ var logger = sp.GetRequiredService>();
+ return new Nexo.Infrastructure.Validation.Adapters.CachedValidationServiceAdapter(inner, cache, logger);
+ });
+
+ services.AddSingleton();
+ services.AddSingleton();
+ }
+
+ private static void RegisterPhase19_TestingAdapters(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Testing adapters ───────────────────────────────────────────
+ services.AddScoped();
+
+ // NEXO_EXECUTION_REMOTE_URL (URL string): when set, test
+ // execution is delegated to a remote execution service via
+ // HTTP instead of the local Docker-based platform. Useful in
+ // CI environments where Docker-in-Docker is unavailable.
+ if (modules.IncludeTestingAdapters)
+ {
+ var executionRemoteUrl = options.ExecutionRemoteUrl ?? Environment.GetEnvironmentVariable("NEXO_EXECUTION_REMOTE_URL")?.Trim();
+ if (!string.IsNullOrEmpty(executionRemoteUrl))
+ {
+ var baseUrl = executionRemoteUrl.TrimEnd('/') + "/";
+ services.AddHttpClient("NexoExecution", c => c.BaseAddress = new Uri(baseUrl));
+ services.AddSingleton(sp =>
+ {
+ var factory = sp.GetRequiredService();
+ var client = factory.CreateClient("NexoExecution");
+ var logger = sp.GetService>();
+ return new Nexo.Infrastructure.Testing.ExecutionPlatform.RemoteExecutionPlatform(client, logger);
+ });
+ }
+ else
+ {
+ services.AddSingleton(sp =>
+ new Nexo.Infrastructure.Testing.ExecutionPlatform.DockerExecutionPlatform(sp.GetRequiredService>()));
+ }
+
+ services.AddSingleton(sp =>
+ new Nexo.Infrastructure.Testing.Docker.DockerService(sp.GetRequiredService>()));
+ services.AddSingleton(sp =>
+ new Nexo.Infrastructure.Testing.CodeAnalysis.RoslynCodeAnalysisService(sp.GetRequiredService>()));
+ services.AddArtifactCleanup();
+ }
+
+ }
+
+ private static void RegisterPhase20_AnalysisRuleEngine(NexoKernelRegistrationContext ctx)
+ {
+ var services = ctx.Services;
+ var options = ctx.Options;
+ var modules = ctx.Modules;
+ var configuration = ctx.Configuration;
+
+ // ── Analysis rule engine ───────────────────────────────────────
+ // Rules are collected via DI multi-registration and fed into
+ // the engine. Add new IAnalysisRule implementations to extend
+ // the static analysis suite without touching this file.
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped(sp =>
+ {
+ var rules = sp.GetServices();
+ var logger = sp.GetRequiredService>();
+ return new Nexo.Infrastructure.Analysis.Rules.AnalysisRuleEngine(rules, logger);
+ });
+
+ services.AddNexoFleetDirector();
+ services.AddNexoMeshElasticScheduling(configuration);
+ services.AddNexoMeshCheckpointScheduling(configuration);
+
+ }
+
+}
diff --git a/src/Nexo.Hosting/NexoKernelRegistrar.cs b/src/Nexo.Hosting/NexoKernelRegistrar.cs
new file mode 100644
index 000000000..de0f52b6c
--- /dev/null
+++ b/src/Nexo.Hosting/NexoKernelRegistrar.cs
@@ -0,0 +1,75 @@
+using FluentValidation;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Http;
+using Nexo.Abstractions.Routing;
+using Nexo.Abstractions.Transport;
+using Nexo.BackgroundAgents;
+using Nexo.BackgroundAgents.Trust;
+using Nexo.Core.Application.Adaptation.Ports;
+using Nexo.Core.Application.Analysis.UseCases.AnalyzeCode;
+using Nexo.Core.Application.Common.Ports;
+using Nexo.Core.Application.Common.Services;
+using Nexo.Core.Application.Copilot.Ports;
+using Nexo.Core.Application.Ephemeral.Ports;
+using Nexo.Core.Application.Knowledge.Ports;
+using Nexo.Core.Application.Observation.Ports;
+using Nexo.Core.Application.Paths;
+using Nexo.Core.Application.Testing.UseCases.RunTests;
+using Nexo.Core.Application.Trust.Ports;
+using Nexo.Core.Application.Validation.UseCases.RunValidation;
+using Nexo.Infrastructure;
+using Nexo.Infrastructure.Copilot;
+using Nexo.Infrastructure.Execution;
+using Nexo.Infrastructure.Execution.Ephemeral;
+using Nexo.Infrastructure.Execution.LoadPolicy;
+using Nexo.Infrastructure.Execution.Routing;
+using Nexo.Infrastructure.Knowledge;
+using Nexo.Infrastructure.Maintenance;
+using Nexo.Infrastructure.ModelArtifacts;
+using Nexo.Infrastructure.NodeCapabilityRuntime;
+using Nexo.Infrastructure.Persistence;
+using Nexo.Infrastructure.Persistence.Ephemeral;
+using Nexo.Infrastructure.Pipelines;
+using Nexo.Orchestration;
+using Nexo.Orchestration.Models;
+using Nexo.Orchestration.Transport;
+using Nexo.Runtime;
+using Nexo.Runtime.Routing;
+using Nexo.Transport.Grpc;
+
+namespace Nexo.Hosting;
+
+/// Extracted kernel DI phases from . Registration order is preserved.
+internal static partial class NexoKernelRegistrar
+{
+ public static void Register(
+ IServiceCollection services,
+ NexoHostingOptions options,
+ ModuleSelection modules,
+ IConfiguration configuration)
+ {
+ var ctx = new NexoKernelRegistrationContext(services, options, modules, configuration);
+ RegisterPhase01_ConfigurationNodeCapabilityRuntime(ctx);
+ RegisterPhase02_CQRSMediatRFluentValidation(ctx);
+ RegisterPhase03_ConfigurationServiceAdapter(ctx);
+ RegisterPhase04_LoopKernelDecoratorChain(ctx);
+ RegisterPhase05_OrchestrationTransport(ctx);
+ RegisterPhase06_Persistence(ctx);
+ RegisterPhase07_Adaptation(ctx);
+ RegisterPhase08_CopilotTaskStore(ctx);
+ RegisterPhase09_KnowledgeQueryService(ctx);
+ RegisterPhase10_PipelineComposition(ctx);
+ RegisterPhase11_BackgroundAgentsRAG(ctx);
+ RegisterPhase12_ObservationPipeline(ctx);
+ RegisterPhase13_ModelDecoratorChain(ctx);
+ RegisterPhase14_EphemeralLifecycle(ctx);
+ RegisterPhase15_TrustProviderFactory3wayBranching(ctx);
+ RegisterPhase16_ExecutionCoreWorkflow(ctx);
+ RegisterPhase17_WorkflowExecutor(ctx);
+ RegisterPhase18_AnalysisValidation(ctx);
+ RegisterPhase19_TestingAdapters(ctx);
+ RegisterPhase20_AnalysisRuleEngine(ctx);
+ }
+}
diff --git a/src/Nexo.Hosting/NexoKernelRegistrationModels.cs b/src/Nexo.Hosting/NexoKernelRegistrationModels.cs
new file mode 100644
index 000000000..a93f3cc68
--- /dev/null
+++ b/src/Nexo.Hosting/NexoKernelRegistrationModels.cs
@@ -0,0 +1,28 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Nexo.Hosting;
+
+///
+/// Flags produced by that decide which subsystem
+/// modules are registered.
+///
+internal sealed record ModuleSelection(
+ bool IncludeNodeCapabilityRuntime,
+ bool IncludeRuntimeTransport,
+ bool IncludePersistence,
+ bool IncludeAdaptation,
+ bool IncludePipelineComposition,
+ bool IncludeBackgroundAgents,
+ bool IncludeBackgroundAgentRag,
+ bool IncludeObservationPipeline,
+ bool IncludeTrustServices,
+ bool IncludeWorkflowIntegrations,
+ bool IncludeTestingAdapters);
+
+/// Tuple-style context passed sequentially through phase methods.
+internal readonly record struct NexoKernelRegistrationContext(
+ IServiceCollection Services,
+ NexoHostingOptions Options,
+ ModuleSelection Modules,
+ IConfiguration Configuration);
diff --git a/src/Nexo.Hosting/NexoServiceCollectionExtensions.Deployment.cs b/src/Nexo.Hosting/NexoServiceCollectionExtensions.Deployment.cs
new file mode 100644
index 000000000..fe1763d9c
--- /dev/null
+++ b/src/Nexo.Hosting/NexoServiceCollectionExtensions.Deployment.cs
@@ -0,0 +1,164 @@
+namespace Nexo.Hosting;
+
+public static partial class NexoServiceCollectionExtensions
+{
+ ///
+ /// Resolves the deployment profile from (in priority order):
+ /// 1. Explicit set by the caller.
+ /// 2. NEXO_DEPLOYMENT_PROFILE environment variable (case-insensitive;
+ /// accepts "full", "server", "edge", "airgapped"/"air-gapped", "system"/"core").
+ /// 3. Falls back to .
+ ///
+ private static NexoDeploymentProfile ResolveDeploymentProfile(NexoHostingOptions options)
+ {
+ if (options.DeploymentProfile.HasValue)
+ {
+ return options.DeploymentProfile.Value;
+ }
+
+ var raw = Environment.GetEnvironmentVariable("NEXO_DEPLOYMENT_PROFILE");
+ if (string.IsNullOrWhiteSpace(raw))
+ {
+ return NexoDeploymentProfile.Full;
+ }
+
+ if (TryParseDeploymentProfile(raw, out var parsed))
+ {
+ return parsed;
+ }
+
+ throw new InvalidOperationException(
+ $"NEXO_DEPLOYMENT_PROFILE='{raw}' is not recognized. " +
+ "Valid values: full, server, edge, air-gapped, system.");
+ }
+
+ private static bool TryParseDeploymentProfile(string? raw, out NexoDeploymentProfile profile)
+ {
+ profile = NexoDeploymentProfile.Full;
+ if (string.IsNullOrWhiteSpace(raw))
+ {
+ return false;
+ }
+
+ var normalized = raw.Trim().ToLowerInvariant();
+ profile = normalized switch
+ {
+ "full" => NexoDeploymentProfile.Full,
+ "server" => NexoDeploymentProfile.Server,
+ "edge" => NexoDeploymentProfile.Edge,
+ "airgapped" => NexoDeploymentProfile.AirGapped,
+ "air-gapped" => NexoDeploymentProfile.AirGapped,
+ "system" => NexoDeploymentProfile.System,
+ "core" => NexoDeploymentProfile.System,
+ _ => profile
+ };
+
+ return normalized is "full" or "server" or "edge" or "airgapped" or "air-gapped" or "system" or "core";
+ }
+
+ ///
+ /// Maps a deployment profile to the set of subsystem modules that should
+ /// be registered. The peeling order (Full → Server → Edge → AirGapped
+ /// → System) progressively strips capabilities:
+ ///
+ /// - Full — everything; used in development & CI.
+ /// - Server — same as Full (reserved for future server-specific gating).
+ /// - Edge — persistence + pipelines only; no NCR, no agents.
+ /// - AirGapped— NCR + adaptation + persistence; no network transport.
+ /// - System — bare minimum for CLI tooling; nothing optional.
+ ///
+ ///
+ private static ModuleSelection GetModuleSelection(NexoDeploymentProfile profile)
+ {
+ return profile switch
+ {
+ NexoDeploymentProfile.Full => new ModuleSelection(
+ IncludeNodeCapabilityRuntime: true,
+ IncludeRuntimeTransport: true,
+ IncludePersistence: true,
+ IncludeAdaptation: true,
+ IncludePipelineComposition: true,
+ IncludeBackgroundAgents: true,
+ IncludeBackgroundAgentRag: true,
+ IncludeObservationPipeline: true,
+ IncludeTrustServices: true,
+ IncludeWorkflowIntegrations: true,
+ IncludeTestingAdapters: true),
+ NexoDeploymentProfile.Server => new ModuleSelection(
+ IncludeNodeCapabilityRuntime: true,
+ IncludeRuntimeTransport: true,
+ IncludePersistence: true,
+ IncludeAdaptation: true,
+ IncludePipelineComposition: true,
+ IncludeBackgroundAgents: true,
+ IncludeBackgroundAgentRag: true,
+ IncludeObservationPipeline: true,
+ IncludeTrustServices: true,
+ IncludeWorkflowIntegrations: true,
+ IncludeTestingAdapters: true),
+ NexoDeploymentProfile.Edge => new ModuleSelection(
+ IncludeNodeCapabilityRuntime: false,
+ IncludeRuntimeTransport: false,
+ IncludePersistence: true,
+ IncludeAdaptation: false,
+ IncludePipelineComposition: true,
+ IncludeBackgroundAgents: false,
+ IncludeBackgroundAgentRag: false,
+ IncludeObservationPipeline: false,
+ IncludeTrustServices: false,
+ IncludeWorkflowIntegrations: false,
+ IncludeTestingAdapters: false),
+ NexoDeploymentProfile.AirGapped => new ModuleSelection(
+ IncludeNodeCapabilityRuntime: true,
+ IncludeRuntimeTransport: false,
+ IncludePersistence: true,
+ IncludeAdaptation: true,
+ IncludePipelineComposition: true,
+ IncludeBackgroundAgents: false,
+ IncludeBackgroundAgentRag: false,
+ IncludeObservationPipeline: false,
+ IncludeTrustServices: false,
+ IncludeWorkflowIntegrations: false,
+ IncludeTestingAdapters: false),
+ NexoDeploymentProfile.System => new ModuleSelection(
+ IncludeNodeCapabilityRuntime: false,
+ IncludeRuntimeTransport: false,
+ IncludePersistence: false,
+ IncludeAdaptation: false,
+ IncludePipelineComposition: false,
+ IncludeBackgroundAgents: false,
+ IncludeBackgroundAgentRag: false,
+ IncludeObservationPipeline: false,
+ IncludeTrustServices: false,
+ IncludeWorkflowIntegrations: false,
+ IncludeTestingAdapters: false),
+ _ => throw new ArgumentOutOfRangeException(nameof(profile), profile, "Unknown Nexo deployment profile.")
+ };
+ }
+
+ ///
+ /// Applies the NEXO_STRICT_MODE ("1" / "true") environment variable
+ /// when the caller has not already enabled strict mode programmatically.
+ /// Strict mode turns configuration warnings into hard failures — intended
+ /// for CI gates where misconfiguration should break the build.
+ ///
+ private static void ResolveStrictMode(NexoHostingOptions options)
+ {
+ if (!options.StrictMode.Enabled)
+ {
+ options.StrictMode.Enabled = ParseBooleanEnvironmentVariable("NEXO_STRICT_MODE");
+ }
+ }
+
+ internal static bool ParseBooleanEnvironmentVariable(string key)
+ {
+ var value = Environment.GetEnvironmentVariable(key);
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return false;
+ }
+
+ return string.Equals(value, "1", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(value, "true", StringComparison.OrdinalIgnoreCase);
+ }
+}
diff --git a/src/Nexo.Hosting/NexoServiceCollectionExtensions.NodeCapabilityRuntime.cs b/src/Nexo.Hosting/NexoServiceCollectionExtensions.NodeCapabilityRuntime.cs
new file mode 100644
index 000000000..d294db9b9
--- /dev/null
+++ b/src/Nexo.Hosting/NexoServiceCollectionExtensions.NodeCapabilityRuntime.cs
@@ -0,0 +1,53 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Nexo.Infrastructure.ModelArtifacts;
+using Nexo.Infrastructure.NodeCapabilityRuntime;
+
+namespace Nexo.Hosting;
+
+public static partial class NexoServiceCollectionExtensions
+{
+ ///
+ /// Registers the platform-specific NCR (Node Capability Runtime) module.
+ /// NCR probes local hardware (GPU, RAM, accelerators) and exposes
+ /// capabilities used by ICapabilityRouter to decide
+ /// whether a job can run locally or must be routed to a peer/cloud.
+ /// Falls back to Linux when the OS is not recognised.
+ /// Also wires model artifact catalog sources used during NCR/model discovery.
+ /// Invoked from phase 01 when the deployment profile includes NCR.
+ ///
+ internal static void RegisterNodeCapabilityRuntime(IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddNodeCapabilityRuntimeCore(configuration);
+ if (OperatingSystem.IsWindows())
+ {
+ services.AddNodeCapabilityRuntimeWindows(configuration);
+ }
+ else if (OperatingSystem.IsMacOS())
+ {
+ services.AddNodeCapabilityRuntimeMacOS(configuration);
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ services.AddNodeCapabilityRuntimeLinux(configuration);
+ }
+ else if (OperatingSystem.IsIOS())
+ {
+ services.AddNodeCapabilityRuntimeiOS(configuration);
+ }
+ else if (OperatingSystem.IsAndroid())
+ {
+ services.AddNodeCapabilityRuntimeAndroid(configuration);
+ }
+ else
+ {
+ services.AddNodeCapabilityRuntimeLinux(configuration);
+ }
+
+ services.AddModelArtifactCatalog(configuration);
+ if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+ {
+ services.AddDockerOllamaModelArtifactCatalogSource();
+ }
+ }
+}
diff --git a/src/Nexo.Hosting/NexoServiceCollectionExtensions.cs b/src/Nexo.Hosting/NexoServiceCollectionExtensions.cs
index 05167edd9..523ec75d4 100644
--- a/src/Nexo.Hosting/NexoServiceCollectionExtensions.cs
+++ b/src/Nexo.Hosting/NexoServiceCollectionExtensions.cs
@@ -1,754 +1,124 @@
-using FluentValidation;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Http;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using Nexo.BackgroundAgents;
-using Nexo.BackgroundAgents.Trust;
-using Nexo.Core.Application.Adaptation.Ports;
-using Nexo.Contracts;
-using Nexo.Core.Application.Analysis.UseCases.AnalyzeCode;
-using Nexo.Core.Application.Ephemeral.Ports;
-using Nexo.Core.Application.Knowledge.Ports;
-using Nexo.Core.Application.Observation.Ports;
-using Nexo.Core.Application.Validation.UseCases.RunValidation;
-using Nexo.Core.Application.Testing.UseCases.RunTests;
-using Nexo.Core.Application.Common.Ports;
-using Nexo.Core.Application.Common.Services;
-using Nexo.Core.Application.Copilot.Ports;
-using Nexo.Core.Application.Paths;
-using Nexo.Core.Application.Trust.Ports;
-using Nexo.Infrastructure;
-using Nexo.Infrastructure.Environments;
-using Nexo.Infrastructure.Execution;
-using Nexo.Infrastructure.Execution.Routing;
-using Nexo.Infrastructure.Execution.Ephemeral;
-using Nexo.Infrastructure.Execution.LoadPolicy;
-using Nexo.Infrastructure.Knowledge;
-using Nexo.Infrastructure.Maintenance;
-using Nexo.Infrastructure.NodeCapabilityRuntime;
-using Nexo.Infrastructure.ModelArtifacts;
-using Nexo.Infrastructure.Pipelines;
-using Nexo.Infrastructure.Persistence.Ephemeral;
-using Nexo.Infrastructure.Persistence;
-using Nexo.Infrastructure.Copilot;
-using Nexo.Infrastructure.Fleet;
-using Nexo.Orchestration;
-using Nexo.Orchestration.Models;
-using Nexo.Abstractions.Routing;
-using Nexo.Abstractions.Transport;
-using Nexo.Orchestration.Transport;
-using Nexo.Runtime;
-using Nexo.Runtime.Routing;
-using Nexo.Transport.Grpc;
-
-namespace Nexo.Hosting;
-
-///
-/// DI composition root for the Nexo kernel. This is the single place that wires every
-/// subsystem together — orchestration, adaptation, persistence, trust, execution, etc.
-///
-/// Architecture: The method follows a strict registration
-/// order because later registrations depend on services registered earlier (e.g. the
-/// model decorator chain wraps ProviderBackedModel → HotSwappableModel →
-/// OrchestrationRuntimeModelDecorator, so the provider factory must already exist).
-///
-///
-/// Deployment profiles: A (resolved from
-/// NEXO_DEPLOYMENT_PROFILE or )
-/// controls which subsystem modules are included via .
-/// Profiles range from Full (all modules) down to System (bare minimum
-/// for CLI/headless tooling).
-///
-///
-/// Related files:
-/// — caller-facing option bag;
-/// — deployment tier enum;
-/// Nexo.Core.Domain.NexoDefaults — all tuneable default constants.
-///
-///
-public static class NexoServiceCollectionExtensions
-{
- ///
- /// Flags produced by that decide which subsystem
- /// modules are registered. Each flag maps 1-to-1 to a conditional block inside
- /// . The mapping is intentionally explicit (no reflection)
- /// so that trimming and ahead-of-time compilation remain safe.
- ///
- private sealed record ModuleSelection(
- bool IncludeNodeCapabilityRuntime,
- bool IncludeRuntimeTransport,
- bool IncludePersistence,
- bool IncludeAdaptation,
- bool IncludePipelineComposition,
- bool IncludeBackgroundAgents,
- bool IncludeBackgroundAgentRag,
- bool IncludeObservationPipeline,
- bool IncludeTrustServices,
- bool IncludeWorkflowIntegrations,
- bool IncludeTestingAdapters);
-
- ///
- /// Adds Nexo with an explicit deployment profile.
- ///
- /// The service collection.
- /// Dependency profile to apply.
- /// Optional additional options overrides.
- /// The service collection for chaining.
- public static IServiceCollection AddNexoProfile(
- this IServiceCollection services,
- NexoDeploymentProfile profile,
- Action? configure = null)
- {
- return services.AddNexo(options =>
- {
- options.DeploymentProfile = profile;
- configure?.Invoke(options);
- });
- }
-
- ///
- /// Registers every Nexo subsystem into the DI container. The registration order
- /// matters: downstream registrations (model decorator chain, workflow executor)
- /// resolve services registered in earlier blocks.
- ///
- /// Environment variables read here (see inline comments for each):
- /// NEXO_STRICT_MODE, NEXO_DEPLOYMENT_PROFILE,
- /// NEXO_LOOP_PARALLEL, NEXO_LOOP_INSTRUMENT,
- /// NEXO_OBSERVATION_FAIL_OPEN, NEXO_EPHEMERAL,
- /// NEXO_EPHEMERAL_MODELS, NEXO_EPHEMERAL_DB,
- /// NEXO_TRUST_ENABLED, NEXO_LOAD_PREFERENCE,
- /// NEXO_EXECUTION_REMOTE_URL.
- ///
- ///
- public static IServiceCollection AddNexo(
- this IServiceCollection services,
- Action? configure = null)
- {
- // ── Strict mode & deployment profile ───────────────────────────
- // Strict mode is resolved first because the configuration service
- // adapter (registered below) reads it to decide whether config
- // warnings should throw. Deployment profile gates every
- // conditional module block that follows.
- var options = new NexoHostingOptions();
- configure?.Invoke(options);
- ResolveStrictMode(options);
- var deploymentProfile = ResolveDeploymentProfile(options);
- var modules = GetModuleSelection(deploymentProfile);
-
- services.AddSingleton(options.StrictMode);
-
- // ── Configuration & Node Capability Runtime ────────────────────
- // Environment variables are the primary config source; appsettings
- // is intentionally NOT loaded here so that containerised deployments
- // stay 12-factor compliant. RemoteCapabilitiesOptions binds from
- // the "Nexo:RemoteCapabilities" section for RunPod/cloud routing.
- services.AddHttpClient();
- var configuration = new ConfigurationBuilder()
- .AddEnvironmentVariables()
- .Build();
- services.AddOptions()
- .Bind(configuration.GetSection("Nexo:RemoteCapabilities"));
- if (modules.IncludeNodeCapabilityRuntime)
- {
- services.AddRunPodCapabilityRouting(configuration);
- RegisterNodeCapabilityRuntime(services, configuration);
- }
-
- // ── CQRS (MediatR) & FluentValidation ─────────────────────────
- // MediatR handlers from both the Analysis and Testing assemblies
- // are registered in one pass. The ValidationBehavior pipeline
- // behavior runs FluentValidation before each handler, so
- // validators must also be registered here.
- services.AddMediatR(cfg =>
- {
- cfg.RegisterServicesFromAssembly(typeof(AnalyzeCodeCommand).Assembly);
- cfg.RegisterServicesFromAssembly(typeof(RunTestsCommand).Assembly);
- });
-
- services.TryAddSingleton();
-
- services.AddValidatorsFromAssembly(typeof(AnalyzeCodeValidator).Assembly);
- services.AddTransient(typeof(MediatR.IPipelineBehavior<,>), typeof(Nexo.Core.Application.Behaviors.IngressLoggingPipelineBehavior<,>));
- services.AddTransient(typeof(MediatR.IPipelineBehavior<,>), typeof(Nexo.Core.Application.Behaviors.ValidationBehavior<,>));
- services.TryAddSingleton();
-
- // ── Configuration service adapter ──────────────────────────────
- // Bridges the domain-level IConfigurationService port to the
- // infrastructure adapter. Strict mode controls whether config
- // warnings escalate to hard failures (useful in CI pipelines).
- services.AddSingleton(sp =>
- {
- var logger = sp.GetRequiredService>();
- var strictMode = sp.GetService();
- return new Nexo.Infrastructure.Configuration.ConfigurationServiceAdapter(logger, strictMode?.ShouldFailOnConfigurationWarnings ?? false);
- });
-
- // ── Loop kernel (decorator chain) ──────────────────────────────
- // The loop kernel runs brick-level iterations. It is composed via
- // the decorator pattern:
- // SequentialLoopKernel (always present — baseline)
- // → ParallelLoopKernel (if NEXO_LOOP_PARALLEL=1)
- // → InstrumentedLoopKernel (if NEXO_LOOP_INSTRUMENT=1)
- //
- // NEXO_LOOP_PARALLEL ("1"): wraps in a parallelising decorator for
- // concurrent brick evaluation; useful on multi-core servers.
- // NEXO_LOOP_INSTRUMENT ("1"): adds timing/counter telemetry around
- // each loop iteration; adds overhead, meant for dev profiling.
- services.AddSingleton(sp =>
- {
- ILoopKernel k = new SequentialLoopKernel();
- var enableParallel = string.Equals(Environment.GetEnvironmentVariable("NEXO_LOOP_PARALLEL"), "1", StringComparison.OrdinalIgnoreCase);
- if (enableParallel)
- k = new ParallelLoopKernel(k);
- var instrument = string.Equals(Environment.GetEnvironmentVariable("NEXO_LOOP_INSTRUMENT"), "1", StringComparison.OrdinalIgnoreCase);
- if (instrument)
- k = new InstrumentedLoopKernel(k, sp.GetRequiredService>());
- return k;
- });
-
- // ── Orchestration & transport ──────────────────────────────────
- // Orchestration is always registered (it owns the runtime spec
- // accessor used by the model decorator chain). Transport is
- // optional: when present it registers gRPC channels plus the
- // dual in-process / gRPC agent transport pair used for peer
- // communication. See Nexo.Transport.Grpc for channel config.
- services.AddNexoOrchestration();
- if (modules.IncludeRuntimeTransport)
- {
- services.AddOptions();
- services.AddOptions();
- services.TryAddSingleton();
- services.TryAddSingleton();
- services.AddNexoRuntimeTransport();
- }
-
- // ── Persistence ────────────────────────────────────────────────
- if (modules.IncludePersistence)
- {
- services.AddNexoPersistence();
- services.AddPostgresIsolatedDatabaseProvisioner();
- }
-
- // ── Adaptation ─────────────────────────────────────────────────
- // Pattern store path is forwarded so the adaptation layer knows
- // where to persist learned patterns on disk.
- if (modules.IncludeAdaptation)
- {
- services.AddAdaptationInfrastructure(options.PatternStorePath);
- services.AddNexoMeshKnowledgeReplication(configuration);
- }
-
- if (modules.IncludeAdaptation)
- services.AddNexoFederatedBrickMesh(configuration);
-
- // ── Copilot task store ──────────────────────────────────────────
- // LiteDB file is co-located with the pattern store directory
- // (or the repo root as fallback) to keep all Nexo-generated
- // state in one discoverable location.
- var copilotTasksBasePath = !string.IsNullOrEmpty(options.PatternStorePath)
- ? Path.GetDirectoryName(options.PatternStorePath) ?? "."
- : RepoPathResolver.FindRepoRoot();
- var copilotTasksDbPath = Path.Combine(copilotTasksBasePath, "nexo-copilot-tasks.db");
- services.TryAddSingleton(_ => new LiteDbCopilotTaskStore(copilotTasksDbPath));
-
- // ── Knowledge query service ────────────────────────────────────
- // Aggregates adaptation logs, pattern store, and (optionally)
- // user-knowledge logs into a single query façade. Falls back to
- // an in-memory knowledge log when the trust module is absent.
- services.TryAddSingleton(sp =>
- {
- var adaptationLog = sp.GetRequiredService();
- var patternStore = sp.GetRequiredService();
- var userKnowledgeStore = sp.GetService()
- ?? new Nexo.Infrastructure.Trust.InMemoryUserKnowledgeLogStore();
- return new KnowledgeQueryService(adaptationLog, patternStore, userKnowledgeStore);
- });
-
- // ── Pipeline composition ───────────────────────────────────────
- if (modules.IncludePipelineComposition)
- services.AddPipelineCompositionLayer();
-
- // ── Background agents & RAG ────────────────────────────────────
- if (modules.IncludeBackgroundAgents)
- services.AddBackgroundAgents(registerHostedService: options.RegisterBackgroundAgentHostedService);
-
- if (modules.IncludeBackgroundAgentRag)
- services.AddBackgroundAgentsRAG();
-
- // ── Observation pipeline ───────────────────────────────────────
- // Captures runtime telemetry and persists it alongside patterns.
- // NEXO_OBSERVATION_FAIL_OPEN ("1" / "true"): when set, store I/O
- // errors are swallowed instead of failing the pipeline — safe
- // for edge nodes with unreliable storage.
- if (modules.IncludeObservationPipeline && !options.DisableObservationPipeline)
- {
- var repoRoot = RepoPathResolver.FindRepoRoot();
- var observationFailOpen = options.ObservationFailOpen ?? ParseBooleanEnvironmentVariable("NEXO_OBSERVATION_FAIL_OPEN");
- services.AddObservationPipeline(opts =>
- {
- opts.RepoRoot = repoRoot;
- opts.StorePath = options.PatternStorePath ?? "nexo-patterns.db";
- opts.FailOpenOnStoreErrors = observationFailOpen;
- }, registerHostedService: options.RegisterBackgroundAgentHostedService);
- }
-
- // Mock web-search provider is registered as a fallback so
- // background agents can be instantiated even when no real
- // provider is configured.
- if (modules.IncludeBackgroundAgents)
- services.TryAddSingleton();
-
- // ── Model decorator chain ──────────────────────────────────────
- // The IModel abstraction is built as a three-layer decorator:
- //
- // 1. ProviderBackedModel – delegates to IProviderFactory
- // 2. HotSwappableModel – allows runtime model switching
- // without restarting the host
- // 3. OrchestrationRuntimeModelDecorator
- // – injects orchestration-level
- // spec overrides (temperature,
- // token limits, etc.) per-call
- //
- // HotSwappableModel is registered as a concrete singleton so that
- // administrative endpoints can resolve it directly for hot-swap
- // operations, while IModel always returns the fully decorated chain.
- services.AddSingleton(sp =>
- {
- var providerFactory = sp.GetRequiredService();
- var providerBacked = new Nexo.Infrastructure.Execution.Models.ProviderBackedModel(
- providerFactory,
- sp.GetRequiredService>());
- return new Nexo.Infrastructure.Execution.Models.HotSwappableModel(
- providerBacked,
- sp.GetRequiredService>());
- });
-
- services.AddSingleton(sp =>
- {
- var accessor = sp.GetRequiredService();
- var inner = sp.GetRequiredService();
- return new Nexo.Orchestration.Models.OrchestrationRuntimeModelDecorator(
- inner,
- accessor,
- sp.GetRequiredService>());
- });
-
- // ── Ephemeral lifecycle ────────────────────────────────────────
- // "Ephemeral" means Nexo can spin up and tear down backing
- // resources on demand (Ollama models, Postgres databases).
- //
- // NEXO_EPHEMERAL ("1"): master switch — enables ALL ephemeral
- // subsystems.
- // NEXO_EPHEMERAL_MODELS ("1"): enables only ephemeral model
- // lifecycle (Ollama pull/remove) without affecting databases.
- // NEXO_EPHEMERAL_DB ("postgres"): enables ephemeral Postgres
- // database creation; only takes effect when persistence is on.
- var ephemeralAll = string.Equals(Environment.GetEnvironmentVariable("NEXO_EPHEMERAL"), "1", StringComparison.OrdinalIgnoreCase);
- var ephemeralModels = ephemeralAll || string.Equals(Environment.GetEnvironmentVariable("NEXO_EPHEMERAL_MODELS"), "1", StringComparison.OrdinalIgnoreCase);
- if (ephemeralModels)
- services.AddSingleton();
-
- var ephemeralDb = Environment.GetEnvironmentVariable("NEXO_EPHEMERAL_DB")?.Trim();
- if (modules.IncludePersistence && string.Equals(ephemeralDb, "postgres", StringComparison.OrdinalIgnoreCase))
- services.AddSingleton();
-
- // ── Trust & provider factory (3-way branching) ─────────────────
- // The provider factory is the gateway through which every LLM
- // call flows. Three mutually-exclusive wiring paths exist:
- //
- // Path A — Adaptive load-balancing (NEXO_LOAD_PREFERENCE set):
- // ProviderFactory → (optional SanitizingProviderFactory if
- // trust is on) → AdaptiveProviderFactory.
- // Load policy is driven by NEXO_LOAD_PREFERENCE value.
- //
- // Path B — Trust without adaptive (NEXO_TRUST_ENABLED=1,
- // no load pref):
- // Trust module registers its own SanitizingProviderFactory
- // via AddTrustServices (skipProviderRegistration: false).
- //
- // Path C — Plain (neither trust nor adaptive):
- // Bare ProviderFactory is registered directly.
- //
- // NEXO_TRUST_ENABLED ("1"): activates the sanitization proxy
- // that scrubs PII before LLM calls leave the trust boundary.
- // NEXO_LOAD_PREFERENCE (string, e.g. "latency" / "cost"):
- // activates adaptive load balancing and selects the policy.
- var trustEnabledByConfig = options.TrustEnabled ?? string.Equals(Environment.GetEnvironmentVariable("NEXO_TRUST_ENABLED"), "1", StringComparison.OrdinalIgnoreCase);
- var trustEnabled = modules.IncludeTrustServices && trustEnabledByConfig;
- var loadPref = Environment.GetEnvironmentVariable("NEXO_LOAD_PREFERENCE")?.Trim();
- var useAdaptive = options.UseAdaptiveLoadBalancing ?? !string.IsNullOrEmpty(loadPref);
-
- if (modules.IncludeTrustServices)
- {
- services.AddTrustServices(useSanitizingProviderFactory: trustEnabled, ephemeralLifecycle: ephemeralModels, skipProviderRegistration: useAdaptive);
- }
-
- // Path A: adaptive load-balancing wraps everything
- if (useAdaptive)
- {
- services.AddSingleton(sp =>
- {
- var logger = sp.GetRequiredService>();
- var lifecycle = sp.GetService();
- return new ProviderFactory(logger, lifecycle);
- });
- services.TryAddSingleton();
- services.AddSingleton(sp =>
- {
- var pf = sp.GetRequiredService();
- Nexo.Infrastructure.Execution.IProviderFactory inner = trustEnabled
- ? new SanitizingProviderFactory(pf, sp.GetRequiredService(),
- sp.GetRequiredService>())
- : pf;
- var policy = sp.GetRequiredService();
- var logger = sp.GetService>();
- return new AdaptiveProviderFactory(inner, policy, logger);
- });
- }
- // Path C: plain provider (Path B is handled inside AddTrustServices)
- else if (!trustEnabled)
- {
- services.AddSingleton(sp =>
- {
- var logger = sp.GetRequiredService>();
- var lifecycle = sp.GetService();
- return new ProviderFactory(logger, lifecycle);
- });
- }
-
- // ── Execution core & workflow ──────────────────────────────────
- services.AddSingleton();
- services.AddMapDataProviderRouting();
-
- // Workflow integrations (PDF export, webhooks, DB read/write,
- // cluster store) are only available in Full/Server profiles.
- // WorkflowExecutor resolves them as optional dependencies so it
- // can still execute pure in-memory workflows without them.
- if (modules.IncludeWorkflowIntegrations)
- {
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- }
-
- // Semantic cache, behavior registry, step mode, and behavior
- // executor form the brick execution pipeline. TryAddSingleton
- // is used so that test hosts or SDK consumers can substitute
- // any of these before calling AddNexo.
- services.TryAddSingleton(sp =>
- new Nexo.Infrastructure.Execution.SemanticCache(sp.GetRequiredService>()));
- services.TryAddSingleton(_ =>
- new Nexo.Infrastructure.Execution.BehaviorRegistry(Array.Empty()));
- services.TryAddSingleton(sp =>
- new Nexo.Infrastructure.Execution.StepExecutionModeStore(
- null,
- sp.GetService>()));
- services.TryAddSingleton(sp =>
- new Nexo.Infrastructure.Execution.BehaviorExecutor(
- sp.GetRequiredService(),
- sp.GetRequiredService(),
- sp.GetRequiredService(),
- sp.GetRequiredService(),
- sp.GetRequiredService>(),
- sp.GetService(),
- sp.GetService(),
- sp.GetService()));
- // Agent registry is populated from SDK-provided AgentCards; if
- // none are supplied the registry starts empty and agents can be
- // registered later at runtime.
- services.TryAddSingleton(sp =>
- {
- var sdkOptions = sp.GetService();
- var cards = sdkOptions?.AgentCards?.ToList() ?? new List();
- return new Nexo.Infrastructure.Execution.AgentRegistry(cards);
- });
-
- // ── Workflow executor ──────────────────────────────────────────
- // Scoped because a single workflow execution may accumulate
- // state (e.g. cluster affinity) that should not leak across
- // independent request scopes.
- services.AddScoped(sp =>
- new Nexo.Core.Application.Workflows.WorkflowExecutor(
- sp.GetRequiredService(),
- sp.GetRequiredService(),
- sp.GetRequiredService(),
- sp.GetRequiredService(),
- sp.GetRequiredService(),
- sp.GetRequiredService(),
- sp.GetRequiredService>(),
- pdfExporter: sp.GetService(),
- webhookClient: sp.GetService(),
- databaseReader: sp.GetService(),
- databaseWriter: sp.GetService(),
- clusterStore: sp.GetService()));
-
- services.AddScoped();
- services.AddScoped();
-
- // ── Analysis & validation ──────────────────────────────────────
- // Both services use a caching decorator (CachedAnalysis/
- // CachedValidation) to avoid re-running expensive analysis
- // or parsing when the same input appears within a scope.
- services.AddScoped(sp =>
- {
- var inner = new Nexo.Infrastructure.Analysis.Adapters.AnalysisServiceAdapter(
- sp.GetRequiredService>(),
- sp.GetRequiredService());
- var cache = sp.GetRequiredService();
- var logger = sp.GetRequiredService>();
- return new Nexo.Infrastructure.Analysis.Adapters.CachedAnalysisServiceAdapter(inner, cache, logger);
- });
-
- services.AddScoped(sp =>
- {
- var inner = new Nexo.Infrastructure.Validation.Adapters.ValidationServiceAdapter(
- sp.GetRequiredService>(),
- sp.GetRequiredService());
- var cache = sp.GetRequiredService();
- var logger = sp.GetRequiredService>();
- return new Nexo.Infrastructure.Validation.Adapters.CachedValidationServiceAdapter(inner, cache, logger);
- });
-
- services.AddSingleton();
- services.AddSingleton();
- // ── Testing adapters ───────────────────────────────────────────
- services.AddScoped();
-
- // NEXO_EXECUTION_REMOTE_URL (URL string): when set, test
- // execution is delegated to a remote execution service via
- // HTTP instead of the local Docker-based platform. Useful in
- // CI environments where Docker-in-Docker is unavailable.
- if (modules.IncludeTestingAdapters)
- {
- var executionRemoteUrl = options.ExecutionRemoteUrl ?? Environment.GetEnvironmentVariable("NEXO_EXECUTION_REMOTE_URL")?.Trim();
- if (!string.IsNullOrEmpty(executionRemoteUrl))
- {
- var baseUrl = executionRemoteUrl.TrimEnd('/') + "/";
- services.AddHttpClient("NexoExecution", c => c.BaseAddress = new Uri(baseUrl));
- services.AddSingleton(sp =>
- {
- var factory = sp.GetRequiredService();
- var client = factory.CreateClient("NexoExecution");
- var logger = sp.GetService>();
- return new Nexo.Infrastructure.Testing.ExecutionPlatform.RemoteExecutionPlatform(client, logger);
- });
- }
- else
- {
- services.AddSingleton(sp =>
- new Nexo.Infrastructure.Testing.ExecutionPlatform.DockerExecutionPlatform(sp.GetRequiredService>()));
- }
-
- services.AddSingleton(sp =>
- new Nexo.Infrastructure.Testing.Docker.DockerService(sp.GetRequiredService>()));
- services.AddSingleton(sp =>
- new Nexo.Infrastructure.Testing.CodeAnalysis.RoslynCodeAnalysisService(sp.GetRequiredService>()));
- services.AddArtifactCleanup();
- }
-
- // ── Analysis rule engine ───────────────────────────────────────
- // Rules are collected via DI multi-registration and fed into
- // the engine. Add new IAnalysisRule implementations to extend
- // the static analysis suite without touching this file.
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped(sp =>
- {
- var rules = sp.GetServices();
- var logger = sp.GetRequiredService>();
- return new Nexo.Infrastructure.Analysis.Rules.AnalysisRuleEngine(rules, logger);
- });
-
- // Phase 1 mesh director (in-memory fleet + task placement). See docs/MeshPhase0NorthStar.md.
- services.AddNexoFleetDirector();
- services.AddNexoMeshElasticScheduling(configuration);
- services.AddNexoMeshCheckpointScheduling(configuration);
-
- return services;
- }
-
- ///
- /// Registers the platform-specific NCR (Node Capability Runtime) module.
- /// NCR probes local hardware (GPU, RAM, accelerators) and exposes
- /// capabilities used by ICapabilityRouter to decide
- /// whether a job can run locally or must be routed to a peer/cloud.
- /// Falls back to Linux when the OS is not recognised.
- ///
- private static void RegisterNodeCapabilityRuntime(IServiceCollection services, IConfiguration configuration)
- {
- services.AddNodeCapabilityRuntimeCore(configuration);
- if (OperatingSystem.IsWindows())
- services.AddNodeCapabilityRuntimeWindows(configuration);
- else if (OperatingSystem.IsMacOS())
- services.AddNodeCapabilityRuntimeMacOS(configuration);
- else if (OperatingSystem.IsLinux())
- services.AddNodeCapabilityRuntimeLinux(configuration);
- else if (OperatingSystem.IsIOS())
- services.AddNodeCapabilityRuntimeiOS(configuration);
- else if (OperatingSystem.IsAndroid())
- services.AddNodeCapabilityRuntimeAndroid(configuration);
- else
- services.AddNodeCapabilityRuntimeLinux(configuration);
-
- services.AddModelArtifactCatalog(configuration);
- if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
- {
- services.AddDockerOllamaModelArtifactCatalogSource();
- }
- }
-
- ///
- /// Resolves the deployment profile from (in priority order):
- /// 1. Explicit set by the caller.
- /// 2. NEXO_DEPLOYMENT_PROFILE environment variable (case-insensitive;
- /// accepts "full", "server", "edge", "airgapped"/"air-gapped", "system"/"core").
- /// 3. Falls back to .
- ///
- private static NexoDeploymentProfile ResolveDeploymentProfile(NexoHostingOptions options)
- {
- if (options.DeploymentProfile.HasValue)
- return options.DeploymentProfile.Value;
-
- var raw = Environment.GetEnvironmentVariable("NEXO_DEPLOYMENT_PROFILE");
- if (string.IsNullOrWhiteSpace(raw))
- return NexoDeploymentProfile.Full;
-
- if (TryParseDeploymentProfile(raw, out var parsed))
- return parsed;
-
- throw new InvalidOperationException(
- $"NEXO_DEPLOYMENT_PROFILE='{raw}' is not recognized. " +
- "Valid values: full, server, edge, air-gapped, system.");
- }
-
- private static bool TryParseDeploymentProfile(string? raw, out NexoDeploymentProfile profile)
- {
- profile = NexoDeploymentProfile.Full;
- if (string.IsNullOrWhiteSpace(raw))
- return false;
-
- var normalized = raw.Trim().ToLowerInvariant();
- profile = normalized switch
- {
- "full" => NexoDeploymentProfile.Full,
- "server" => NexoDeploymentProfile.Server,
- "edge" => NexoDeploymentProfile.Edge,
- "airgapped" => NexoDeploymentProfile.AirGapped,
- "air-gapped" => NexoDeploymentProfile.AirGapped,
- "system" => NexoDeploymentProfile.System,
- "core" => NexoDeploymentProfile.System,
- _ => profile
- };
-
- return normalized is "full" or "server" or "edge" or "airgapped" or "air-gapped" or "system" or "core";
- }
-
- ///
- /// Maps a deployment profile to the set of subsystem modules that should
- /// be registered. The peeling order (Full → Server → Edge → AirGapped
- /// → System) progressively strips capabilities:
- ///
- /// - Full — everything; used in development & CI.
- /// - Server — same as Full (reserved for future server-specific gating).
- /// - Edge — persistence + pipelines only; no NCR, no agents.
- /// - AirGapped— NCR + adaptation + persistence; no network transport.
- /// - System — bare minimum for CLI tooling; nothing optional.
- ///
- ///
- private static ModuleSelection GetModuleSelection(NexoDeploymentProfile profile)
- {
- return profile switch
- {
- NexoDeploymentProfile.Full => new ModuleSelection(
- IncludeNodeCapabilityRuntime: true,
- IncludeRuntimeTransport: true,
- IncludePersistence: true,
- IncludeAdaptation: true,
- IncludePipelineComposition: true,
- IncludeBackgroundAgents: true,
- IncludeBackgroundAgentRag: true,
- IncludeObservationPipeline: true,
- IncludeTrustServices: true,
- IncludeWorkflowIntegrations: true,
- IncludeTestingAdapters: true),
- NexoDeploymentProfile.Server => new ModuleSelection(
- IncludeNodeCapabilityRuntime: true,
- IncludeRuntimeTransport: true,
- IncludePersistence: true,
- IncludeAdaptation: true,
- IncludePipelineComposition: true,
- IncludeBackgroundAgents: true,
- IncludeBackgroundAgentRag: true,
- IncludeObservationPipeline: true,
- IncludeTrustServices: true,
- IncludeWorkflowIntegrations: true,
- IncludeTestingAdapters: true),
- NexoDeploymentProfile.Edge => new ModuleSelection(
- IncludeNodeCapabilityRuntime: false,
- IncludeRuntimeTransport: false,
- IncludePersistence: true,
- IncludeAdaptation: false,
- IncludePipelineComposition: true,
- IncludeBackgroundAgents: false,
- IncludeBackgroundAgentRag: false,
- IncludeObservationPipeline: false,
- IncludeTrustServices: false,
- IncludeWorkflowIntegrations: false,
- IncludeTestingAdapters: false),
- NexoDeploymentProfile.AirGapped => new ModuleSelection(
- IncludeNodeCapabilityRuntime: true,
- IncludeRuntimeTransport: false,
- IncludePersistence: true,
- IncludeAdaptation: true,
- IncludePipelineComposition: true,
- IncludeBackgroundAgents: false,
- IncludeBackgroundAgentRag: false,
- IncludeObservationPipeline: false,
- IncludeTrustServices: false,
- IncludeWorkflowIntegrations: false,
- IncludeTestingAdapters: false),
- NexoDeploymentProfile.System => new ModuleSelection(
- IncludeNodeCapabilityRuntime: false,
- IncludeRuntimeTransport: false,
- IncludePersistence: false,
- IncludeAdaptation: false,
- IncludePipelineComposition: false,
- IncludeBackgroundAgents: false,
- IncludeBackgroundAgentRag: false,
- IncludeObservationPipeline: false,
- IncludeTrustServices: false,
- IncludeWorkflowIntegrations: false,
- IncludeTestingAdapters: false),
- _ => throw new ArgumentOutOfRangeException(nameof(profile), profile, "Unknown Nexo deployment profile.")
- };
- }
-
- ///
- /// Applies the NEXO_STRICT_MODE ("1" / "true") environment variable
- /// when the caller has not already enabled strict mode programmatically.
- /// Strict mode turns configuration warnings into hard failures — intended
- /// for CI gates where misconfiguration should break the build.
- ///
- private static void ResolveStrictMode(NexoHostingOptions options)
- {
- if (!options.StrictMode.Enabled)
- options.StrictMode.Enabled = ParseBooleanEnvironmentVariable("NEXO_STRICT_MODE");
- }
-
- private static bool ParseBooleanEnvironmentVariable(string key)
- {
- var value = Environment.GetEnvironmentVariable(key);
- if (string.IsNullOrWhiteSpace(value))
- {
- return false;
- }
-
- return string.Equals(value, "1", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(value, "true", StringComparison.OrdinalIgnoreCase);
- }
-}
+using FluentValidation;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Http;
+using Nexo.Abstractions.Routing;
+using Nexo.Abstractions.Transport;
+using Nexo.BackgroundAgents;
+using Nexo.BackgroundAgents.Trust;
+using Nexo.Core.Application.Adaptation.Ports;
+using Nexo.Core.Application.Analysis.UseCases.AnalyzeCode;
+using Nexo.Core.Application.Common.Ports;
+using Nexo.Core.Application.Common.Services;
+using Nexo.Core.Application.Copilot.Ports;
+using Nexo.Core.Application.Ephemeral.Ports;
+using Nexo.Core.Application.Knowledge.Ports;
+using Nexo.Core.Application.Observation.Ports;
+using Nexo.Core.Application.Paths;
+using Nexo.Core.Application.Testing.UseCases.RunTests;
+using Nexo.Core.Application.Trust.Ports;
+using Nexo.Core.Application.Validation.UseCases.RunValidation;
+using Nexo.Infrastructure;
+using Nexo.Infrastructure.Copilot;
+using Nexo.Infrastructure.Execution;
+using Nexo.Infrastructure.Execution.Ephemeral;
+using Nexo.Infrastructure.Execution.LoadPolicy;
+using Nexo.Infrastructure.Execution.Routing;
+using Nexo.Infrastructure.Knowledge;
+using Nexo.Infrastructure.Maintenance;
+using Nexo.Infrastructure.ModelArtifacts;
+using Nexo.Infrastructure.NodeCapabilityRuntime;
+using Nexo.Infrastructure.Persistence;
+using Nexo.Infrastructure.Persistence.Ephemeral;
+using Nexo.Infrastructure.Pipelines;
+using Nexo.Orchestration;
+using Nexo.Orchestration.Models;
+using Nexo.Orchestration.Transport;
+using Nexo.Runtime;
+using Nexo.Runtime.Routing;
+using Nexo.Transport.Grpc;
+
+namespace Nexo.Hosting;
+
+///
+/// DI composition root for the Nexo kernel. This is the single place that wires every
+/// subsystem together — orchestration, adaptation, persistence, trust, execution, etc.
+///
+/// Architecture: The method follows a strict registration
+/// order because later registrations depend on services registered earlier (e.g. the
+/// model decorator chain wraps ProviderBackedModel → HotSwappableModel →
+/// OrchestrationRuntimeModelDecorator, so the provider factory must already exist).
+///
+///
+/// Deployment profiles: A (resolved from
+/// NEXO_DEPLOYMENT_PROFILE or )
+/// controls which subsystem modules are included via .
+/// Profiles range from Full (all modules) down to System (bare minimum
+/// for CLI/headless tooling).
+///
+///
+/// Related files:
+/// — caller-facing option bag;
+/// — deployment tier enum;
+/// Nexo.Core.Domain.NexoDefaults — all tuneable default constants.
+///
+///
+public static partial class NexoServiceCollectionExtensions
+{
+ ///
+ /// Adds Nexo with an explicit deployment profile.
+ ///
+ /// The service collection.
+ /// Dependency profile to apply.
+ /// Optional additional options overrides.
+ /// The service collection for chaining.
+ public static IServiceCollection AddNexoProfile(
+ this IServiceCollection services,
+ NexoDeploymentProfile profile,
+ Action? configure = null)
+ {
+ return services.AddNexo(options =>
+ {
+ options.DeploymentProfile = profile;
+ configure?.Invoke(options);
+ });
+ }
+
+ ///
+ /// Registers every Nexo subsystem into the DI container. The registration order
+ /// matters: downstream registrations (model decorator chain, workflow executor)
+ /// resolve services registered in earlier blocks.
+ ///
+ /// Environment variables read here (see inline comments for each):
+ /// NEXO_STRICT_MODE, NEXO_DEPLOYMENT_PROFILE,
+ /// NEXO_LOOP_PARALLEL, NEXO_LOOP_INSTRUMENT,
+ /// NEXO_OBSERVATION_FAIL_OPEN, NEXO_EPHEMERAL,
+ /// NEXO_EPHEMERAL_MODELS, NEXO_EPHEMERAL_DB,
+ /// NEXO_TRUST_ENABLED, NEXO_LOAD_PREFERENCE,
+ /// NEXO_EXECUTION_REMOTE_URL.
+ ///
+ ///
+ public static IServiceCollection AddNexo(
+ this IServiceCollection services,
+ Action? configure = null)
+ {
+ var options = new NexoHostingOptions();
+ configure?.Invoke(options);
+ ResolveStrictMode(options);
+ var deploymentProfile = ResolveDeploymentProfile(options);
+ var modules = GetModuleSelection(deploymentProfile);
+
+ services.AddSingleton(options.StrictMode);
+
+ services.AddHttpClient();
+ var configuration = new ConfigurationBuilder()
+ .AddEnvironmentVariables()
+ .Build();
+
+ NexoKernelRegistrar.Register(services, options, modules, configuration);
+
+ return services;
+ }
+}
+
diff --git a/src/Nexo.Hosting/Sdk/NexoSdkBuilder.cs b/src/Nexo.Hosting/Sdk/Builders/HostNexoSdkBuilder.cs
similarity index 61%
rename from src/Nexo.Hosting/Sdk/NexoSdkBuilder.cs
rename to src/Nexo.Hosting/Sdk/Builders/HostNexoSdkBuilder.cs
index 616170bb5..46fb71937 100644
--- a/src/Nexo.Hosting/Sdk/NexoSdkBuilder.cs
+++ b/src/Nexo.Hosting/Sdk/Builders/HostNexoSdkBuilder.cs
@@ -1,49 +1,69 @@
-using Microsoft.Extensions.DependencyInjection;
-using Nexo.Abstractions;
-using Nexo.Core.Application.Sdk.Ports;
-using Nexo.Core.Domain.Agents;
-using Nexo.Core.Domain.Bricks;
-
-namespace Nexo.Hosting.Sdk;
-
-///
-/// Implementation of INexoSdkBuilder. Configures NexoSdkOptions for runtime registration.
-///
-public sealed class NexoSdkBuilder : INexoSdkBuilder
-{
- private readonly NexoSdkOptions _options;
-
- ///
- /// Creates a new SDK builder with the given options.
- ///
- /// Options to populate.
- public NexoSdkBuilder(NexoSdkOptions options)
- {
- _options = options ?? throw new ArgumentNullException(nameof(options));
- }
-
- ///
- public INexoSdkBuilder RegisterBrick() where T : Brick
- {
- _options.BrickTypes.Add(typeof(T));
- return this;
- }
-
- ///
- public INexoSdkBuilder RegisterAgent() where T : class
- {
- if (!typeof(IAgent).IsAssignableFrom(typeof(T)))
- throw new ArgumentException($"Type {typeof(T).Name} must implement {nameof(IAgent)}", nameof(T));
- _options.AgentTypes.Add(typeof(T));
- return this;
- }
-
- ///
- public INexoSdkBuilder RegisterAgentCard(AgentCard card)
- {
- if (card == null)
- throw new ArgumentNullException(nameof(card));
- _options.AgentCards.Add(card);
- return this;
- }
-}
+using Nexo.Abstractions;
+using Nexo.Core.Domain.Agents;
+using Nexo.Core.Domain.Bricks;
+using Nexo.Infrastructure.Sdk.Ports;
+
+namespace Nexo.Hosting.Sdk;
+
+#pragma warning disable CS0618 // NexoSdkBuilder is an obsolete type forwarder in this file
+
+///
+/// Default implementation of . Configures for kernel registration.
+///
+public class HostNexoSdkBuilder : INexoSdkBuilder
+{
+ private readonly NexoSdkOptions _options;
+
+ ///
+ /// Creates a new SDK builder with the given options.
+ ///
+ /// Options to populate.
+ public HostNexoSdkBuilder(NexoSdkOptions options)
+ {
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+ }
+
+ ///
+ public INexoSdkBuilder RegisterBrick() where T : Brick
+ {
+ _options.BrickTypes.Add(typeof(T));
+ return this;
+ }
+
+ ///
+ public INexoSdkBuilder RegisterAgent() where T : class
+ {
+ if (!typeof(IAgent).IsAssignableFrom(typeof(T)))
+ {
+ throw new ArgumentException($"Type {typeof(T).Name} must implement {nameof(IAgent)}", nameof(T));
+ }
+
+ _options.AgentTypes.Add(typeof(T));
+ return this;
+ }
+
+ ///
+ public INexoSdkBuilder RegisterAgentCard(AgentCard card)
+ {
+ if (card == null)
+ {
+ throw new ArgumentNullException(nameof(card));
+ }
+
+ _options.AgentCards.Add(card);
+ return this;
+ }
+}
+
+///
+/// Back-compat type name for .
+///
+[Obsolete("Renamed to HostNexoSdkBuilder.", error: false)]
+public sealed class NexoSdkBuilder : HostNexoSdkBuilder
+{
+ ///
+ public NexoSdkBuilder(NexoSdkOptions options)
+ : base(options)
+ {
+ }
+}
diff --git a/src/Nexo.Hosting/Sdk/NexoSdkServiceCollectionExtensions.cs b/src/Nexo.Hosting/Sdk/Extensions/NexoSdkServiceCollectionExtensions.cs
similarity index 89%
rename from src/Nexo.Hosting/Sdk/NexoSdkServiceCollectionExtensions.cs
rename to src/Nexo.Hosting/Sdk/Extensions/NexoSdkServiceCollectionExtensions.cs
index cb76da6aa..d99263bb4 100644
--- a/src/Nexo.Hosting/Sdk/NexoSdkServiceCollectionExtensions.cs
+++ b/src/Nexo.Hosting/Sdk/Extensions/NexoSdkServiceCollectionExtensions.cs
@@ -1,45 +1,49 @@
-using Microsoft.Extensions.DependencyInjection;
-using Nexo.Abstractions;
-using Nexo.Core.Application.Sdk.Ports;
-using Nexo.Infrastructure.Adaptation;
-
-namespace Nexo.Hosting.Sdk;
-
-///
-/// Extension methods for SDK-based component registration.
-/// Call AddNexoSdk before AddNexo to register external bricks and agents at runtime.
-///
-public static class NexoSdkServiceCollectionExtensions
-{
- ///
- /// Configures the Nexo SDK builder for runtime registration of bricks and agents.
- /// Call before AddNexo(). Example:
- ///
- /// services.AddNexoSdk(sdk => sdk
- /// .RegisterBrick<MyBrick>()
- /// .RegisterAgent<MyAgent>()
- /// .RegisterAgentCard(myCard));
- /// services.AddNexo();
- ///
- ///
- /// The service collection.
- /// Action to configure the SDK builder.
- /// The service collection for chaining.
- public static IServiceCollection AddNexoSdk(
- this IServiceCollection services,
- Action configure)
- {
- var options = new NexoSdkOptions();
- configure(new NexoSdkBuilder(options));
-
- services.AddSingleton(options);
-
- if (options.BrickTypes.Count > 0)
- services.Configure(o => o.AdditionalBrickTypes.AddRange(options.BrickTypes));
-
- foreach (var agentType in options.AgentTypes)
- services.AddSingleton(typeof(IAgent), agentType);
-
- return services;
- }
-}
+using Microsoft.Extensions.DependencyInjection;
+using Nexo.Abstractions;
+using Nexo.Infrastructure.Adaptation;
+using Nexo.Infrastructure.Sdk.Ports;
+
+namespace Nexo.Hosting.Sdk;
+
+///
+/// Extension methods for SDK-based component registration.
+/// Call AddNexoSdk before AddNexo to register external bricks and agents at runtime.
+///
+public static class NexoSdkServiceCollectionExtensions
+{
+ ///
+ /// Configures the Nexo SDK builder for runtime registration of bricks and agents.
+ /// Call before AddNexo(). Example:
+ ///
+ /// services.AddNexoSdk(sdk => sdk
+ /// .RegisterBrick<MyBrick>()
+ /// .RegisterAgent<MyAgent>()
+ /// .RegisterAgentCard(myCard));
+ /// services.AddNexo();
+ ///
+ ///
+ /// The service collection.
+ /// Action to configure the SDK builder.
+ /// The service collection for chaining.
+ public static IServiceCollection AddNexoSdk(
+ this IServiceCollection services,
+ Action configure)
+ {
+ var options = new NexoSdkOptions();
+ configure(new HostNexoSdkBuilder(options));
+
+ services.AddSingleton(options);
+
+ if (options.BrickTypes.Count > 0)
+ {
+ services.Configure(o => o.AdditionalBrickTypes.AddRange(options.BrickTypes));
+ }
+
+ foreach (var agentType in options.AgentTypes)
+ {
+ services.AddSingleton(typeof(IAgent), agentType);
+ }
+
+ return services;
+ }
+}
diff --git a/src/Nexo.Hosting/OpenTelemetryServiceCollectionExtensions.cs b/src/Nexo.Hosting/Sdk/Extensions/OpenTelemetryServiceCollectionExtensions.cs
similarity index 97%
rename from src/Nexo.Hosting/OpenTelemetryServiceCollectionExtensions.cs
rename to src/Nexo.Hosting/Sdk/Extensions/OpenTelemetryServiceCollectionExtensions.cs
index b5f6d1633..08145e60c 100644
--- a/src/Nexo.Hosting/OpenTelemetryServiceCollectionExtensions.cs
+++ b/src/Nexo.Hosting/Sdk/Extensions/OpenTelemetryServiceCollectionExtensions.cs
@@ -1,39 +1,39 @@
-using Microsoft.Extensions.DependencyInjection;
-using Nexo.Core.Application.Common.Ports;
-using Nexo.Infrastructure.Metrics;
-using OpenTelemetry.Metrics;
-
-namespace Nexo.Hosting;
-
-///
-/// OpenTelemetry integration for Nexo. Call AddNexoOpenTelemetry() after AddNexo() to enable
-/// metrics export via OpenTelemetry (OTLP, Console, etc.).
-///
-public static class OpenTelemetryServiceCollectionExtensions
-{
- ///
- /// Adds OpenTelemetry metrics for Nexo and replaces IMetricsCollector with OpenTelemetryMetricsCollector.
- /// Call after AddNexo(). Optionally configure the MeterProviderBuilder (e.g. AddConsoleExporter, AddOtlpExporter).
- ///
- /// The service collection (after AddNexo).
- /// Optional action to configure the MeterProviderBuilder.
- /// The service collection for chaining.
- public static IServiceCollection AddNexoOpenTelemetry(
- this IServiceCollection services,
- Action? configure = null)
- {
- services.AddOpenTelemetry()
- .WithMetrics(m =>
- {
- m.AddMeter(OpenTelemetryMetricsCollector.MeterName);
- configure?.Invoke(m);
- });
-
- // Replace default MemoryMetricsCollector with OpenTelemetryMetricsCollector (last registration wins)
- services.AddSingleton(sp =>
- new OpenTelemetryMetricsCollector(
- sp.GetService>()));
-
- return services;
- }
-}
+using Microsoft.Extensions.DependencyInjection;
+using Nexo.Core.Application.Common.Ports;
+using Nexo.Infrastructure.Metrics;
+using OpenTelemetry.Metrics;
+
+namespace Nexo.Hosting;
+
+///