feat: add buildRolePrompt in agent-kit, integrate with uwf-hermes

- New buildRolePrompt() in workflow-agent-kit: four-phase prompt assembly
  with fallback to systemPrompt
- Export from agent-kit index
- Update uwf-hermes to use buildRolePrompt instead of raw systemPrompt
- Add tests for all modes: four-phase, legacy, mixed

Refs #359, #362
This commit is contained in:
2026-05-21 02:31:56 +00:00
parent fc7d482b4f
commit 505f85e3c4
4 changed files with 113 additions and 3 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
import { spawn } from "node:child_process";
import { type AgentContext, type AgentRunResult, createAgent } from "@uncaged/workflow-agent-kit";
import { type AgentContext, type AgentRunResult, buildRolePrompt, createAgent } from "@uncaged/workflow-agent-kit";
import {
loadHermesSession,
@@ -34,12 +34,12 @@ function buildHistorySummary(steps: AgentContext["steps"]): string {
/** Assemble system prompt, task, and prior step outputs for Hermes. */
export function buildHermesPrompt(ctx: AgentContext): string {
const roleDef = ctx.workflow.roles[ctx.role];
const systemPrompt = roleDef?.systemPrompt ?? "";
const rolePrompt = roleDef !== undefined ? buildRolePrompt(roleDef) : "";
const parts: string[] = [];
if (ctx.outputFormatInstruction !== undefined && ctx.outputFormatInstruction !== "") {
parts.push(ctx.outputFormatInstruction, "");
}
parts.push(systemPrompt, "", "## Task", ctx.start.prompt);
parts.push(rolePrompt, "", "## Task", ctx.start.prompt);
const historyBlock = buildHistorySummary(ctx.steps);
if (historyBlock !== "") {
parts.push("", historyBlock);
@@ -0,0 +1,68 @@
import { describe, expect, test } from "vitest";
import type { RoleDefinition } from "@uncaged/workflow-protocol";
import { buildRolePrompt } from "../src/build-role-prompt.js";
describe("buildRolePrompt", () => {
test("four-phase: all fields present", () => {
const role: RoleDefinition = {
description: "A coder",
systemPrompt: "legacy prompt",
identity: "You are a senior developer.",
prepare: "Load cursor-agent skill.",
execute: "Implement the feature.",
report: "Summarize changes.",
outputSchema: "placeholder00000" as string,
};
const result = buildRolePrompt(role);
expect(result).toContain("## Identity");
expect(result).toContain("You are a senior developer.");
expect(result).toContain("## Prepare");
expect(result).toContain("Load cursor-agent skill.");
expect(result).toContain("## Execute");
expect(result).toContain("Implement the feature.");
expect(result).toContain("## Report");
expect(result).toContain("Summarize changes.");
expect(result).not.toContain("legacy prompt");
});
test("legacy: only systemPrompt", () => {
const role: RoleDefinition = {
description: "A planner",
systemPrompt: "You are a planning agent.",
identity: null,
prepare: null,
execute: null,
report: null,
outputSchema: "placeholder00000" as string,
};
const result = buildRolePrompt(role);
expect(result).toBe("You are a planning agent.");
});
test("legacy: no fields at all", () => {
const role = {
description: "Minimal",
outputSchema: "placeholder00000",
} as unknown as RoleDefinition;
const result = buildRolePrompt(role);
expect(result).toBe("");
});
test("mixed: identity present, partial other fields", () => {
const role: RoleDefinition = {
description: "A reviewer",
systemPrompt: "legacy",
identity: "You are a code reviewer.",
prepare: null,
execute: "Review the PR diff carefully.",
report: null,
outputSchema: "placeholder00000" as string,
};
const result = buildRolePrompt(role);
expect(result).toContain("## Identity");
expect(result).toContain("## Execute");
expect(result).not.toContain("## Prepare");
expect(result).not.toContain("## Report");
expect(result).not.toContain("legacy");
});
});
@@ -0,0 +1,41 @@
import type { RoleDefinition } from "@uncaged/workflow-protocol";
/**
* Build the role prompt from a RoleDefinition.
*
* Four-phase mode (identity/prepare/execute/report present):
* Assembles structured sections for each phase.
*
* Legacy mode (only systemPrompt):
* Returns systemPrompt as-is.
*
* When both are present, four-phase fields take priority.
*/
export function buildRolePrompt(role: RoleDefinition): string {
const hasFourPhase =
role.identity !== null &&
role.identity !== undefined &&
role.identity !== "";
if (!hasFourPhase) {
return role.systemPrompt ?? "";
}
const sections: string[] = [];
sections.push(`## Identity\n\n${role.identity}`);
if (role.prepare !== null && role.prepare !== undefined && role.prepare !== "") {
sections.push(`## Prepare\n\n${role.prepare}`);
}
if (role.execute !== null && role.execute !== undefined && role.execute !== "") {
sections.push(`## Execute\n\n${role.execute}`);
}
if (role.report !== null && role.report !== undefined && role.report !== "") {
sections.push(`## Report\n\n${role.report}`);
}
return sections.join("\n\n");
}
+1
View File
@@ -7,6 +7,7 @@ export {
resolveModel,
} from "./extract.js";
export { buildOutputFormatInstruction } from "./build-output-format-instruction.js";
export { buildRolePrompt } from "./build-role-prompt.js";
export type { FrontmatterFastPathResult } from "./frontmatter.js";
export { tryFrontmatterFastPath } from "./frontmatter.js";
export { createAgent } from "./run.js";