Octopus
DI and Services
Plugins use standard ASP.NET Core dependency injection to register and consume services. This page covers service lifetime recommendations, how plugins share services, and how to override built-in plugin services with custom implementations.
Service Lifetimes in Plugins
| Lifetime | When to Use | Example |
|---|---|---|
| Singleton | Stateless services or services with expensive initialisation | MCPToolRegistry, IEmbeddingProvider, connection pools |
| Scoped | Per-request services; services that hold request state | OctopusDbContext, ITenantContext, IAgentStore |
| Transient | Lightweight, stateless services | Validators, 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
| Service | Provided By | Purpose |
|---|---|---|
MCPToolRegistry | Octopus Core | Register and execute MCP tools |
ICredentialResolver | Octopus Core | Retrieve secrets by credentialId |
ITokenCounter | Octopus Core | Count tokens for text strings |
ITenantContext | Octopus Core | Current request's tenant ID |
ILLMProviderFactory | Octopus Core | Resolve ILLMProvider by config |