M4: agent-run job queue + worker drain (Increment 1)

SharedKernel: IWorkerModule seam (RegisterWorker runs in the worker host only).
Bootstrap: AddTeamUpWorkerServices; the worker host now wires it.

Assembler module (schema "assembler", InitialAssembler migration):
- Job (Pending→Processing→Done/Failed) + AgentRun (Queued→Running→Completed/Failed) entities.
- JobQueue: enqueue + ClaimNextAsync using `FOR UPDATE SKIP LOCKED` in a transaction.
- AgentRunExecutor (Increment-1 placeholder — real assemble/model/parse lands in Increment 2).
- JobProcessor BackgroundService drains the queue on the worker host (web off the model path).
- POST /api/assembler/runs enqueues a run; GET /api/assembler/runs/{id} reads it.

Verified: build green; ArchitectureTests 8/8 (Assembler references only SharedKernel);
IntegrationTests 28/28 incl. enqueue→claim(SKIP LOCKED)→process→Completed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-10 01:16:37 +03:30
parent 34ea407e86
commit 09eaf360a3
18 changed files with 906 additions and 17 deletions
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TeamUp.SharedKernel.Modularity;
namespace TeamUp.Bootstrap;
@@ -29,4 +30,20 @@ public static class TeamUpModuleExtensions
return endpoints;
}
/// <summary>Runs <c>RegisterWorker</c> for modules with background services. WORKER host only.</summary>
public static IServiceCollection AddTeamUpWorkerServices(
this IServiceCollection services,
IConfiguration configuration)
{
foreach (var module in ModuleCatalog.All)
{
if (module is IWorkerModule workerModule)
{
workerModule.RegisterWorker(services, configuration);
}
}
return services;
}
}