Portal Community

Service Lifetimes in Plugins

LifetimeWhen to UseExample
SingletonStateless services or services with expensive initialisationMCPToolRegistry, IEmbeddingProvider, connection pools
ScopedPer-request services; services that hold request stateOctopusDbContext, ITenantContext, IAgentStore
TransientLightweight, stateless servicesValidators, formatters, simple calculators

Registering Services in OnRegister

public void OnRegister(IServiceCollection services, OctopusConfig config)
{
    // Scoped services (per HTTP request)
    services.AddScoped<ILeaveService, LeaveService>();
    services.AddScoped<IEmployeeRepository, EmployeeRepository>();

    // Singleton (shared across all requests)
    services.AddSingleton<ILeaveCalculator, LeaveCalculator>();

    // HttpClient with typed client pattern
    services.AddHttpClient<IHRSystemClient, HRSystemClient>(client =>
    {
        client.BaseAddress = new Uri(config.Configuration["HRSystem:BaseUrl"]!);
    });

    // Options pattern
    services.Configure<HRPluginOptions>(
        config.Configuration.GetSection("HRPlugin"));

    // DbContext for this plugin's tables
    services.AddDbContext<HRDbContext>(opt =>
        opt.UseSqlServer(config.Configuration.GetConnectionString("Default")));
}

Resolving Services in OnStartAsync

public async Task OnStartAsync(IServiceProvider sp, CancellationToken ct)
{
    // For scoped services: create a scope
    using var scope = sp.CreateScope();
    var db = scope.ServiceProvider.GetRequiredService<HRDbContext>();
    await db.Database.MigrateAsync(ct);

    // For singleton services: resolve directly
    var registry = sp.GetRequiredService<MCPToolRegistry>();
    var leaveCalc = sp.GetRequiredService<ILeaveCalculator>();

    registry.Register(new MCPTool
    {
        Schema  = LeaveToolDefinition,
        Handler = (input, ctx, token) => HandleLeaveAsync(input, ctx, leaveCalc, token)
    });
}

Overriding Built-In Plugin Services

// To replace the built-in IEpisodicMemoryStore with a custom implementation:
// 1. Register SqlServerPlugin first (it registers the default)
config.AddPlugin<SqlServerPlugin>();

// 2. In your custom plugin's OnRegister, override:
public void OnRegister(IServiceCollection services, OctopusConfig config)
{
    // This replaces the last registered IEpisodicMemoryStore
    services.AddScoped<IEpisodicMemoryStore, MyCustomEpisodicStore>();
}

// The DI container uses the last registration for an interface
// SqlServerPlugin registers first, your plugin overrides it

Core Services Available to All Plugins

ServiceProvided ByPurpose
MCPToolRegistryOctopus CoreRegister and execute MCP tools
ICredentialResolverOctopus CoreRetrieve secrets by credentialId
ITokenCounterOctopus CoreCount tokens for text strings
ITenantContextOctopus CoreCurrent request's tenant ID
ILLMProviderFactoryOctopus CoreResolve ILLMProvider by config