# p0149: migrate DI to feature-set extension methods

- [Architecture] Each feature-set's `AddXxx()` extension method lives in the project that holds the services it registers, in its own file under the feature's folder. Composition root (`AddAgentSmithCommands` / `AddAgentSmithInfrastructure` / Server's `Program.cs`) is now a flat list of `services.AddXxx()` calls — removing one call removes one feature-set; the program still compiles. Matches the RHS.Common.Commands pattern.
- [Architecture] Application split into ten feature-set extensions: `AddCommandExecutor` (Services/), `AddPromptCatalog` (Prompts/), `AddConfigurationValidation` (Services/Configuration/), `AddPipelineHandlers` (Services/Handlers/), `AddSkillRunHandlers` (Services/Handlers/), `AddSkillRoundInfrastructure` (Services/SkillRounds/), `AddContextBuilders` (Services/Builders/), `AddPipelineExecution` (Services/Pipeline/), `AddLoopRuntime` (Services/Loop/), `AddSwaggerCompression` (Services/), `AddWebhookCommentIntent` (Webhooks/). The 475-line `Extensions/ServiceCollectionExtensions.cs` shrank to a 30-line composition root.
- [Architecture] Infrastructure split into nine feature-set extensions: `AddTicketProviders` (Providers/Tickets/), `AddSourceProviders` (Providers/Source/), `AddAgentProviders` (Providers/Agent/, includes chat-client builders + loop-limits), `AddOutputStrategies` (Output/), `AddContainerRunners` (Containers/), `AddSecurityScanners` (Security/), `AddDialogueTransport` (Dialogue/), `AddProjectMeta` (Services/). The composition root shrank from 52 lines to ~25.
- [Architecture] Server's per-feature-set extensions sit under `Services/<feature>/` but keep `namespace AgentSmith.Server.Extensions` so `Program.cs` and tests pick up every extension method with a single `using AgentSmith.Server.Extensions;`. This honours the "no wrapper shims" rule — the file *location* matches the spec (next to the services), and the namespace is the single import point. The composition root is a marker class plus the flat `Program.cs` call chain.
- [Architecture] Server feature-sets: `AddRedis` (Services/RedisExtensions.cs), `AddCoreDispatcherServices` + `AddServerCompositionOverrides` (Services/DispatcherExtensions.cs), `AddLongRunningServices` (Services/Hosting/PollingExtensions.cs), `AddIntentEngine` + `AddIntentHandlers` (Services/Handlers/IntentExtensions.cs), `AddWebhookHandlers` (Services/Webhooks/WebhookEndpointsExtensions.cs), `AddSlackAdapter` + `AddTeamsAdapter` (Services/Adapters/ChatAdaptersExtensions.cs), `AddSandbox` + the three sandbox/orchestrator config-binding methods (Services/Sandbox/ServerSandboxExtensions.cs), `AddJobSpawnerOptions` (Services/JobSpawnerOptionsExtensions.cs).
- [Architecture] The seven dormant p0147h partials in `Application/Extensions/ServiceCollectionExtensions.*.cs` (BootstrapHandlers, ContextBuilders, PipelineHandlers, PipelineRuntime, SecurityHandlers, SkillRuntime, TriageServices) were never wired into AddAgentSmithCommands — they duplicated the registrations the monolith already carried. Deleted as part of this phase per the YAML's `drop-dormant-partials` step.
- [Implementation] `CheckoutSourceHandler`, `TryCheckoutSourceHandler`, `PipelineNameInitializerHandler`, `BootstrapCheckHandler`, `PublishProjectLanguageHandler` are triple-registered (concrete + `ICommandHandler<TContext>` + `IConceptWriter` singleton-of-handler). Extracted to a generic `AddConceptPublishingHandler<THandler, TContext>` helper in `PipelineHandlersExtensions` so the five repeated three-line blocks fold to one-line calls. Constraint is `where TContext : ICommandContext` (matching `ICommandHandler<TContext>`'s upstream constraint).
- [Implementation] `SkillRoundDispatcher`'s twin registration (concrete transient + `ISkillRoundDispatcher` factory) + IConceptWriter singleton stays the same shape — the dispatcher is still both a per-handler-instance dispatcher and the `tool_set_size` concept emitter from p0148. Moved into `SkillRoundInfrastructureExtensions` next to the dispatcher class.
- [Implementation] `PipelineExecutorLegacy` registration kept intact behind the `PIPELINE_EXECUTOR_USE_LEGACY` env flag — p0149 is a pure DI-shape refactor, not a service-lifecycle change. p0150 is slated to delete the legacy class entirely.
- [Implementation] The `FindConfigFile` helper moved from the deleted `ServiceCollectionExtensions.ConfigLoading.cs` partial into `SecurityScannersExtensions.FindConfigFile` (it's the only feature-set that loads YAML configs at registration time). The one external caller — `SpectralSpawner.GetRulesetPath` — was updated to reference the new home.
- [Implementation] `SandboxBackend` enum + `SandboxBackendInfo` record kept in `namespace AgentSmith.Server.Extensions` (not `Server.Services.Sandbox`) because tests in `tests/AgentSmith.Tests/Sandbox/SandboxBackendDetectionTests.cs` import the types unqualified through the existing `using AgentSmith.Server.Extensions;`. Moving the namespace would force a test-file edit unrelated to this phase's intent.
- [Implementation] The per-backend Kubernetes/Docker sandbox factory registrations were extracted from the 131-line `ServerSandboxExtensions.cs` into a sibling `SandboxBackendRegistrations.cs` so the main extensions file stays under the 120-line class-size limit (final: ~78 lines).
- [TradeOff] `AddPipelineHandlers` is 81 lines — over the 60-line spec target but under the 120-line hard limit. The YAML's PipelineHandlers list (line 23-30 of the spec) explicitly bundles ~55 handler registrations including bootstrap + triage + security SpawnX. Splitting further would create per-handler-flavour extension files for parallel agents to edit, but that's exactly the "20 micro-extensions for one feature" anti-pattern the spec is steering away from. Kept the single feature-set file and used a helper method (`AddConceptPublishingHandler`) to compress the repeated triple-registration pattern.
- [TradeOff] Default-lifetime audit: every registration carried into the new extension files keeps its prior lifetime. The YAML's `lifetime-audit` step prescribes flipping Singletons to Transient unless cross-call state is genuinely shared. Spot audit on the registrations carried into the new shape shows: the remaining `AddSingleton` calls are either (a) stateless analyzers / parsers / builders deliberately shared as a single instance (`KnowledgePromptBuilder`, `OutcomeClassifier`, `JsonSchemaLoader`, `SinglePhaseCollapser`, the activation pipeline), (b) configuration-derived singletons (`LoopLimitsConfig`, `JobSpawnerOptions`), (c) Redis-resource holders (`IConnectionMultiplexer`, `IRedisJobQueue`, `IRedisClaimLock`), (d) hosted services that must be singletons by ASP.NET contract, or (e) the `IConceptWriter` family which the registry consumes as a `List<IConceptWriter>` once at boot. No registration triggered a flip in this phase; the audit confirmed the current shape is justified. p0150 can carry the flip-to-Transient sweep alongside its service decomposition pass.
- [TradeOff] Considered making the Server feature-set files' namespaces match their folder (`AgentSmith.Server.Services.Webhooks`, `…Hosting`, `…Adapters`) for consistency with Application/Infrastructure. Rejected because Server has 9 callers of these extensions (Program.cs + 1 test file) — switching each to import 6 new namespaces produces a churn footprint disproportionate to the readability gain. The single-namespace re-export is the cheaper consistency-vs-churn trade.
