Managing Plugin Resources in FabrCore

Eric Brasher February 21, 2026 at 9:48 AM 4 min read

Plugins that open HTTP connections, file handles, or database connections need cleanup. FabrCore uses the standard .NET disposal pattern — no custom lifecycle hooks required.

The Standard Pattern

If your plugin acquires resources during InitializeAsync, implement IDisposable or IAsyncDisposable. FabrCore checks for these interfaces and calls them when the agent grain deactivates:

WebBrowserPlugin.cs
[PluginAlias("WebBrowser")]
public class WebBrowserPlugin : IFabrCorePlugin, IAsyncDisposable
{
    private HttpClient? _httpClient;

    public Task InitializeAsync(
        AgentConfiguration config,
        IServiceProvider serviceProvider)
    {
        var timeout = config.GetPluginSetting(
            "WebBrowser", "TimeoutMs");
        _httpClient = new HttpClient
        {
            Timeout = TimeSpan.FromMilliseconds(
                int.TryParse(timeout, out var ms)
                    ? ms : 30000)
        };
        return Task.CompletedTask;
    }

    public ValueTask DisposeAsync()
    {
        _httpClient?.Dispose();
        return ValueTask.CompletedTask;
    }
}

When Disposal Happens

FabrCore agents are Orleans grains. When a grain deactivates — due to idle timeout, explicit deactivation, or silo shutdown — the framework disposes all plugins that implement the disposal interfaces. The disposal order matches the reverse of initialization order.

This means your cleanup code runs in the same context as your initialization code. No separate lifecycle callback is needed.

Why Not OnBeforeMessage / OnAfterMessage?

Some teams request per-message lifecycle hooks like OnBeforeMessage or OnAfterMessage for plugins. Here's why FabrCore doesn't include these:

Plugins provide tools, not middleware. A plugin's job is to expose tools that the agent can call. Per-message interception is an agent concern, not a plugin concern. If you need to run code before or after every message, put that logic in your agent's OnMessage override.

Resource pooling is an application pattern. If a plugin needs to manage a connection pool or rate limiter across messages, that's standard .NET resource management — use IHttpClientFactory, semaphores, or whatever fits your scenario. These are DI-registered services, not plugin lifecycle hooks.

Agent-Level Message Interception
public override async Task<AgentMessage> OnMessage(
    AgentMessage message)
{
    // Pre-message logic belongs here, not in plugins
    logger.LogInformation("Processing: {Type}",
        message.MessageType);

    var result = await base.OnMessage(message);

    // Post-message logic
    logger.LogInformation("Completed: {Type}",
        message.MessageType);

    return result;
}

Learn More

Check out the plugin lifecycle documentation for the full reference on plugin initialization and disposal.


Eric Brasher

Builder of FabrCore and OpenCaddis.