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:
@@ -0,0 +1,47 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeamUp.Modules.Assembler.Domain;
|
||||
using TeamUp.Modules.Assembler.Persistence;
|
||||
using TeamUp.Modules.Assembler.Queue;
|
||||
using TeamUp.Modules.Assembler.Runtime;
|
||||
using TeamUp.SharedKernel.Modularity;
|
||||
|
||||
namespace TeamUp.Modules.Assembler.Endpoints;
|
||||
|
||||
internal static class AssemblerEndpoints
|
||||
{
|
||||
public static void Map(IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
var group = endpoints.MapGroup("/api/assembler").WithTags("Assembler");
|
||||
|
||||
group.MapGet("/ping", () => TypedResults.Ok(new ModulePing("assembler")));
|
||||
group.MapPost("/runs", CreateRun).RequireAuthorization();
|
||||
group.MapGet("/runs/{id:guid}", GetRun).RequireAuthorization();
|
||||
}
|
||||
|
||||
// Dispatch a task to an AI seat: record a queued AgentRun and enqueue the job. The worker
|
||||
// drains it off the request path. (Scope-checking the seat's team is added in Increment 2.)
|
||||
private static async Task<IResult> CreateRun(
|
||||
CreateRunRequest request, AssemblerDbContext db, JobQueue queue, TimeProvider clock, CancellationToken ct)
|
||||
{
|
||||
var run = new AgentRun(request.SeatId, request.WorkItemId, clock.GetUtcNow());
|
||||
db.AgentRuns.Add(run);
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
await queue.EnqueueAsync("agent.run", JsonSerializer.Serialize(new AgentRunPayload(run.Id)), ct);
|
||||
return Results.Ok(ToResponse(run));
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetRun(Guid id, AssemblerDbContext db, CancellationToken ct)
|
||||
{
|
||||
var run = await db.AgentRuns.FirstOrDefaultAsync(r => r.Id == id, ct);
|
||||
return run is null ? Results.NotFound() : Results.Ok(ToResponse(run));
|
||||
}
|
||||
|
||||
private static RunResponse ToResponse(AgentRun run) => new(
|
||||
run.Id, run.SeatId, run.WorkItemId, run.AgentId, run.Status.ToString(),
|
||||
run.ActionType, run.ActionRisk, run.Prompt, run.Output, run.Error);
|
||||
}
|
||||
Reference in New Issue
Block a user