Merge pull request 'feat: Role 四段式描述 (identity/prepare/execute/report)' (#361) from feat/359-role-four-phase into main
This commit is contained in:
@@ -0,0 +1,40 @@
|
|||||||
|
name: "analyze-topic"
|
||||||
|
description: "Single-role topic analysis using four-phase role description"
|
||||||
|
roles:
|
||||||
|
analyst:
|
||||||
|
description: "Analyzes a given topic and produces a structured summary"
|
||||||
|
identity: |
|
||||||
|
You are a research analyst with expertise in breaking down complex topics
|
||||||
|
into clear, structured summaries. You think critically and cite key points.
|
||||||
|
prepare: |
|
||||||
|
Review the topic carefully. Consider multiple perspectives and identify
|
||||||
|
the core question being asked.
|
||||||
|
execute: |
|
||||||
|
Analyze the topic by:
|
||||||
|
1. Identifying the main thesis or question
|
||||||
|
2. Listing 3-5 key points with brief explanations
|
||||||
|
3. Noting any counterarguments or caveats
|
||||||
|
Keep your analysis concise (under 500 words).
|
||||||
|
report: |
|
||||||
|
Provide your analysis as markdown under the frontmatter.
|
||||||
|
The frontmatter must include your structured findings.
|
||||||
|
outputSchema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
thesis:
|
||||||
|
type: string
|
||||||
|
keyPoints:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
caveats:
|
||||||
|
type: string
|
||||||
|
required: [thesis, keyPoints]
|
||||||
|
conditions: {}
|
||||||
|
graph:
|
||||||
|
$START:
|
||||||
|
- role: "analyst"
|
||||||
|
condition: null
|
||||||
|
analyst:
|
||||||
|
- role: "$END"
|
||||||
|
condition: null
|
||||||
@@ -3,7 +3,10 @@ description: "End-to-end issue resolution"
|
|||||||
roles:
|
roles:
|
||||||
planner:
|
planner:
|
||||||
description: "Creates implementation plan"
|
description: "Creates implementation plan"
|
||||||
systemPrompt: "You are a planning agent. Analyze the issue and create a step-by-step plan."
|
identity: "You are a planning agent. You analyze issues and create step-by-step plans."
|
||||||
|
prepare: "Read the issue description and any linked context carefully."
|
||||||
|
execute: "Analyze the issue and create a detailed, actionable implementation plan."
|
||||||
|
report: "Output the plan summary and list of concrete steps."
|
||||||
outputSchema:
|
outputSchema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -16,7 +19,10 @@ roles:
|
|||||||
required: [plan, steps]
|
required: [plan, steps]
|
||||||
developer:
|
developer:
|
||||||
description: "Implements code changes"
|
description: "Implements code changes"
|
||||||
systemPrompt: "You are a developer agent. Implement the plan."
|
identity: "You are a developer agent. You implement code changes according to plans."
|
||||||
|
prepare: "Load coding tools and review the project structure and conventions."
|
||||||
|
execute: "Implement the plan. Write code, tests, and ensure existing tests pass."
|
||||||
|
report: "List all files changed and provide a summary of the implementation."
|
||||||
outputSchema:
|
outputSchema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -29,7 +35,10 @@ roles:
|
|||||||
required: [filesChanged, summary]
|
required: [filesChanged, summary]
|
||||||
reviewer:
|
reviewer:
|
||||||
description: "Reviews code changes"
|
description: "Reviews code changes"
|
||||||
systemPrompt: "You are a code reviewer. Review the implementation."
|
identity: "You are a code reviewer. You review implementations for correctness and quality."
|
||||||
|
prepare: "Review the project's coding standards and conventions."
|
||||||
|
execute: "Review the implementation against the plan. Check for bugs, edge cases, and style."
|
||||||
|
report: "Approve or reject with detailed comments explaining your decision."
|
||||||
outputSchema:
|
outputSchema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@uncaged/cli-workflow",
|
"name": "@uncaged/cli-workflow",
|
||||||
"version": "0.1.0",
|
"version": "0.5.0",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -211,7 +211,10 @@ describe("cmdThreadRead ### Content section", () => {
|
|||||||
roles: {
|
roles: {
|
||||||
writer: {
|
writer: {
|
||||||
description: "Write",
|
description: "Write",
|
||||||
systemPrompt: "You are a writer.",
|
identity: "You are a writer.",
|
||||||
|
prepare: "",
|
||||||
|
execute: "Write content as requested.",
|
||||||
|
report: "Summarize what was written.",
|
||||||
outputSchema: "placeholder00" as CasRef,
|
outputSchema: "placeholder00" as CasRef,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ function runAction(action: () => Promise<void>): void {
|
|||||||
|
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
program.name("uwf").description("Stateless workflow CLI");
|
// eslint-disable-next-line -- dynamic import for version
|
||||||
|
const pkg = await import("../package.json", { with: { type: "json" } });
|
||||||
|
program.name("uwf").description("Stateless workflow CLI").version(pkg.default.version, "-V, --version");
|
||||||
program.option("--format <fmt>", "Output format: json or yaml", "json");
|
program.option("--format <fmt>", "Output format: json or yaml", "json");
|
||||||
|
|
||||||
const workflow = program.command("workflow").description("Workflow registry and CAS");
|
const workflow = program.command("workflow").description("Workflow registry and CAS");
|
||||||
|
|||||||
@@ -500,7 +500,8 @@ function formatThreadReadMarkdown(options: {
|
|||||||
];
|
];
|
||||||
const roleDef = workflow.roles[item.payload.role];
|
const roleDef = workflow.roles[item.payload.role];
|
||||||
if (roleDef) {
|
if (roleDef) {
|
||||||
stepLines.push("", "### Prompt", "", roleDef.systemPrompt);
|
const prompt = roleDef.identity;
|
||||||
|
stepLines.push("", "### Prompt", "", prompt);
|
||||||
}
|
}
|
||||||
if (item.payload.detail) {
|
if (item.payload.detail) {
|
||||||
const content = extractLastAssistantContent(uwf, item.payload.detail);
|
const content = extractLastAssistantContent(uwf, item.payload.detail);
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ async function materializeWorkflowPayload(
|
|||||||
);
|
);
|
||||||
roles[roleName] = {
|
roles[roleName] = {
|
||||||
description: role.description,
|
description: role.description,
|
||||||
systemPrompt: role.systemPrompt,
|
identity: role.identity,
|
||||||
|
prepare: role.prepare,
|
||||||
|
execute: role.execute,
|
||||||
|
report: role.report,
|
||||||
outputSchema,
|
outputSchema,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,12 @@ function isRoleDefinition(value: unknown): boolean {
|
|||||||
const outputSchema = value.outputSchema;
|
const outputSchema = value.outputSchema;
|
||||||
const schemaOk = isRecord(outputSchema) && typeof outputSchema.type === "string";
|
const schemaOk = isRecord(outputSchema) && typeof outputSchema.type === "string";
|
||||||
return (
|
return (
|
||||||
typeof value.description === "string" && typeof value.systemPrompt === "string" && schemaOk
|
typeof value.description === "string" &&
|
||||||
|
typeof value.identity === "string" &&
|
||||||
|
typeof value.prepare === "string" &&
|
||||||
|
typeof value.execute === "string" &&
|
||||||
|
typeof value.report === "string" &&
|
||||||
|
schemaOk
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@uncaged/workflow-agent-hermes",
|
"name": "@uncaged/workflow-agent-hermes",
|
||||||
"version": "0.1.0",
|
"version": "0.5.0",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { spawn } from "node:child_process";
|
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 {
|
import {
|
||||||
loadHermesSession,
|
loadHermesSession,
|
||||||
@@ -34,12 +34,12 @@ function buildHistorySummary(steps: AgentContext["steps"]): string {
|
|||||||
/** Assemble system prompt, task, and prior step outputs for Hermes. */
|
/** Assemble system prompt, task, and prior step outputs for Hermes. */
|
||||||
export function buildHermesPrompt(ctx: AgentContext): string {
|
export function buildHermesPrompt(ctx: AgentContext): string {
|
||||||
const roleDef = ctx.workflow.roles[ctx.role];
|
const roleDef = ctx.workflow.roles[ctx.role];
|
||||||
const systemPrompt = roleDef?.systemPrompt ?? "";
|
const rolePrompt = roleDef !== undefined ? buildRolePrompt(roleDef) : "";
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
if (ctx.outputFormatInstruction !== undefined && ctx.outputFormatInstruction !== "") {
|
if (ctx.outputFormatInstruction !== undefined && ctx.outputFormatInstruction !== "") {
|
||||||
parts.push(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);
|
const historyBlock = buildHistorySummary(ctx.steps);
|
||||||
if (historyBlock !== "") {
|
if (historyBlock !== "") {
|
||||||
parts.push("", historyBlock);
|
parts.push("", historyBlock);
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { describe, expect, test } from "vitest";
|
||||||
|
import type { RoleDefinition } from "@uncaged/workflow-protocol";
|
||||||
|
import { buildRolePrompt } from "../src/build-role-prompt.js";
|
||||||
|
|
||||||
|
describe("buildRolePrompt", () => {
|
||||||
|
test("all fields present", () => {
|
||||||
|
const role: RoleDefinition = {
|
||||||
|
description: "A coder",
|
||||||
|
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.");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("empty fields are omitted", () => {
|
||||||
|
const role: RoleDefinition = {
|
||||||
|
description: "A reviewer",
|
||||||
|
identity: "You are a code reviewer.",
|
||||||
|
prepare: "",
|
||||||
|
execute: "Review the PR diff carefully.",
|
||||||
|
report: "",
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("all empty returns empty string", () => {
|
||||||
|
const role: RoleDefinition = {
|
||||||
|
description: "Minimal",
|
||||||
|
identity: "",
|
||||||
|
prepare: "",
|
||||||
|
execute: "",
|
||||||
|
report: "",
|
||||||
|
outputSchema: "placeholder00000" as string,
|
||||||
|
};
|
||||||
|
const result = buildRolePrompt(role);
|
||||||
|
expect(result).toBe("");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@uncaged/workflow-agent-kit",
|
"name": "@uncaged/workflow-agent-kit",
|
||||||
"version": "0.1.0",
|
"version": "0.5.0",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import type { RoleDefinition } from "@uncaged/workflow-protocol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the role prompt from a RoleDefinition.
|
||||||
|
*
|
||||||
|
* Assembles structured sections: Identity, Prepare, Execute, Report.
|
||||||
|
* Empty strings are omitted from the output.
|
||||||
|
*/
|
||||||
|
export function buildRolePrompt(role: RoleDefinition): string {
|
||||||
|
const sections: string[] = [];
|
||||||
|
|
||||||
|
if (role.identity !== "") {
|
||||||
|
sections.push(`## Identity\n\n${role.identity}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role.prepare !== "") {
|
||||||
|
sections.push(`## Prepare\n\n${role.prepare}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role.execute !== "") {
|
||||||
|
sections.push(`## Execute\n\n${role.execute}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role.report !== "") {
|
||||||
|
sections.push(`## Report\n\n${role.report}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections.join("\n\n");
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ export {
|
|||||||
resolveModel,
|
resolveModel,
|
||||||
} from "./extract.js";
|
} from "./extract.js";
|
||||||
export { buildOutputFormatInstruction } from "./build-output-format-instruction.js";
|
export { buildOutputFormatInstruction } from "./build-output-format-instruction.js";
|
||||||
|
export { buildRolePrompt } from "./build-role-prompt.js";
|
||||||
export type { FrontmatterFastPathResult } from "./frontmatter.js";
|
export type { FrontmatterFastPathResult } from "./frontmatter.js";
|
||||||
export { tryFrontmatterFastPath } from "./frontmatter.js";
|
export { tryFrontmatterFastPath } from "./frontmatter.js";
|
||||||
export { createAgent } from "./run.js";
|
export { createAgent } from "./run.js";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export type AgentContext = ModeratorContext & {
|
|||||||
store: Store;
|
store: Store;
|
||||||
workflow: WorkflowPayload;
|
workflow: WorkflowPayload;
|
||||||
/**
|
/**
|
||||||
* Prepend to the role's systemPrompt when building the agent prompt.
|
* Prepend to the role's prompt when building the agent prompt.
|
||||||
* Contains the frontmatter deliverable format instruction derived from the
|
* Contains the frontmatter deliverable format instruction derived from the
|
||||||
* role's output schema. Populated by `createAgent` at run time.
|
* role's output schema. Populated by `createAgent` at run time.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,17 +9,26 @@ const solveIssueWorkflow: WorkflowPayload = {
|
|||||||
roles: {
|
roles: {
|
||||||
planner: {
|
planner: {
|
||||||
description: "Creates implementation plan",
|
description: "Creates implementation plan",
|
||||||
systemPrompt: "You are a planning agent...",
|
identity: "You are a planning agent.",
|
||||||
|
prepare: "Review the issue context.",
|
||||||
|
execute: "Create a step-by-step plan.",
|
||||||
|
report: "Output the plan and steps.",
|
||||||
outputSchema: "5GWKR8TN1V3JA",
|
outputSchema: "5GWKR8TN1V3JA",
|
||||||
},
|
},
|
||||||
developer: {
|
developer: {
|
||||||
description: "Implements code changes",
|
description: "Implements code changes",
|
||||||
systemPrompt: "You are a developer agent...",
|
identity: "You are a developer agent.",
|
||||||
|
prepare: "Load coding tools.",
|
||||||
|
execute: "Implement the plan.",
|
||||||
|
report: "List files changed and summary.",
|
||||||
outputSchema: "8CNWT4KR6D1HV",
|
outputSchema: "8CNWT4KR6D1HV",
|
||||||
},
|
},
|
||||||
reviewer: {
|
reviewer: {
|
||||||
description: "Reviews code changes",
|
description: "Reviews code changes",
|
||||||
systemPrompt: "You are a code reviewer...",
|
identity: "You are a code reviewer.",
|
||||||
|
prepare: "Review project conventions.",
|
||||||
|
execute: "Review the implementation.",
|
||||||
|
report: "Approve or reject with comments.",
|
||||||
outputSchema: "1VPBG9SM5E7WK",
|
outputSchema: "1VPBG9SM5E7WK",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@uncaged/workflow-moderator",
|
"name": "@uncaged/workflow-moderator",
|
||||||
"version": "0.1.0",
|
"version": "0.5.0",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@uncaged/workflow-protocol",
|
"name": "@uncaged/workflow-protocol",
|
||||||
"version": "0.1.0",
|
"version": "0.5.0",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import type { JSONSchema } from "@uncaged/json-cas";
|
|||||||
|
|
||||||
const ROLE_DEFINITION: JSONSchema = {
|
const ROLE_DEFINITION: JSONSchema = {
|
||||||
type: "object",
|
type: "object",
|
||||||
required: ["description", "systemPrompt", "outputSchema"],
|
required: ["description", "identity", "prepare", "execute", "report", "outputSchema"],
|
||||||
properties: {
|
properties: {
|
||||||
description: { type: "string" },
|
description: { type: "string" },
|
||||||
systemPrompt: { type: "string" },
|
identity: { type: "string" },
|
||||||
|
prepare: { type: "string" },
|
||||||
|
execute: { type: "string" },
|
||||||
|
report: { type: "string" },
|
||||||
outputSchema: { type: "string", format: "cas_ref" },
|
outputSchema: { type: "string", format: "cas_ref" },
|
||||||
},
|
},
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ export type StepRecord = {
|
|||||||
|
|
||||||
export type RoleDefinition = {
|
export type RoleDefinition = {
|
||||||
description: string;
|
description: string;
|
||||||
systemPrompt: string;
|
identity: string;
|
||||||
|
prepare: string;
|
||||||
|
execute: string;
|
||||||
|
report: string;
|
||||||
outputSchema: CasRef;
|
outputSchema: CasRef;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@uncaged/workflow-util",
|
"name": "@uncaged/workflow-util",
|
||||||
"version": "0.5.0-alpha.4",
|
"version": "0.5.0",
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
Reference in New Issue
Block a user