Repository navigation
Description
RFC #4 — Plugin discovery and load model
Self-RFC. Pins the runtime mechanics: how plugins get found, when they load, and what trust posture the host assumes.
Context
RFC #97 fixes the contract (assembly attribute + IFalloutPlugin). This RFC fixes the mechanics — when the host looks for plugins, what happens if one is broken, and whether plugins run isolated or full-trust.
Proposed model
Discovery: opt-in via assembly attribute
At startup the host enumerates loaded assemblies and reads [assembly: FalloutPlugin(typeof(...))] from each. Only assemblies declaring the attribute incur scanning cost. No type-walking, no GetAssemblies().SelectMany(a => a.GetTypes()) — the attribute is a single read.
Load timing: before phase 1 (Initializing)
Discovery and IFalloutPlugin.Configure(builder) run before the orchestrator starts Initializing. The composition root is built and frozen before any middleware runs:
- Plugins can register middleware, hosts, parameter sources — anything the phases consume.
- Plugins cannot alter the composition root mid-build. (If runtime reconfiguration is ever needed, ship a new extension point; don't unfreeze the container.)
Caching
Discovery results (assembly hash → plugin types) cache at <root>/.build/plugin-cache.json. Cache key is the hash of every referenced assembly. Hits skip the attribute read; misses re-scan and update. Saves ~20–100ms on later builds.
Trust model: same as Roslyn analyzers
Plugins run in-process, full trust. A malicious or buggy plugin can call Environment.Exit(0), touch the filesystem, read env vars, make network calls — same as Roslyn analyzers, MSBuild SDKs, source generators. The right tradeoff:
- The user already added the plugin via
PackageReference— a trust decision. - In-process .NET sandboxing is hard and incomplete (
AppDomains deprecated; collectibleAssemblyLoadContexts isolate lifetime, not capability). - Out-of-process (subprocess per plugin) adds 10–100ms per call and breaks debugging.
The host documents this in the author guide and consumer FAQ. No engineering around it.
Optional: collectible AssemblyLoadContext
If hot-reloading plugins in a long-running daemon ever matters, load each into a collectible AssemblyLoadContext. Defer to a future RFC unless someone surfaces the use case here.
Error handling
- Attribute points at a type that doesn't implement
IFalloutPlugin→ throw at startup with the assembly + offending type name. Configurethrows → throw, wrapping the inner exception with "plugin {name} failed to configure". Build stops.- Plugin requires a newer
PluginApiVersionthan the host → throw with the version mismatch.
Fail-fast rationale: silently skipping a broken plugin produces "why doesn't my plugin work" tickets that are hard to debug. Loud, clear failure is kinder.
Questions for discussion
- Deterministic load order? Default: yes — alphabetical by package ID. Predictable beats clever. Disagree?
- Load-order dependencies between plugins (
[FalloutPlugin(LoadAfter = "...")])? Default: no — extension points are unordered within a phase; cross-phase order is fixed. Need another plugin's services? Resolve via DI like any consumer. - Ship
dotnet fallout plugins list? Default: yes, useful for debugging. Belongs inFallout.GlobalTool.
Out of scope (other RFCs)
- Plugin contract — RFC [RFC #1] Plugin contract shape — IFalloutPlugin discovery & registration #97.
- Extension points — RFC [RFC #2] Extension-point catalogue for v12 SDK #98.
- SDK versioning — RFC [RFC #3] Fallout.Plugin.Sdk versioning policy #99.
- Conflicts between plugins — RFC [RFC #5] Conflict resolution semantics between plugins #101.
How to engage
Use cases that don't fit this load model are most valuable. "I want plugin X to register a new tool wrapper at minute 3 of the build" tells us the freeze-after-Configure decision is wrong.
Decision lands in
When this RFC locks (2026-08-31), its shape becomes the spec for:
- [SDK-3] Plugin discovery & load pipeline #148 — [SDK-3] Plugin discovery & load pipeline
Activity
ChrisonSimtian commentedon May 25, 2026
Self-decision lock-in date: 2026-08-31
This is a self-RFC (per the roadmap) — it ships an opinionated proposal and asks "tell me where I'm wrong." Currently zero comments.
If no substantive counter-proposal lands by 2026-08-31, the proposed discovery/load model locks, gets handed to the implementing SDK issue (#148 — plugin discovery & load pipeline), and this RFC closes as decided.
The most useful counter-example shape is: "I'd want to load a plugin from <transport / source / trust-tier> and the proposed model doesn't allow it." Pinning down the discovery surface (NuGet only? loose DLLs? signed-only?) and the trust model is the highest-stakes call in this RFC — push on it.