Portal Community

How the Credential Resolver Works

1
appsettings.json contains only the credential ID "ConnectionStringCredentialId": 1 — an integer reference to the credential store
2
OnStartAsync resolves the actual connection string ICredentialResolver.GetPasswordAsync(credentialId) fetches the value from the encrypted credential store
3
DbContext is configured with the resolved connection string The EF Core DbContextOptions are built dynamically with the live connection string
4
Connection verified before accepting requests A lightweight CanConnectAsync() call confirms the server is reachable before startup completes

SqlServerPlugin OnRegister / OnStartAsync Internals

public class SqlServerPlugin : IOctopusPlugin
{
    private SqlServerPluginConfig _config = null!;

    public void OnRegister(IServiceCollection services, OctopusConfig config)
    {
        // Bind config — no connection string here, just the credential ID
        _config = config.GetSection<SqlServerPluginConfig>("SqlServerPlugin");

        services.AddOptions<SqlServerPluginConfig>()
            .Bind(config.Configuration.GetSection("SqlServerPlugin"))
            .ValidateDataAnnotations()
            .ValidateOnStart();

        // Register DbContext with a factory so the connection string is resolved lazily
        services.AddDbContextFactory<OctopusDbContext>((sp, opt) =>
        {
            // The connection string is injected at factory invocation time, not here
        });

        // Register storage implementations
        services.AddScoped<IEpisodicMemoryStore, SqlEpisodicMemoryStore>();
        services.AddScoped<IProceduralMemoryStore, SqlProceduralMemoryStore>();
        services.AddScoped<IAgentStore, SqlAgentStore>();
        services.AddScoped<IAIFunctionStore, SqlAIFunctionStore>();
        services.AddScoped<IAreaStore, SqlAreaStore>();
    }

    public async Task OnStartAsync(IServiceProvider sp, CancellationToken ct)
    {
        // Resolve the connection string from the credential store
        var credResolver = sp.GetRequiredService<ICredentialResolver>();
        var connectionString = await credResolver.GetPasswordAsync(_config.ConnectionStringCredentialId);

        // Configure the DbContext factory with the live connection string
        var optionsFactory = sp.GetRequiredService<IDbContextOptionsConfigurator<OctopusDbContext>>();
        optionsFactory.Configure(opt =>
            opt.UseSqlServer(connectionString, sql =>
            {
                sql.CommandTimeout(_config.CommandTimeoutSeconds);
                if (_config.EnableRetryOnFailure)
                    sql.EnableRetryOnFailure(_config.MaxRetryCount,
                        TimeSpan.FromSeconds(_config.MaxRetryDelaySeconds), null);
            }));

        // Verify connectivity
        using var scope = sp.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<OctopusDbContext>();
        if (!await db.Database.CanConnectAsync(ct))
            throw new InvalidOperationException(
                $"Cannot connect to Octopus SQL Server (credential {_config.ConnectionStringCredentialId}).");

        // Apply pending migrations
        if (_config.RunMigrationsOnStartup)
            await db.Database.MigrateAsync(ct);
    }

    public Task OnStopAsync(CancellationToken ct) => Task.CompletedTask;
}

Supported Connection String Formats

ScenarioConnection String Example
Windows Integrated AuthServer=sql01;Database=OctopusAI;Integrated Security=True;TrustServerCertificate=True
SQL Server AuthServer=sql01;Database=OctopusAI;User Id=octopus_app;Password=***;TrustServerCertificate=True
Azure SQL (managed identity)Server=myserver.database.windows.net;Database=OctopusAI;Authentication=Active Directory Managed Identity
Azure SQL (connection string)Server=tcp:myserver.database.windows.net,1433;Database=OctopusAI;User Id=octopus@myserver;Password=***;Encrypt=True
SQL Express (dev)Server=(localdb)\mssqllocaldb;Database=OctopusAI;Trusted_Connection=True
Never put the connection string in appsettings.json. Only the integer ConnectionStringCredentialId belongs in config. The actual connection string, including the password, must live exclusively in the encrypted credential store.

Connection Pooling

EF Core uses ADO.NET connection pooling by default. The default pool size of 100 connections is appropriate for most deployments. For high-concurrency scenarios adjust the pool size in the connection string:

// Added to the connection string stored in the credential:
// Max Pool Size=200;Min Pool Size=5;Connection Timeout=30

Retry Policy

When EnableRetryOnFailure is true, the plugin applies SQL Server transient error retry via EF Core's built-in strategy. Retried error codes include connection failures, deadlocks, and timeout errors. The retry uses exponential backoff with jitter up to MaxRetryDelaySeconds.