ADR-006: Execution Plugin Framework
Status: Accepted Date: 2026-05-25 Sprint: SPRINT_20260525_012_ReleaseOrchestrator_execution_plugin_framework.md Related ADRs: ADR-011 (EPF plugin service contract)
This record unifies the three kinds of out-of-process release work — build, scan, and deploy — behind a single, signed, isolated plugin contract hosted on the existing Plugin SDK. Read it before adding a deploy backend, a build step, or any new execution behaviour, or before touching the agent capability registry.
Context
Stella Ops performs three kinds of out-of-process work on behalf of a release: it builds artifacts, it scans them (SBOM, call-graph), and it deploys them. Today these are implemented by three unrelated mechanisms:
Deployment “capabilities” (
docker,compose,ssh,winrm,ecs,nomad) live insrc/ReleaseOrchestrator/__Agents/. They implementStellaOps.Agent.Core/Capability/IAgentCapability.csand are wired into the agent by a hardcodedswitchinsrc/ReleaseOrchestrator/__Agents/StellaOps.Agent.Host/Program.cs(CreateCapabilityRegistry). They are not discovered, not versioned, not signed, not isolated, and adding one requires editing the host.A container script executor in
src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Scripts/Execution/ScriptExecutor.csalready runs operator scripts in isolated, pooled Docker containers (IRuntimeImageManager,IContainerPoolManager) with deterministic capture (ScriptRawOutputProof). It is scoped only to “release scripts”.A general plugin SDK in
src/Plugin/(StellaOps.Plugin.Abstractions/IPlugin.cs,StellaOps.Plugin.Host/) provides per-pluginAssemblyLoadContextisolation, a v2 manifest (Manifest/PluginManifest.cs: capabilities, dependencies, resource limits, permissions, config schema, signature), three trust levels (PluginTrustLevel.cs: BuiltIn / Trusted / Untrusted with process + overlay-FS + AppArmor/JobObject sandboxing), and X.509+JOSE signature verification (StellaOps.Plugin.Host/Trust/). The Integrations subsystem already loads connectors through this SDK (IntegrationPluginLoader+IIntegrationConnectorPlugin). The deployment capabilities do not use it.
The result is duplicated execution infrastructure, an unsafe and non-extensible deployment capability model, and no single place to add new execution behaviours (e.g. a new deploy backend, a build step, a custom-language step) with isolation, signing, and resource limits.
Decision
Introduce a single Execution Plugin Framework: one plugin contract for build, scan, deploy, and verify steps, hosted on the existing Plugin SDK, with two interchangeable execution backends.
Layer 1 — Plugin runtime (reuse src/Plugin)
Execution plugins are ordinary Plugin SDK plugins: ALC-isolated, manifest-described, signature-verified, with manifest-declared capabilities, permissions, and resource limits, and a trust level. No new loader is introduced.
Layer 2 — Execution contract (new, small)
IExecutionPlugin : IPlugin
ExecutionKind Kind { get; } // Build | Scan | Deploy | Verify | Custom
IReadOnlyList<string> Capabilities { get; } // e.g. "deploy.compose", "deploy.ansible",
// "build.docker", "scan.sbom"
Task<ExecutionResult> ExecuteAsync(ExecutionContext ctx,
ExecutionInputs inputs,
CancellationToken ct); // streams progress/logs,
// returns result + raw-output proof
IExecutionPlugin generalises today’s IAgentCapability. The manifest declares the plugin’s input/output schema. Secrets are resolved by the host/orchestrator and handed to the plugin as scoped inputs — plugins never read ambient credentials.
Layer 3 — Two hosts, one contract
- In-process C# host (BuiltIn trust): the agent host loads execution plugins through the Plugin SDK (
FileSystemPluginDiscovery+PluginHost) and registers them intoCapabilityRegistry, replacing the hardcodedswitch. The existing capabilities (docker/compose/ssh/winrm/ecs/nomad) are migrated ontoIExecutionPlugin, behaviour-preserving. - Container host (multi-language, Trusted/Untrusted trust): the
ScriptExecutor/IRuntimeImageManager/IContainerPoolManagermachinery is generalised from “release scripts” into an execution-plugin backend so a plugin can be packaged as any container/image and authored in any language (bash, Python, .NET, …), run sandboxed, withScriptRawOutputProof.
Layer 4 — Orchestration
TargetExecutor selects the plugin by capability / task-type from the registry instead of a switch. Build and scan steps may be invoked from the scan pipeline or JobEngine through the same hosts.
Consequences
- New execution behaviours (new deploy backend, build step, custom-language step) are added as signed, isolated, resource-limited plugins with no host edits.
- Deployment gains the SDK’s isolation, signature verification, and trust model it lacks today.
- Multi-language execution is first-class via the container host — without a second execution stack.
- A new deploy backend (e.g. Ansible) is just an execution plugin; conversely, deploy behaviours can later be expressed as native plugins (compose/exec/artifact-extract), so any single external tool dependency can be retired without changing the framework.
- Migration risk: the capability migration must be behaviour-preserving. Existing deploy integration tests are the regression gate and must pass unchanged through the new plugin path.
- Backward compatibility:
IAgentCapabilitymay be retained as a thin shim overIExecutionPluginduring migration, or the agent’s internal registry adapted; either way external task-type strings and dispatch semantics are preserved.
Alternatives Considered
- Keep the hardcoded switch, add Ansible as another case. Rejected: no isolation/signing, not extensible, and duplicates the script-execution stack for any multi-language need.
- Build a new multi-language plugin protocol (gRPC/WASM/sidecar). Rejected for now: the container host already provides a language-agnostic, sandboxed, proof-producing boundary; a wire protocol can be added later under the same
IExecutionPlugincontract if needed. - Use JobEngine worker SDKs (Go/Python) as the plugin model. Rejected as the primary model: those are long-running workers that claim jobs, not discovered/signed plugins; they can still participate as a container-host execution target.
References
- Plugin SDK contract —
src/Plugin/StellaOps.Plugin.Abstractions/IPlugin.cs - Plugin manifest v2 —
src/Plugin/StellaOps.Plugin.Abstractions/Manifest/PluginManifest.cs - Trust levels —
src/Plugin/StellaOps.Plugin.Abstractions/PluginTrustLevel.cs - Plugin host + discovery + trust —
src/Plugin/StellaOps.Plugin.Host/ - Reference SDK consumer —
src/Integrations/StellaOps.Integrations.WebService/IntegrationPluginLoader.cs - Current capability contract/registry —
src/ReleaseOrchestrator/__Agents/StellaOps.Agent.Core/Capability/ - Current hardcoded registration —
src/ReleaseOrchestrator/__Agents/StellaOps.Agent.Host/Program.cs - Container execution substrate —
src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Scripts/Execution/ScriptExecutor.cs - Deployment dispatch —
src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Deployment/Executor/TargetExecutor.cs
