diff --git a/src/Marten.ScaleTesting/Commands/DaemonLoadCommand.cs b/src/Marten.ScaleTesting/Commands/DaemonLoadCommand.cs
new file mode 100644
index 0000000000..5edc8b92d0
--- /dev/null
+++ b/src/Marten.ScaleTesting/Commands/DaemonLoadCommand.cs
@@ -0,0 +1,421 @@
+using System.Diagnostics;
+using System.Text.Json;
+using JasperFx;
+using JasperFx.CommandLine;
+using JasperFx.Events;
+using JasperFx.Events.Projections;
+using Marten.Events;
+using Marten.Events.Projections;
+using Marten.ScaleTesting.Instrumentation;
+using Marten.Storage;
+using Npgsql;
+using Spectre.Console;
+
+namespace Marten.ScaleTesting.Commands;
+
+///
+/// daemonload: the jasperfx#486 WS2 measurement scenario. Everything else in this harness
+/// measures REBUILDS; this measures the steady-state RUNNING daemon — the deployment shape whose
+/// database connection footprint at ~100 tenants is the WS2 concern.
+///
+/// Flow:
+///
+/// - Build an isolated UseTenantPartitionedEvents store in its own schema with
+/// --projections async projections and a dedicated Application Name so
+/// pg_stat_activity can attribute every connection the store opens
+/// - Register --tenants tenants and start the projection daemon —
+/// StartAllAsync fans out one subscription agent per (projection × tenant)
+/// - Append events continuously across every tenant for --duration-seconds while
+/// sampling the store's connection count every --sample-seconds
+/// - Stop appending, wait for every tenant's agents to catch up to that tenant's own
+/// high-water ceiling, and report peak/mean connections + catch-up coverage
+///
+///
+/// The WS2 gate: connections should be O(databases), not O(tenant agents). Run this BEFORE and
+/// AFTER the daemon command-batching work lands to quantify the win; --max-connections
+/// turns the report into a pass/fail gate for regression runs.
+///
+[Description("WS2 (jasperfx#486): run the async daemon over N partitioned tenants under continuous append load and sample pg_stat_activity for the store's connection footprint.")]
+public sealed class DaemonLoadCommand: JasperFxAsyncCommand
+{
+ private const string Schema = "scaletest_daemonload";
+ private const string ApplicationName = "scaletest-daemonload";
+
+ public override async Task Execute(DaemonLoadInput input)
+ {
+ var totalElapsed = Stopwatch.StartNew();
+
+ AnsiConsole.MarkupLine(
+ $"[blue]daemonload: schema=[yellow]{Schema}[/] tenants=[yellow]{input.TenantsFlag}[/] " +
+ $"projections=[yellow]{input.ProjectionsFlag}[/] duration=[yellow]{input.DurationSecondsFlag}s[/] " +
+ $"rate=[yellow]~{input.AppendRatePerSecondFlag}/s[/][/]");
+
+ if (input.WipeFlag)
+ {
+ await WipeSchemaAsync().ConfigureAwait(false);
+ }
+
+ // Stamp our own Application Name so the sampler counts exactly the store's connections —
+ // daemon sessions, event loaders, appender sessions — and nothing else on the dev box.
+ var storeConnectionString = new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString)
+ {
+ ApplicationName = ApplicationName
+ }.ConnectionString;
+
+ var projectionNames = Enumerable.Range(0, Math.Max(1, input.ProjectionsFlag))
+ .Select(i => $"LoadRollup{i}")
+ .ToArray();
+
+ using var store = Marten.DocumentStore.For(opts =>
+ {
+ opts.Connection(storeConnectionString);
+ opts.DisableNpgsqlLogging = true;
+ opts.DatabaseSchemaName = Schema;
+ opts.Events.TenancyStyle = TenancyStyle.Conjoined;
+ opts.Events.UseTenantPartitionedEvents = true;
+ opts.Events.AppendMode = EventAppendMode.QuickWithServerTimestamps;
+ opts.Policies.AllDocumentsAreMultiTenanted();
+ opts.Events.AddEventType();
+
+ foreach (var name in projectionNames)
+ {
+ opts.Projections.Add(new DaemonLoadRollupProjection(name), ProjectionLifecycle.Async, name);
+ }
+ });
+
+ await store.Storage.ApplyAllConfiguredChangesToDatabaseAsync().ConfigureAwait(false);
+
+ var tenants = Enumerable.Range(0, input.TenantsFlag)
+ .Select(i => $"tenant_{i:0000}")
+ .ToArray();
+
+ AnsiConsole.MarkupLine($"[grey]Registering {tenants.Length} tenants (per-tenant partition DDL — this can take a bit)...[/]");
+ await store.Advanced.AddMartenManagedTenantsAsync(CancellationToken.None, tenants).ConfigureAwait(false);
+
+ // One seed event per tenant so every per-tenant sequence + partition exists before the
+ // daemon starts, and every (projection × tenant) agent has something to fan out for.
+ foreach (var tenant in tenants)
+ {
+ await using var session = store.LightweightSession(tenant);
+ session.Events.StartStream(Guid.NewGuid(), new DaemonLoadEvent(tenant, 0));
+ await session.SaveChangesAsync().ConfigureAwait(false);
+ }
+
+ using var cts = new CancellationTokenSource();
+ await using var sampler = ConnectionSampler.Start(
+ ConnectionSource.ConnectionString, ApplicationName,
+ TimeSpan.FromSeconds(Math.Max(0.1, input.SampleSecondsFlag)),
+ string.IsNullOrWhiteSpace(input.TraceFlag) ? null : input.TraceFlag,
+ cts.Token);
+
+ AnsiConsole.MarkupLine("[grey]Starting projection daemon (per-tenant agent fan-out)...[/]");
+ using var daemon = await store.BuildProjectionDaemonAsync().ConfigureAwait(false);
+ await daemon.StartAllAsync().ConfigureAwait(false);
+
+ // ---- Continuous append load ------------------------------------------
+
+ var appended = 0L;
+ var appendFailures = 0L;
+ var appendElapsed = Stopwatch.StartNew();
+ var writers = Enumerable.Range(0, Math.Max(1, input.WritersFlag))
+ .Select(w => Task.Run(() => AppendLoopAsync(store, tenants, w, input, cts.Token,
+ () => Interlocked.Increment(ref appended),
+ () => Interlocked.Increment(ref appendFailures))))
+ .ToArray();
+
+ await Task.Delay(TimeSpan.FromSeconds(input.DurationSecondsFlag)).ConfigureAwait(false);
+ cts.Cancel();
+ await Task.WhenAll(writers).ConfigureAwait(false);
+ appendElapsed.Stop();
+
+ // ---- Catch-up + verification ------------------------------------------
+
+ AnsiConsole.MarkupLine(
+ $"[grey]Appends stopped ({appended:N0} events). Waiting for per-tenant catch-up...[/]");
+ var (caughtUpTenants, stalledTenants) = await WaitForCatchUpAsync(
+ tenants, projectionNames, TimeSpan.FromSeconds(input.CatchUpTimeoutSecondsFlag))
+ .ConfigureAwait(false);
+
+ var connections = sampler.Capture();
+ await daemon.StopAllAsync().ConfigureAwait(false);
+
+ totalElapsed.Stop();
+
+ // ---- Report ------------------------------------------------------------
+
+ var appendRate = appended / Math.Max(0.001, appendElapsed.Elapsed.TotalSeconds);
+ var agentCount = tenants.Length * projectionNames.Length;
+
+ var table = new Table().AddColumn("Metric").AddColumn(new TableColumn("Value").RightAligned());
+ table.AddRow("Tenants", tenants.Length.ToString("N0"));
+ table.AddRow("Async projections", projectionNames.Length.ToString("N0"));
+ table.AddRow("Tenant agents (projections × tenants)", agentCount.ToString("N0"));
+ table.AddRow("Events appended", appended.ToString("N0"));
+ table.AddRow("Append failures", appendFailures.ToString("N0"));
+ table.AddRow("Sustained append rate (events/sec)", appendRate.ToString("N0"));
+ table.AddRow("Connection samples", connections.SampleCount.ToString("N0"));
+ table.AddRow("[bold]Peak connections[/]", $"[bold]{connections.MaxTotal:N0}[/]");
+ table.AddRow("Mean connections", connections.MeanTotal.ToString("N1"));
+ table.AddRow("Peak busy connections", connections.MaxBusy.ToString("N0"));
+ table.AddRow("Mean busy connections", connections.MeanBusy.ToString("N1"));
+ table.AddRow("Tenants caught up", $"{caughtUpTenants.Count:N0} / {tenants.Length:N0}");
+ table.AddRow("Total elapsed", $"{totalElapsed.Elapsed.TotalSeconds:N1}s");
+ AnsiConsole.Write(table);
+
+ if (stalledTenants.Count > 0)
+ {
+ AnsiConsole.MarkupLine(
+ $"[red]{stalledTenants.Count} tenant(s) did not catch up within {input.CatchUpTimeoutSecondsFlag}s: " +
+ $"{string.Join(", ", stalledTenants.Take(10))}{(stalledTenants.Count > 10 ? ", ..." : "")}[/]");
+ }
+
+ await WriteMetricsAsync(input, tenants.Length, projectionNames.Length, appended, appendRate,
+ connections, caughtUpTenants.Count, stalledTenants).ConfigureAwait(false);
+
+ var gatePassed = input.MaxConnectionsFlag <= 0 || connections.MaxTotal <= input.MaxConnectionsFlag;
+ if (!gatePassed)
+ {
+ AnsiConsole.MarkupLine(
+ $"[red]GATE FAILED: peak connections {connections.MaxTotal:N0} > --max-connections {input.MaxConnectionsFlag:N0}.[/]");
+ }
+
+ var healthy = appendFailures == 0 && stalledTenants.Count == 0;
+ if (!healthy)
+ {
+ AnsiConsole.MarkupLine("[red]Run unhealthy — see append failures / stalled tenants above.[/]");
+ }
+
+ return gatePassed && healthy;
+ }
+
+ private static async Task AppendLoopAsync(IDocumentStore store, string[] tenants, int writerIndex,
+ DaemonLoadInput input, CancellationToken token, Action onAppended, Action onFailure)
+ {
+ // Each writer owns an interleaved slice of the tenant ring so all writers together cover
+ // every tenant. Rate control: each writer targets rate/writers events/sec, appending in
+ // small per-tenant batches with a delay computed from the batch size.
+ var writerCount = Math.Max(1, input.WritersFlag);
+ var perWriterRate = Math.Max(1.0, (double)input.AppendRatePerSecondFlag / writerCount);
+ const int batchSize = 5;
+ var delay = TimeSpan.FromSeconds(batchSize / perWriterRate);
+
+ var sequence = 0;
+ var position = writerIndex;
+ while (!token.IsCancellationRequested)
+ {
+ var tenant = tenants[position % tenants.Length];
+ position += writerCount;
+
+ try
+ {
+ await using var session = store.LightweightSession(tenant);
+ var events = Enumerable.Range(0, batchSize)
+ .Select(_ => new DaemonLoadEvent(tenant, ++sequence))
+ .Cast