Wire skills into agent runs: org-scoped, published-only, org-preferred resolution

ISkillCatalog.GetByKeysAsync now takes the org id and resolves each key within that org's namespace
only — the org's own published skill, else a shared builtin (null org), never another org's. Org-owned
is preferred over the builtin; only Published (golden-tested) skills are injected; the resolved
skill@version is recorded in the prompt heading and run trace. AgentRunExecutor threads
context.OrganizationId. SeatsPage now loads the org library (builtins + authored + installed), dedupes
to one entry per key, and flags drafts (won't run until published).

Verified: ArchitectureTests 8/8, IntegrationTests 48/48 (new SkillRunScopingTests: a run assembles the
org's own skill over the builtin of the same key, and another org's same-key skill never leaks in),
client build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-13 13:35:53 +03:30
parent cca7c68da3
commit 2ebe2808be
6 changed files with 216 additions and 9 deletions
@@ -4,15 +4,21 @@ namespace TeamUp.SharedKernel.Ai;
public sealed record SkillPrompt(
string Key,
string Name,
string Version,
string Body,
string PrimaryAction,
string PrimaryActionRisk,
IReadOnlyList<string> Roles);
/// <summary>Resolves skill prompts by key (latest version). Implemented by the Skills module.</summary>
/// <summary>
/// Resolves skill prompts for one org's agent run. Each key resolves within that org's namespace
/// only — the org's own (published) skill, else a shared builtin — never another org's; latest
/// version, org-owned preferred over builtin. Implemented by the Skills module.
/// </summary>
public interface ISkillCatalog
{
Task<IReadOnlyList<SkillPrompt>> GetByKeysAsync(
Guid organizationId,
IReadOnlyCollection<string> keys,
CancellationToken cancellationToken = default);
}