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,30 @@
using TeamUp.SharedKernel.Access;
namespace TeamUp.SharedKernel.Ai;
/// <summary>
/// Everything the assembler needs about a run, gathered from OrgBoard: the agent's config and the
/// task. Lets the Assembler module build a prompt without referencing OrgBoard's entities.
/// </summary>
public sealed record AgentRunContext(
Guid SeatId,
Guid AgentId,
string AgentName,
string? Monogram,
Autonomy Autonomy,
Guid ApiConfigId,
Guid? FallbackApiConfigId,
IReadOnlyList<string> SkillKeys,
IReadOnlyList<string> Docs,
Guid WorkItemId,
string TaskTitle,
string? TaskDescription,
string TaskType,
Guid TeamId,
Guid OrganizationId);
/// <summary>Resolves the run context for a (seat, task) pair. Implemented by OrgBoard.</summary>
public interface IAgentRunContextProvider
{
Task<AgentRunContext?> GetAsync(Guid seatId, Guid workItemId, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,18 @@
namespace TeamUp.SharedKernel.Ai;
/// <summary>A skill's prompt body + its primary risk-tagged action, for prompt assembly.</summary>
public sealed record SkillPrompt(
string Key,
string Name,
string Body,
string PrimaryAction,
string PrimaryActionRisk,
IReadOnlyList<string> Roles);
/// <summary>Resolves skill prompts by key (latest version). Implemented by the Skills module.</summary>
public interface ISkillCatalog
{
Task<IReadOnlyList<SkillPrompt>> GetByKeysAsync(
IReadOnlyCollection<string> keys,
CancellationToken cancellationToken = default);
}