feat(pi-workflow): reuse standard agent profiles

This commit is contained in:
jb
2026-08-26 22:51:20 -04:00
parent a4784b75c3
commit aa50f0fd16
12 changed files with 606 additions and 54 deletions
+224 -19
View File
@@ -30,6 +30,17 @@ function profileDocument(options: {
return `---\nname: ${options.name}\ndescription: ${options.description ?? options.name}\nroles: ${options.roles ?? "[discover]"}\n${options.extra ?? ""}selection:\n${options.selection ?? " stages: [discover]\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n"}---\n\n${options.body ?? "Follow the assignment."}\n`;
}
function standardAgentDocument(options: {
name: string;
description?: string;
model?: string;
tools?: string;
thinking?: string;
body?: string;
}) {
return `---\nname: ${options.name}\ndescription: ${options.description ?? options.name}\n${options.model ? `model: ${options.model}\n` : ""}${options.tools ? `tools: ${options.tools}\n` : ""}${options.thinking ? `thinking: ${options.thinking}\n` : ""}subagent_agents: scout, researcher\nsystem-prompt: append\nauto-exit: true\n---\n\n${options.body ?? "Follow the standard assignment."}\n`;
}
async function writeProfile(
root: string,
file: string,
@@ -39,26 +50,197 @@ async function writeProfile(
await writeFile(path.join(root, file), profileDocument(options));
}
async function writeStandardAgent(
root: string,
file: string,
options: Parameters<typeof standardAgentDocument>[0],
) {
await mkdir(root, { recursive: true });
await writeFile(path.join(root, file), standardAgentDocument(options));
}
test("bundled profiles form a complete deterministic catalog", async () => {
const catalog = await loadAgentProfiles({
cwd: process.cwd(),
projectTrusted: false,
await withTempDir(async (directory) => {
const catalog = await loadAgentProfiles({
cwd: directory,
agentDir: path.join(directory, "agent"),
projectTrusted: false,
});
assert.deepEqual(
catalog.profiles.map((profile) => profile.name),
[
"feature-designer",
"feature-implementer",
"feature-verifier",
"general-discovery",
"independent-reviewer",
"ui-prototyper",
],
);
assert.equal(catalog.diagnostics.length, 0);
assert.ok(
catalog.profiles.every((profile) => /^[a-f0-9]{64}$/.test(profile.digest)),
);
});
});
test("standard package, global, and trusted project agents use role overlays and precedence", async () => {
await withTempDir(async (directory) => {
const bundled = path.join(directory, "bundled");
const packageRoot = path.join(directory, "package-agents");
const agentDir = path.join(directory, "agent");
const globalRoot = path.join(agentDir, "agents");
const projectRoot = path.join(directory, ".pi", "agents");
await writeProfile(bundled, "general.md", {
name: "general-discovery",
});
await writeStandardAgent(packageRoot, "scout.md", {
name: "scout",
model: "openai-codex/gpt-test",
tools: "read, grep, find, ls",
thinking: "high",
body: "Package scout.",
});
await writeStandardAgent(packageRoot, "worker.md", {
name: "worker",
tools: "read, write, edit, bash",
body: "Package worker.",
});
await writeStandardAgent(globalRoot, "scout.md", {
name: "scout",
body: "Global scout.",
});
await writeStandardAgent(globalRoot, "unmapped.md", {
name: "unmapped",
});
await writeStandardAgent(projectRoot, "worker.md", {
name: "worker",
body: "Project worker.",
});
const catalog = await loadAgentProfiles({
cwd: directory,
agentDir,
bundledRoot: bundled,
standardPackageRoot: packageRoot,
projectRoot,
projectTrusted: true,
standardAgents: {
enabled: true,
roles: { scout: ["discover"], worker: ["implement"] },
},
});
const scout = catalog.profiles.find((profile) => profile.name === "scout");
assert.equal(scout?.source, "standard-global");
assert.equal(scout?.instructions, "Global scout.");
assert.deepEqual(scout?.roles, ["discover"]);
assert.deepEqual(scout?.selection.stages, ["discover"]);
const worker = catalog.profiles.find((profile) => profile.name === "worker");
assert.equal(worker?.source, "standard-project");
assert.equal(worker?.instructions, "Project worker.");
assert.deepEqual(worker?.roles, ["implement"]);
assert.equal(catalog.profiles.some((profile) => profile.name === "unmapped"), false);
});
});
test("package and global standard definitions cannot self-declare workflow roles", async () => {
await withTempDir(async (directory) => {
const bundled = path.join(directory, "bundled");
const packageRoot = path.join(directory, "package-agents");
await writeProfile(bundled, "general.md", { name: "general-discovery" });
await writeProfile(packageRoot, "worker.md", {
name: "worker",
roles: "[review]",
selection:
" stages: [review]\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n",
body: "Attempted self-declared reviewer.",
});
await writeProfile(packageRoot, "unmapped.md", {
name: "unmapped",
roles: "[review]",
selection:
" stages: [review]\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n",
});
const catalog = await loadAgentProfiles({
cwd: directory,
agentDir: path.join(directory, "agent"),
bundledRoot: bundled,
standardPackageRoot: packageRoot,
projectTrusted: false,
standardAgents: {
enabled: true,
roles: { worker: ["implement"] },
},
});
const worker = catalog.profiles.find((profile) => profile.name === "worker");
assert.equal(worker?.source, "standard-package");
assert.deepEqual(worker?.roles, ["implement"]);
assert.deepEqual(worker?.selection.stages, ["implement"]);
assert.equal(catalog.profiles.some((profile) => profile.name === "unmapped"), false);
});
});
test("a trusted workflow profile keeps its declared roles when its name also has a standard overlay", async () => {
await withTempDir(async (directory) => {
const bundled = path.join(directory, "bundled");
const packageRoot = path.join(directory, "package-agents");
const projectRoot = path.join(directory, ".pi", "agents");
await writeProfile(bundled, "general.md", { name: "general-discovery" });
await writeStandardAgent(packageRoot, "scout.md", { name: "scout" });
await writeProfile(projectRoot, "scout.md", {
name: "scout",
roles: "[verify]",
selection:
" stages: [verify]\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n",
body: "Project verifier.",
});
const catalog = await loadAgentProfiles({
cwd: directory,
agentDir: path.join(directory, "agent"),
bundledRoot: bundled,
standardPackageRoot: packageRoot,
projectRoot,
projectTrusted: true,
standardAgents: {
enabled: true,
roles: { scout: ["discover"] },
},
});
const scout = catalog.profiles.find((profile) => profile.name === "scout");
assert.equal(scout?.source, "project");
assert.deepEqual(scout?.roles, ["verify"]);
assert.deepEqual(scout?.selection.stages, ["verify"]);
assert.equal(scout?.instructions, "Project verifier.");
});
});
test("standard agents are disabled explicitly and ignored for untrusted projects", async () => {
await withTempDir(async (directory) => {
const bundled = path.join(directory, "bundled");
const packageRoot = path.join(directory, "package-agents");
const projectRoot = path.join(directory, ".pi", "agents");
await writeProfile(bundled, "general.md", { name: "general-discovery" });
await writeStandardAgent(packageRoot, "scout.md", { name: "scout" });
await writeStandardAgent(projectRoot, "worker.md", { name: "worker" });
const disabled = await loadAgentProfiles({
cwd: directory,
agentDir: path.join(directory, "agent"),
bundledRoot: bundled,
standardPackageRoot: packageRoot,
projectRoot,
projectTrusted: true,
standardAgents: {
enabled: false,
roles: { scout: ["discover"], worker: ["implement"] },
},
});
assert.deepEqual(disabled.profiles.map((profile) => profile.name), ["general-discovery"]);
});
assert.deepEqual(
catalog.profiles.map((profile) => profile.name),
[
"feature-designer",
"feature-implementer",
"feature-verifier",
"general-discovery",
"independent-reviewer",
"ui-prototyper",
],
);
assert.equal(catalog.diagnostics.length, 0);
assert.ok(
catalog.profiles.every((profile) => /^[a-f0-9]{64}$/.test(profile.digest)),
);
});
test("trusted project profiles override bundled profiles while first duplicate wins", async () => {
@@ -340,6 +522,7 @@ test("active profile contents are pinned by digest", async () => {
function agent(
name: string,
selection: AgentProfile["selection"],
source: AgentProfile["source"] = "bundled",
): AgentProfile {
return {
name,
@@ -347,7 +530,7 @@ function agent(
roles: ["discover"],
selection,
instructions: "Discover.",
source: "bundled",
source,
sourcePath: name,
resolvedPath: name,
digest: name.padEnd(64, "0"),
@@ -414,6 +597,28 @@ test("eligibility records exclusion reasons and selects the most specific discov
);
});
test("an eligible standard agent replaces the bundled fallback for its role", () => {
const emptySelection: AgentProfile["selection"] = {
stages: ["discover"],
anyTags: [],
allTags: [],
pathGlobs: [],
keywords: [],
};
const bundled = agent("general-discovery", emptySelection);
const scout = agent("scout", emptySelection, "standard-package");
const result = selectInitialDiscoveryProfile(
[bundled, scout],
baseFacts,
);
assert.equal(result.selected.name, "scout");
assert.deepEqual(
result.evaluations.find((item) => item.profile.name === "general-discovery")?.reasons,
["eligible standard agent available for role discover"],
);
});
test("eligibility enforces every criterion and uses a code-point name tie-breaker", () => {
const emptySelection: AgentProfile["selection"] = {
stages: ["discover"],