diff --git a/examples/analyze-topic.yaml b/examples/analyze-topic.yaml new file mode 100644 index 0000000..7f0b9e6 --- /dev/null +++ b/examples/analyze-topic.yaml @@ -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 diff --git a/examples/solve-issue.yaml b/examples/solve-issue.yaml index bb33e26..2f86083 100644 --- a/examples/solve-issue.yaml +++ b/examples/solve-issue.yaml @@ -3,7 +3,10 @@ description: "End-to-end issue resolution" roles: planner: 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: type: object properties: @@ -16,7 +19,10 @@ roles: required: [plan, steps] developer: 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: type: object properties: @@ -29,7 +35,10 @@ roles: required: [filesChanged, summary] reviewer: 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: type: object properties: diff --git a/packages/cli-workflow/package.json b/packages/cli-workflow/package.json index 4b30e2c..41e1266 100644 --- a/packages/cli-workflow/package.json +++ b/packages/cli-workflow/package.json @@ -1,6 +1,6 @@ { "name": "@uncaged/cli-workflow", - "version": "0.1.0", + "version": "0.5.0", "files": [ "src", "dist", diff --git a/packages/cli-workflow/src/__tests__/thread.test.ts b/packages/cli-workflow/src/__tests__/thread.test.ts index 7cf54dc..fb83981 100644 --- a/packages/cli-workflow/src/__tests__/thread.test.ts +++ b/packages/cli-workflow/src/__tests__/thread.test.ts @@ -211,7 +211,10 @@ describe("cmdThreadRead ### Content section", () => { roles: { writer: { 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, }, }, diff --git a/packages/cli-workflow/src/cli.ts b/packages/cli-workflow/src/cli.ts index 4201404..995fb67 100755 --- a/packages/cli-workflow/src/cli.ts +++ b/packages/cli-workflow/src/cli.ts @@ -45,7 +45,9 @@ function runAction(action: () => Promise): void { 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 ", "Output format: json or yaml", "json"); const workflow = program.command("workflow").description("Workflow registry and CAS"); diff --git a/packages/cli-workflow/src/commands/thread.ts b/packages/cli-workflow/src/commands/thread.ts index 1cf6d22..0cc89dc 100644 --- a/packages/cli-workflow/src/commands/thread.ts +++ b/packages/cli-workflow/src/commands/thread.ts @@ -500,7 +500,8 @@ function formatThreadReadMarkdown(options: { ]; const roleDef = workflow.roles[item.payload.role]; if (roleDef) { - stepLines.push("", "### Prompt", "", roleDef.systemPrompt); + const prompt = roleDef.identity; + stepLines.push("", "### Prompt", "", prompt); } if (item.payload.detail) { const content = extractLastAssistantContent(uwf, item.payload.detail); diff --git a/packages/cli-workflow/src/commands/workflow.ts b/packages/cli-workflow/src/commands/workflow.ts index d624159..32c1b93 100644 --- a/packages/cli-workflow/src/commands/workflow.ts +++ b/packages/cli-workflow/src/commands/workflow.ts @@ -69,7 +69,10 @@ async function materializeWorkflowPayload( ); roles[roleName] = { description: role.description, - systemPrompt: role.systemPrompt, + identity: role.identity, + prepare: role.prepare, + execute: role.execute, + report: role.report, outputSchema, }; } diff --git a/packages/cli-workflow/src/validate.ts b/packages/cli-workflow/src/validate.ts index d106dcf..8c50db2 100644 --- a/packages/cli-workflow/src/validate.ts +++ b/packages/cli-workflow/src/validate.ts @@ -17,7 +17,12 @@ function isRoleDefinition(value: unknown): boolean { const outputSchema = value.outputSchema; const schemaOk = isRecord(outputSchema) && typeof outputSchema.type === "string"; 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 ); } diff --git a/packages/workflow-agent-hermes/package.json b/packages/workflow-agent-hermes/package.json index 9a11a28..62f3bd6 100644 --- a/packages/workflow-agent-hermes/package.json +++ b/packages/workflow-agent-hermes/package.json @@ -1,6 +1,6 @@ { "name": "@uncaged/workflow-agent-hermes", - "version": "0.1.0", + "version": "0.5.0", "files": [ "src", "dist", diff --git a/packages/workflow-agent-hermes/src/hermes.ts b/packages/workflow-agent-hermes/src/hermes.ts index 549e4b5..5d853b2 100644 --- a/packages/workflow-agent-hermes/src/hermes.ts +++ b/packages/workflow-agent-hermes/src/hermes.ts @@ -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); diff --git a/packages/workflow-agent-kit/__tests__/build-role-prompt.test.ts b/packages/workflow-agent-kit/__tests__/build-role-prompt.test.ts new file mode 100644 index 0000000..4ce456a --- /dev/null +++ b/packages/workflow-agent-kit/__tests__/build-role-prompt.test.ts @@ -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(""); + }); +}); diff --git a/packages/workflow-agent-kit/package.json b/packages/workflow-agent-kit/package.json index f1bc5b4..1be9724 100644 --- a/packages/workflow-agent-kit/package.json +++ b/packages/workflow-agent-kit/package.json @@ -1,6 +1,6 @@ { "name": "@uncaged/workflow-agent-kit", - "version": "0.1.0", + "version": "0.5.0", "files": [ "src", "dist", diff --git a/packages/workflow-agent-kit/src/build-role-prompt.ts b/packages/workflow-agent-kit/src/build-role-prompt.ts new file mode 100644 index 0000000..9530766 --- /dev/null +++ b/packages/workflow-agent-kit/src/build-role-prompt.ts @@ -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"); +} diff --git a/packages/workflow-agent-kit/src/index.ts b/packages/workflow-agent-kit/src/index.ts index 2d5f6b0..ce965c5 100644 --- a/packages/workflow-agent-kit/src/index.ts +++ b/packages/workflow-agent-kit/src/index.ts @@ -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"; diff --git a/packages/workflow-agent-kit/src/types.ts b/packages/workflow-agent-kit/src/types.ts index 18cce91..8c57d0b 100644 --- a/packages/workflow-agent-kit/src/types.ts +++ b/packages/workflow-agent-kit/src/types.ts @@ -7,7 +7,7 @@ export type AgentContext = ModeratorContext & { store: Store; 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 * role's output schema. Populated by `createAgent` at run time. */ diff --git a/packages/workflow-moderator/__tests__/evaluate.test.ts b/packages/workflow-moderator/__tests__/evaluate.test.ts index 61c65e8..54b6604 100644 --- a/packages/workflow-moderator/__tests__/evaluate.test.ts +++ b/packages/workflow-moderator/__tests__/evaluate.test.ts @@ -9,17 +9,26 @@ const solveIssueWorkflow: WorkflowPayload = { roles: { planner: { 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", }, developer: { 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", }, reviewer: { 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", }, }, diff --git a/packages/workflow-moderator/package.json b/packages/workflow-moderator/package.json index 59aa336..db8b99f 100644 --- a/packages/workflow-moderator/package.json +++ b/packages/workflow-moderator/package.json @@ -1,6 +1,6 @@ { "name": "@uncaged/workflow-moderator", - "version": "0.1.0", + "version": "0.5.0", "files": [ "src", "dist", diff --git a/packages/workflow-protocol/package.json b/packages/workflow-protocol/package.json index e56251d..acf290f 100644 --- a/packages/workflow-protocol/package.json +++ b/packages/workflow-protocol/package.json @@ -1,6 +1,6 @@ { "name": "@uncaged/workflow-protocol", - "version": "0.1.0", + "version": "0.5.0", "files": [ "src", "dist", diff --git a/packages/workflow-protocol/src/schemas.ts b/packages/workflow-protocol/src/schemas.ts index 08cf28c..a4e9c58 100644 --- a/packages/workflow-protocol/src/schemas.ts +++ b/packages/workflow-protocol/src/schemas.ts @@ -2,10 +2,13 @@ import type { JSONSchema } from "@uncaged/json-cas"; const ROLE_DEFINITION: JSONSchema = { type: "object", - required: ["description", "systemPrompt", "outputSchema"], + required: ["description", "identity", "prepare", "execute", "report", "outputSchema"], properties: { description: { type: "string" }, - systemPrompt: { type: "string" }, + identity: { type: "string" }, + prepare: { type: "string" }, + execute: { type: "string" }, + report: { type: "string" }, outputSchema: { type: "string", format: "cas_ref" }, }, additionalProperties: false, diff --git a/packages/workflow-protocol/src/types.ts b/packages/workflow-protocol/src/types.ts index 34f4822..6d57bc7 100644 --- a/packages/workflow-protocol/src/types.ts +++ b/packages/workflow-protocol/src/types.ts @@ -18,7 +18,10 @@ export type StepRecord = { export type RoleDefinition = { description: string; - systemPrompt: string; + identity: string; + prepare: string; + execute: string; + report: string; outputSchema: CasRef; }; diff --git a/packages/workflow-util/package.json b/packages/workflow-util/package.json index a74148a..5a17e96 100644 --- a/packages/workflow-util/package.json +++ b/packages/workflow-util/package.json @@ -1,6 +1,6 @@ { "name": "@uncaged/workflow-util", - "version": "0.5.0-alpha.4", + "version": "0.5.0", "files": [ "src", "dist",