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,48 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TeamUp.Modules.Assembler.Domain;
|
||||
using TeamUp.Modules.Assembler.Persistence;
|
||||
|
||||
namespace TeamUp.Modules.Assembler.Runtime;
|
||||
|
||||
internal sealed record AgentRunPayload(Guid RunId);
|
||||
|
||||
/// <summary>
|
||||
/// Processes one claimed job: drives the AgentRun lifecycle. In M4 Increment 1 it records a
|
||||
/// placeholder; Increment 2 swaps the middle for the real assembler (assemble → model → parse).
|
||||
/// </summary>
|
||||
internal sealed class AgentRunExecutor(AssemblerDbContext db, TimeProvider clock, ILogger<AgentRunExecutor> logger)
|
||||
{
|
||||
public async Task ProcessAsync(Job job, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var payload = JsonSerializer.Deserialize<AgentRunPayload>(job.Payload)
|
||||
?? throw new InvalidOperationException("Invalid job payload.");
|
||||
|
||||
var run = await db.AgentRuns.FirstOrDefaultAsync(r => r.Id == payload.RunId, cancellationToken)
|
||||
?? throw new InvalidOperationException($"AgentRun {payload.RunId} not found.");
|
||||
|
||||
run.Start(agentId: null, prompt: "[assembler pending — M4 Increment 2]", trace: null);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// TODO (M4 Increment 2): assemble the prompt, call the model, parse into action + risk.
|
||||
run.Complete(
|
||||
output: "[assembler pending]",
|
||||
actionType: "pending",
|
||||
actionRisk: "read",
|
||||
resultJson: null,
|
||||
latencyMs: 0,
|
||||
clock.GetUtcNow());
|
||||
job.MarkDone(clock.GetUtcNow());
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
job.MarkFailed(ex.Message, clock.GetUtcNow());
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
logger.LogError(ex, "Agent-run job {JobId} failed.", job.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user