Hosting plugins externally
Start with the template
Section titled “Start with the template”If you want to run your custom-task plugins in their own deployable (separate from the engine
image) — see the Hosting Plugins (Custom Worker Host) guide.
The fleans-custom-worker-example
GitHub template is the supported starting point; it references only Fleans.Worker
(via NuGet) + plugin assemblies, with no Fleans.Application / Fleans.Domain in the
dependency closure.
Overview
Section titled “Overview”The recommended deployment pattern for non-trivial plugin estates is to run plugins in their
own silo process, separate from the engine’s Fleans.Api / Fleans.Web / Fleans.WorkerHost.
When to host externally
Section titled “When to host externally”- Operational isolation. A misbehaving plugin (memory leak, runaway HTTP call) only kills its own silo, never the engine workers that run Script / Condition grains.
- Independent deploy cadence. Ship a new version of an
emailorslackplugin without rebuilding or restarting the engine. - Per-team ownership. Different teams can each ship their own plugin host image; only
Fleans.Worker(from nuget.org) is shared across them — no engine source dependency.
The Plugin role
Section titled “The Plugin role”Fleans:Role accepts three values cluster-wide. The third — Plugin — was added for
external plugin hosts:
| Role | Silo prefix | Purpose |
|---|---|---|
Core | core- | Engine API / Web / Mcp silos. Hosts [CorePlacement] grains. |
Worker / Combined | worker- / combined- | Engine workers (and Combined dev silos). Hosts [WorkerPlacement] grains. |
Plugin | plugin- | External plugin hosts. Hosts only plugin grains registered via AddCustomTaskPlugin<T>(). |
Engine binaries (Fleans.Api, Fleans.WorkerHost) reject Fleans:Role=Plugin at startup
with an explicit message — the Plugin role is reserved for hosts using the
AddFleansPluginHost helper.
The one-liner
Section titled “The one-liner”External hosts call a single extension method to wire up role validation, silo naming, and default placement:
using Fleans.Worker.Hosting;using Fleans.Plugins.MyThing;using StackExchange.Redis;
var builder = WebApplication.CreateBuilder(args);builder.AddServiceDefaults();builder.AddKeyedRedisClient("orleans-redis");
var orleansRedis = builder.Configuration.GetConnectionString("orleans-redis");
builder.UseOrleans(siloBuilder =>{ siloBuilder.AddFleansPluginHost(builder.Configuration); // role + silo name + placement director
if (!string.IsNullOrEmpty(orleansRedis)) { siloBuilder.UseRedisClustering(orleansRedis); siloBuilder.AddRedisGrainStorage("PubSubStore", opt => opt.ConfigurationOptions = ConfigurationOptions.Parse(orleansRedis)); siloBuilder.UseInMemoryReminderService(); }
siloBuilder.AddFleanStreaming(builder.Configuration); // matches engine stream provider});
builder.Services.AddMyThingPlugin();var app = builder.Build();app.Run();AddFleansPluginHost:
- Validates
Fleans:RoleisPlugin(recommended) orCombined. RejectsCore/WorkerwithInvalidOperationException. - Stamps the silo name as
plugin-<machine>-<guid>so other silos see the prefix via Orleans membership. - Registers
WorkerPlacementDirectorso the plugin silo participates correctly in cluster-wide placement decisions for[WorkerPlacement]grains it doesn’t host.
Everything else — Redis clustering, PubSubStore, stream provider, Fleans.ServiceDefaults —
is wired separately. The minimal Program.cs above (AddKeyedRedisClient, UseRedisClustering,
AddFleanStreaming) is the complete operator-facing setup. Match the engine’s Redis connection
string so plugin grains share the same Orleans cluster.
The isolation guarantee
Section titled “The isolation guarantee”A plugin host runs only the plugin grain classes compiled into its assembly load context. The isolation falls out of two Orleans mechanisms:
- Grain class discovery. Orleans’
IPlacementContext.GetCompatibleSilosautomatically excludes silos that don’t have the concrete grain type loaded. Aplugin-*host that doesn’t referenceFleans.Plugins.RestCalleris not a candidate forRestCallerHandleractivations — Orleans never even considers it. WorkerPlacementDirectorrejectsplugin-prefix. Engine-internal grains marked with[WorkerPlacement](ScriptExecutorGrain,ConditionExpressionEvaluatorGrain) route only to silos whose name starts withworker-orcombined-. Theplugin-prefix is explicitly excluded.
The net effect: a plugin-* silo carrying only an EmailHandler plugin grain will host
exactly that, nothing else. Engine workers continue to host Script / Condition grains plus
any engine-bundled plugins (e.g. Fleans.Plugins.RestCaller shipped with Fleans.WorkerHost).
Scaffolding a plugin host
Section titled “Scaffolding a plugin host”The fleans-custom-worker-example
repository is marked as a GitHub template. Click Use this template to scaffold a new
plugin-host project pre-wired with AddFleansPluginHost, Redis client, and a Program.cs
shaped for your own plugin packages. Reference your plugin NuGet packages, call their
AddXxxPlugin() extensions in Program.cs, and build the container image — the resulting
image deploys alongside the engine’s fleans-{api,web,worker,mcp} containers.