Octopus
Plugin Lifecycle
Plugin lifecycle methods are called in a well-defined sequence during application startup and shutdown. Understanding the lifecycle is essential for writing reliable plugins that initialise in the correct order and clean up properly.
Startup Sequence
1
AddOctopus() called in Program.cs
The DI container configuration begins. Octopus core services are registered.
2
Plugin.OnRegister() called for each plugin (in registration order)
Each plugin registers its services. The DI container is not yet built — services cannot be resolved.
3
DI container built (builder.Build())
All registered services are compiled into the service provider.
4
Plugin.OnStartAsync() called for each plugin (in registration order)
Plugins resolve services, apply migrations, register tools. Application is not yet accepting requests.
5
Application starts accepting requests
All plugins fully initialised. Agents can handle conversations.
Shutdown Sequence
1
Shutdown signal received
SIGTERM, Ctrl+C, or host shutdown. Application stops accepting new requests.
2
In-flight requests drain
Active conversations complete (up to graceful shutdown timeout, default 30s).
3
Plugin.OnStopAsync() called in reverse registration order
Plugins clean up in reverse order to dependency order. Last-registered plugin stops first.
4
DI container disposed
All IDisposable singleton services are disposed.
OnStartAsync — Common Patterns
public async Task OnStartAsync(IServiceProvider sp, CancellationToken ct)
{
// Pattern 1: Apply EF Core migrations
using var scope = sp.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<OctopusDbContext>();
await db.Database.MigrateAsync(ct);
// Pattern 2: Register MCP tools (must happen before first request)
var registry = sp.GetRequiredService<MCPToolRegistry>();
registry.Register(new MCPTool { ... });
// Pattern 3: Create vector collection if it doesn't exist
var qdrant = sp.GetRequiredService<IQdrantClient>();
await EnsureCollectionsExistAsync(qdrant, ct);
// Pattern 4: Warm up a cache or connection
var cache = sp.GetRequiredService<IMyCache>();
await cache.WarmUpAsync(ct);
}
OnStopAsync — Clean Shutdown
public async Task OnStopAsync(CancellationToken ct)
{
// Flush any buffered writes
await _writer.FlushAsync(ct);
// Close external connections gracefully
await _externalClient.DisconnectAsync(ct);
// Signal background tasks to stop
_backgroundCts.Cancel();
await _backgroundTask; // Wait for completion
// Dispose resources
_qdrantClient?.Dispose();
}