M4: the assembler — assemble → model → parse (Increment 2)

SharedKernel contracts (so Assembler stays decoupled): IAgentRunContextProvider (agent +
task) and ISkillCatalog (skill prompts by key). Implemented by OrgBoard (AgentRunContextProvider)
and Skills (SkillCatalog).

Assembler:
- PromptAssembler builds house-style + identity + the agent's skill bodies + the task, and
  derives the primary action + risk from the agent's first skill. RAG/working-memory join at M6.
- AgentRunExecutor (real): resolve context + skills → assemble → resolve BYOK config (with
  fallback) → call IModelClient → parse into action + risk → capture all on the AgentRun.

Verified: build green; ArchitectureTests 8/8; IntegrationTests 29/29 — incl. the M4 acceptance:
assigning a Spec task to Aria (PO, gated, stub BYOK) yields a Completed run with the assembled
prompt (skill body + task title), action "write-spec", risk "Draft", and model output. Nothing
executes — the gate is M5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-10 06:19:02 +03:30
parent 09eaf360a3
commit d9f9349117
10 changed files with 388 additions and 16 deletions
@@ -0,0 +1,36 @@
using Microsoft.EntityFrameworkCore;
using TeamUp.Modules.OrgBoard.Persistence;
using TeamUp.SharedKernel.Ai;
namespace TeamUp.Modules.OrgBoard.Runtime;
/// <summary>Gathers the agent config + task into an <see cref="AgentRunContext"/> for the assembler.</summary>
internal sealed class AgentRunContextProvider(OrgBoardDbContext db) : IAgentRunContextProvider
{
public async Task<AgentRunContext?> GetAsync(Guid seatId, Guid workItemId, CancellationToken cancellationToken = default)
{
var agent = await db.Agents.FirstOrDefaultAsync(a => a.SeatId == seatId, cancellationToken);
if (agent is null)
{
return null;
}
var item = await db.WorkItems.FirstOrDefaultAsync(w => w.Id == workItemId, cancellationToken);
if (item is null)
{
return null;
}
var team = await db.Teams.FirstOrDefaultAsync(t => t.Id == item.TeamId, cancellationToken);
if (team is null)
{
return null;
}
return new AgentRunContext(
seatId, agent.Id, agent.Name, agent.Monogram, agent.Autonomy,
agent.ApiConfigId, agent.FallbackApiConfigId, agent.SkillKeys, agent.Docs,
item.Id, item.Title, item.Description, item.Type.ToString(),
team.Id, team.OrganizationId);
}
}