Merge pull request 'refactor: rename RoleDefinition fields for clarity' (#366) from refactor/364-rename-role-fields into main

This commit is contained in:
2026-05-22 00:48:23 +00:00
13 changed files with 187 additions and 129 deletions
+32 -15
View File
@@ -85,8 +85,13 @@ 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." goal: "You are a planning agent. Analyze the issue and create a step-by-step plan."
outputSchema: capabilities:
- issue-analysis
- planning
procedure: "Analyze the issue and create a detailed, actionable implementation plan."
output: "Output the plan summary and list of concrete steps."
meta:
type: object type: object
properties: properties:
plan: { type: string } plan: { type: string }
@@ -94,8 +99,13 @@ 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." goal: "You are a developer agent. Implement the plan."
outputSchema: capabilities:
- file-edit
- shell
procedure: "Implement the plan. Write code, tests, and ensure existing tests pass."
output: "List all files changed and provide a summary of the implementation."
meta:
type: object type: object
properties: properties:
filesChanged: { type: array, items: { type: string } } filesChanged: { type: array, items: { type: string } }
@@ -103,8 +113,12 @@ 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." goal: "You are a code reviewer. Review the implementation."
outputSchema: capabilities:
- code-review
procedure: "Review the implementation against the plan."
output: "Approve or reject with detailed comments."
meta:
type: object type: object
properties: properties:
approved: { type: boolean } approved: { type: boolean }
@@ -133,7 +147,7 @@ graph:
Key properties: Key properties:
- **`roles`** — inline role definitions; each `outputSchema` is a JSON Schema (stored as its own CAS node on registration) - **`roles`** — inline role definitions; each `meta` is a JSON Schema (stored as its own CAS node on registration)
- **`conditions`** — named JSONata expressions evaluated against the `ModeratorContext` - **`conditions`** — named JSONata expressions evaluated against the `ModeratorContext`
- **`graph`** — `Record<Role | "$START", Transition[]>` — first matching transition wins; `condition: null` = fallback - **`graph`** — `Record<Role | "$START", Transition[]>` — first matching transition wins; `condition: null` = fallback
- **No agent binding** — agent selection is a deployment concern, configured in `config.yaml` - **No agent binding** — agent selection is a deployment concern, configured in `config.yaml`
@@ -156,7 +170,7 @@ Each `uwf thread step` runs exactly one cycle: moderator → agent → extract.
│ Output: raw string (frontmatter markdown) │ Output: raw string (frontmatter markdown)
│ Phase 3: EXTRACT │ Phase 3: EXTRACT
│ Input: raw agent output + role's outputSchema │ Input: raw agent output + role's meta schema
│ Engine: two-layer extract (frontmatter fast path → LLM fallback) │ Engine: two-layer extract (frontmatter fast path → LLM fallback)
│ Output: CasRef to structured output node │ Output: CasRef to structured output node
@@ -213,7 +227,7 @@ Contract:
- Parses argv - Parses argv
- Loads `.env` from storage root - Loads `.env` from storage root
- Builds `AgentContext` by walking the CAS chain from `threads.yaml` head - Builds `AgentContext` by walking the CAS chain from `threads.yaml` head
- Resolves the role's `outputSchema` and builds `outputFormatInstruction` - Resolves the role's `meta` schema and builds `outputFormatInstruction`
- Calls the agent's `run` function - Calls the agent's `run` function
- Runs two-layer extract on the raw output - Runs two-layer extract on the raw output
- Writes `StepNode` to CAS (output + detail + prev link) - Writes `StepNode` to CAS (output + detail + prev link)
@@ -242,7 +256,7 @@ scope: role
Fixed the login redirect by updating the auth middleware... Fixed the login redirect by updating the auth middleware...
``` ```
The `outputFormatInstruction` (built by `buildOutputFormatInstruction` in `workflow-agent-kit`) is prepended to the role's system prompt, so the deliverable format is the first thing the agent sees. It lists the expected frontmatter fields derived from the role's JSON Schema. The `outputFormatInstruction` (built by `buildOutputFormatInstruction` in `workflow-agent-kit`) is prepended to the role's system prompt, so the deliverable format is the first thing the agent sees. It lists the expected frontmatter fields derived from the role's `meta` JSON Schema.
## Two-layer extract ## Two-layer extract
@@ -253,7 +267,7 @@ Structured output extraction uses a two-layer strategy (`workflow-agent-kit`):
1. Parse YAML frontmatter from raw agent output (`parseFrontmatterMarkdown`) 1. Parse YAML frontmatter from raw agent output (`parseFrontmatterMarkdown`)
2. Validate required fields (`validateFrontmatter`) 2. Validate required fields (`validateFrontmatter`)
3. Build a candidate object from frontmatter fields (`status`, `next`, `confidence`, `artifacts`, `scope`) 3. Build a candidate object from frontmatter fields (`status`, `next`, `confidence`, `artifacts`, `scope`)
4. `store.put()` the candidate against the role's `outputSchema` 4. `store.put()` the candidate against the role's `meta` schema
5. Validate with `json-cas` schema validation 5. Validate with `json-cas` schema validation
6. If valid → return `outputHash` (zero LLM cost) 6. If valid → return `outputHash` (zero LLM cost)
@@ -272,7 +286,7 @@ If the fast path returns `null` (no frontmatter, invalid, or doesn't satisfy sch
`workflow-agent-kit` prepends two pieces of context to the agent's system prompt: `workflow-agent-kit` prepends two pieces of context to the agent's system prompt:
1. **Deliverable format instruction** — generated from the role's `outputSchema`, tells the agent exactly what frontmatter fields to produce and the expected format 1. **Deliverable format instruction** — generated from the role's `meta` schema, tells the agent exactly what frontmatter fields to produce and the expected format
2. **Scope constraint** — "Focus exclusively on YOUR role's deliverable. Do not perform actions outside your role's scope." 2. **Scope constraint** — "Focus exclusively on YOUR role's deliverable. Do not perform actions outside your role's scope."
This ensures agents produce parseable frontmatter output without requiring per-agent format knowledge. This ensures agents produce parseable frontmatter output without requiring per-agent format knowledge.
@@ -289,8 +303,11 @@ payload:
roles: roles:
planner: planner:
description: "Creates implementation plan" description: "Creates implementation plan"
systemPrompt: "You are a planning agent..." goal: "You are a planning agent..."
outputSchema: "5GWKR8TN1V3JA" # cas_ref → JSON Schema node capabilities: [planning, issue-analysis]
procedure: "Analyze the issue and create a plan."
output: "Output the plan summary."
meta: "5GWKR8TN1V3JA" # cas_ref → JSON Schema node
conditions: conditions:
notApproved: notApproved:
description: "Reviewer rejected" description: "Reviewer rejected"
@@ -318,7 +335,7 @@ payload:
start: "4TNVW8KR2B3MA" # cas_ref → StartNode start: "4TNVW8KR2B3MA" # cas_ref → StartNode
prev: "2MXBG6PN4A8JR" # cas_ref → previous StepNode (null for first step) prev: "2MXBG6PN4A8JR" # cas_ref → previous StepNode (null for first step)
role: "developer" role: "developer"
output: "9KRVW3TN5F1QA" # cas_ref → structured output (validated against outputSchema) output: "9KRVW3TN5F1QA" # cas_ref → structured output (validated against meta schema)
detail: "7BQST3VW9F2MA" # cas_ref → execution detail (raw turns, session data) detail: "7BQST3VW9F2MA" # cas_ref → execution detail (raw turns, session data)
agent: "uwf-hermes" # agent command used (plain string) agent: "uwf-hermes" # agent command used (plain string)
``` ```
+27 -15
View File
@@ -112,8 +112,8 @@ uwf-hermes <thread-id> <role>
**约定:** **约定:**
- `uwf step` 负责 moderator 决策,将 role 传给 agent CLI - `uwf step` 负责 moderator 决策,将 role 传给 agent CLI
- agent-kit 根据 thread + role 从 CAS 读 systemPrompt / outputSchema - agent-kit 根据 thread + role 从 CAS 读 goal / capabilities / procedure / output / meta
- agent-kit 组装完整 prompt(role systemPrompt + thread context + user prompt from StartNode) - agent-kit 组装完整 prompt(role goal/capabilities/procedure/output + thread context + user prompt from StartNode)
- agent 执行实际逻辑,agent-kit 负责 extract - agent 执行实际逻辑,agent-kit 负责 extract
- agent 将 StepNode 写入 CAS(含 output、detail、agent、prev),但**不挪链头指针** - agent 将 StepNode 写入 CAS(含 output、detail、agent、prev),但**不挪链头指针**
- stdout 输出新 StepNode 的 CAS hash(纯文本,一行) - stdout 输出新 StepNode 的 CAS hash(纯文本,一行)
@@ -143,7 +143,7 @@ uwf-hermes <thread-id> <role>
#### `Workflow` #### `Workflow`
Roles 和 moderator 内联在 Workflow 中,只有 outputSchema 独立为 CAS 节点(方便 json-cas 校验)。 Roles 和 moderator 内联在 Workflow 中,只有 meta 独立为 CAS 节点(方便 json-cas 校验)。
```yaml ```yaml
type: <workflow-schema-hash> type: <workflow-schema-hash>
@@ -153,16 +153,25 @@ payload:
roles: roles:
planner: planner:
description: "Creates implementation plan" description: "Creates implementation plan"
systemPrompt: "You are a planning agent..." goal: "You are a planning agent..."
outputSchema: "5GWKR8TN1V3JA" # cas_ref → JSON Schema 节点(json-cas 内置) capabilities: [planning, issue-analysis]
procedure: "Analyze the issue and create a plan."
output: "Output the plan summary."
meta: "5GWKR8TN1V3JA" # cas_ref → JSON Schema 节点(json-cas 内置)
developer: developer:
description: "Implements code changes" description: "Implements code changes"
systemPrompt: "You are a developer agent..." goal: "You are a developer agent..."
outputSchema: "8CNWT4KR6D1HV" # cas_ref → JSON Schema 节点 capabilities: [file-edit, shell]
procedure: "Implement the plan."
output: "List all files changed."
meta: "8CNWT4KR6D1HV" # cas_ref → JSON Schema 节点
reviewer: reviewer:
description: "Reviews code changes" description: "Reviews code changes"
systemPrompt: "You are a code reviewer..." goal: "You are a code reviewer..."
outputSchema: "1VPBG9SM5E7WK" # cas_ref → JSON Schema 节点 capabilities: [code-review]
procedure: "Review the implementation."
output: "Approve or reject with comments."
meta: "1VPBG9SM5E7WK" # cas_ref → JSON Schema 节点
conditions: conditions:
needsClarification: needsClarification:
description: "Planner requests clarification from user" description: "Planner requests clarification from user"
@@ -189,7 +198,7 @@ payload:
condition: null condition: null
``` ```
- `roles` — 内联定义,每个 role 的 `outputSchema` 是独立的 cas_ref(指向 json-cas 内置 JSON Schema 节点) - `roles` — 内联定义,每个 role 的 `meta` 是独立的 cas_ref(指向 json-cas 内置 JSON Schema 节点)
- `conditions``Record<Name, JSONata>`,命名条件,方便画图描述 - `conditions``Record<Name, JSONata>`,命名条件,方便画图描述
- `graph``Record<Role | "$START", Transition[]>`,每个 Transition = `{ role, condition }` - `graph``Record<Role | "$START", Transition[]>`,每个 Transition = `{ role, condition }`
- `condition` 引用 conditions 中的 key,`null` = fallback - `condition` 引用 conditions 中的 key,`null` = fallback
@@ -234,14 +243,14 @@ payload:
start: "4TNVW8KR2B3MA" # cas_ref → StartNode(每个 step 都引用) start: "4TNVW8KR2B3MA" # cas_ref → StartNode(每个 step 都引用)
prev: "2MXBG6PN4A8JR" # cas_ref → 前一个 StepNode,第一步为 null prev: "2MXBG6PN4A8JR" # cas_ref → 前一个 StepNode,第一步为 null
role: "developer" role: "developer"
output: "9KRVW3TN5F1QA" # cas_ref → 结构化输出节点(符合 role 的 outputSchema) output: "9KRVW3TN5F1QA" # cas_ref → 结构化输出节点(符合 role 的 meta schema)
detail: "7BQST3VW9F2MA" # cas_ref → 执行详情(content node / 子 workflow terminal StepNode / ...) detail: "7BQST3VW9F2MA" # cas_ref → 执行详情(content node / 子 workflow terminal StepNode / ...)
agent: "uwf-cursor" # 实际使用的 agent 命令(纯字符串) agent: "uwf-cursor" # 实际使用的 agent 命令(纯字符串)
``` ```
- `start` — 每个 StepNode 都直接引用 StartNode,方便随机访问 - `start` — 每个 StepNode 都直接引用 StartNode,方便随机访问
- `prev` — 前一个 StepNode 的 cas_ref,第一步为 `null`(不指向 StartNode) - `prev` — 前一个 StepNode 的 cas_ref,第一步为 `null`(不指向 StartNode)
- `output` — cas_ref,指向符合 role outputSchema 的 CAS 节点,可用 json-cas 校验 - `output` — cas_ref,指向符合 role meta schema 的 CAS 节点,可用 json-cas 校验
- `detail` — cas_ref,指向执行详情。可以是原始 agent 输出(content node),也可以是子 workflow thread 的 terminal StepNode(workflowAsAgent 场景) - `detail` — cas_ref,指向执行详情。可以是原始 agent 输出(content node),也可以是子 workflow thread 的 terminal StepNode(workflowAsAgent 场景)
- `agent` — 纯字符串,不是 CAS 节点 - `agent` — 纯字符串,不是 CAS 节点
@@ -372,7 +381,7 @@ type ThreadId = string;
/** 一个 step 的核心数据,被 StepNode payload 和 JSONata 上下文共享 */ /** 一个 step 的核心数据,被 StepNode payload 和 JSONata 上下文共享 */
type StepRecord = { type StepRecord = {
role: string; role: string;
output: CasRef; // cas_ref → 结构化输出节点(符合 role outputSchema) output: CasRef; // cas_ref → 结构化输出节点(符合 role meta schema)
detail: CasRef; // cas_ref → 执行详情(content node / 子 workflow terminal StepNode) detail: CasRef; // cas_ref → 执行详情(content node / 子 workflow terminal StepNode)
agent: string; // 实际使用的 agent 命令(纯字符串) agent: string; // 实际使用的 agent 命令(纯字符串)
}; };
@@ -383,8 +392,11 @@ type StepRecord = {
```typescript ```typescript
type RoleDefinition = { type RoleDefinition = {
description: string; description: string;
systemPrompt: string; goal: string;
outputSchema: CasRef; // cas_ref → json-cas 内置 JSON Schema 节点 capabilities: string[];
procedure: string;
output: string;
meta: CasRef; // cas_ref → json-cas 内置 JSON Schema 节点
}; };
type Transition = { type Transition = {
+8 -7
View File
@@ -3,22 +3,23 @@ description: "Single-role topic analysis using four-phase role description"
roles: roles:
analyst: analyst:
description: "Analyzes a given topic and produces a structured summary" description: "Analyzes a given topic and produces a structured summary"
identity: | goal: |
You are a research analyst with expertise in breaking down complex topics You are a research analyst with expertise in breaking down complex topics
into clear, structured summaries. You think critically and cite key points. into clear, structured summaries. You think critically and cite key points.
prepare: | capabilities:
Review the topic carefully. Consider multiple perspectives and identify - research
the core question being asked. - critical-thinking
execute: | - structured-writing
procedure: |
Analyze the topic by: Analyze the topic by:
1. Identifying the main thesis or question 1. Identifying the main thesis or question
2. Listing 3-5 key points with brief explanations 2. Listing 3-5 key points with brief explanations
3. Noting any counterarguments or caveats 3. Noting any counterarguments or caveats
Keep your analysis concise (under 500 words). Keep your analysis concise (under 500 words).
report: | output: |
Provide your analysis as markdown under the frontmatter. Provide your analysis as markdown under the frontmatter.
The frontmatter must include your structured findings. The frontmatter must include your structured findings.
outputSchema: meta:
type: object type: object
properties: properties:
thesis: thesis:
+22 -15
View File
@@ -3,11 +3,13 @@ description: "End-to-end issue resolution"
roles: roles:
planner: planner:
description: "Creates implementation plan" description: "Creates implementation plan"
identity: "You are a planning agent. You analyze issues and create step-by-step plans." goal: "You are a planning agent. You analyze issues and create step-by-step plans."
prepare: "Read the issue description and any linked context carefully." capabilities:
execute: "Analyze the issue and create a detailed, actionable implementation plan." - issue-analysis
report: "Output the plan summary and list of concrete steps." - planning
outputSchema: procedure: "Analyze the issue and create a detailed, actionable implementation plan."
output: "Output the plan summary and list of concrete steps."
meta:
type: object type: object
properties: properties:
plan: plan:
@@ -19,11 +21,14 @@ roles:
required: [plan, steps] required: [plan, steps]
developer: developer:
description: "Implements code changes" description: "Implements code changes"
identity: "You are a developer agent. You implement code changes according to plans." goal: "You are a developer agent. You implement code changes according to plans."
prepare: "Load coding tools and review the project structure and conventions." capabilities:
execute: "Implement the plan. Write code, tests, and ensure existing tests pass." - file-edit
report: "List all files changed and provide a summary of the implementation." - shell
outputSchema: - testing
procedure: "Implement the plan. Write code, tests, and ensure existing tests pass."
output: "List all files changed and provide a summary of the implementation."
meta:
type: object type: object
properties: properties:
filesChanged: filesChanged:
@@ -35,11 +40,13 @@ roles:
required: [filesChanged, summary] required: [filesChanged, summary]
reviewer: reviewer:
description: "Reviews code changes" description: "Reviews code changes"
identity: "You are a code reviewer. You review implementations for correctness and quality." goal: "You are a code reviewer. You review implementations for correctness and quality."
prepare: "Review the project's coding standards and conventions." capabilities:
execute: "Review the implementation against the plan. Check for bugs, edge cases, and style." - code-review
report: "Approve or reject with detailed comments explaining your decision." - static-analysis
outputSchema: procedure: "Review the implementation against the plan. Check for bugs, edge cases, and style."
output: "Approve or reject with detailed comments explaining your decision."
meta:
type: object type: object
properties: properties:
approved: approved:
@@ -211,11 +211,11 @@ describe("cmdThreadRead ### Content section", () => {
roles: { roles: {
writer: { writer: {
description: "Write", description: "Write",
identity: "You are a writer.", goal: "You are a writer.",
prepare: "", capabilities: [],
execute: "Write content as requested.", procedure: "Write content as requested.",
report: "Summarize what was written.", output: "Summarize what was written.",
outputSchema: "placeholder00" as CasRef, meta: "placeholder00" as CasRef,
}, },
}, },
conditions: {}, conditions: {},
+1 -1
View File
@@ -500,7 +500,7 @@ function formatThreadReadMarkdown(options: {
]; ];
const roleDef = workflow.roles[item.payload.role]; const roleDef = workflow.roles[item.payload.role];
if (roleDef) { if (roleDef) {
const prompt = roleDef.identity; const prompt = roleDef.goal;
stepLines.push("", "### Prompt", "", prompt); stepLines.push("", "### Prompt", "", prompt);
} }
if (item.payload.detail) { if (item.payload.detail) {
+14 -14
View File
@@ -42,17 +42,17 @@ function isJsonSchema(value: unknown): value is JSONSchema {
return typeof value === "object" && value !== null && !Array.isArray(value); return typeof value === "object" && value !== null && !Array.isArray(value);
} }
async function resolveOutputSchemaRef( async function resolveMetaRef(
uwf: UwfStore, uwf: UwfStore,
roleName: string, roleName: string,
outputSchema: unknown, meta: unknown,
): Promise<CasRef> { ): Promise<CasRef> {
if (!isJsonSchema(outputSchema)) { if (!isJsonSchema(meta)) {
fail(`role "${roleName}": outputSchema must be a JSON Schema object`); fail(`role "${roleName}": meta must be a JSON Schema object`);
} }
const schema: JSONSchema = outputSchema.title === undefined const schema: JSONSchema = meta.title === undefined
? { ...outputSchema, title: roleName } ? { ...meta, title: roleName }
: outputSchema; : meta;
return putSchema(uwf.store, schema); return putSchema(uwf.store, schema);
} }
@@ -62,18 +62,18 @@ async function materializeWorkflowPayload(
): Promise<WorkflowPayload> { ): Promise<WorkflowPayload> {
const roles: Record<string, RoleDefinition> = {}; const roles: Record<string, RoleDefinition> = {};
for (const [roleName, role] of Object.entries(raw.roles)) { for (const [roleName, role] of Object.entries(raw.roles)) {
const outputSchema = await resolveOutputSchemaRef( const meta = await resolveMetaRef(
uwf, uwf,
`${raw.name}.${roleName}`, `${raw.name}.${roleName}`,
role.outputSchema, role.meta,
); );
roles[roleName] = { roles[roleName] = {
description: role.description, description: role.description,
identity: role.identity, goal: role.goal,
prepare: role.prepare, capabilities: role.capabilities,
execute: role.execute, procedure: role.procedure,
report: role.report, output: role.output,
outputSchema, meta,
}; };
} }
return { return {
+10 -7
View File
@@ -14,15 +14,18 @@ function isRoleDefinition(value: unknown): boolean {
if (!isRecord(value)) { if (!isRecord(value)) {
return false; return false;
} }
const outputSchema = value.outputSchema; const meta = value.meta;
const schemaOk = isRecord(outputSchema) && typeof outputSchema.type === "string"; const metaOk = isRecord(meta) && typeof meta.type === "string";
const capabilities = value.capabilities;
const capabilitiesOk =
Array.isArray(capabilities) && capabilities.every((c) => typeof c === "string");
return ( return (
typeof value.description === "string" && typeof value.description === "string" &&
typeof value.identity === "string" && typeof value.goal === "string" &&
typeof value.prepare === "string" && capabilitiesOk &&
typeof value.execute === "string" && typeof value.procedure === "string" &&
typeof value.report === "string" && typeof value.output === "string" &&
schemaOk metaOk
); );
} }
@@ -1,54 +1,71 @@
import { describe, expect, test } from "vitest";
import type { RoleDefinition } from "@uncaged/workflow-protocol"; import type { RoleDefinition } from "@uncaged/workflow-protocol";
import { describe, expect, test } from "vitest";
import { buildRolePrompt } from "../src/build-role-prompt.js"; import { buildRolePrompt } from "../src/build-role-prompt.js";
describe("buildRolePrompt", () => { describe("buildRolePrompt", () => {
test("all fields present", () => { test("all fields present", () => {
const role: RoleDefinition = { const role: RoleDefinition = {
description: "A coder", description: "A coder",
identity: "You are a senior developer.", goal: "You are a senior developer.",
prepare: "Load cursor-agent skill.", capabilities: ["cursor-agent", "file-edit"],
execute: "Implement the feature.", procedure: "Implement the feature.",
report: "Summarize changes.", output: "Summarize changes.",
outputSchema: "placeholder00000" as string, meta: "placeholder00000" as string,
}; };
const result = buildRolePrompt(role); const result = buildRolePrompt(role);
expect(result).toContain("## Identity"); expect(result).toContain("## Goal");
expect(result).toContain("You are a senior developer."); expect(result).toContain("You are a senior developer.");
expect(result).toContain("## Prepare"); expect(result).toContain("## Capabilities");
expect(result).toContain("Load cursor-agent skill."); expect(result).toContain("- cursor-agent");
expect(result).toContain("## Execute"); expect(result).toContain("- file-edit");
expect(result).toContain("## Procedure");
expect(result).toContain("Implement the feature."); expect(result).toContain("Implement the feature.");
expect(result).toContain("## Report"); expect(result).toContain("## Output");
expect(result).toContain("Summarize changes."); expect(result).toContain("Summarize changes.");
}); });
test("empty fields are omitted", () => { test("empty fields are omitted", () => {
const role: RoleDefinition = { const role: RoleDefinition = {
description: "A reviewer", description: "A reviewer",
identity: "You are a code reviewer.", goal: "You are a code reviewer.",
prepare: "", capabilities: [],
execute: "Review the PR diff carefully.", procedure: "Review the PR diff carefully.",
report: "", output: "",
outputSchema: "placeholder00000" as string, meta: "placeholder00000" as string,
}; };
const result = buildRolePrompt(role); const result = buildRolePrompt(role);
expect(result).toContain("## Identity"); expect(result).toContain("## Goal");
expect(result).toContain("## Execute"); expect(result).toContain("## Procedure");
expect(result).not.toContain("## Prepare"); expect(result).not.toContain("## Capabilities");
expect(result).not.toContain("## Report"); expect(result).not.toContain("## Output");
}); });
test("all empty returns empty string", () => { test("all empty returns empty string", () => {
const role: RoleDefinition = { const role: RoleDefinition = {
description: "Minimal", description: "Minimal",
identity: "", goal: "",
prepare: "", capabilities: [],
execute: "", procedure: "",
report: "", output: "",
outputSchema: "placeholder00000" as string, meta: "placeholder00000" as string,
}; };
const result = buildRolePrompt(role); const result = buildRolePrompt(role);
expect(result).toBe(""); expect(result).toBe("");
}); });
test("capabilities rendered as bullet list", () => {
const role: RoleDefinition = {
description: "Agent",
goal: "",
capabilities: ["search", "code", "browse"],
procedure: "",
output: "",
meta: "placeholder00000" as string,
};
const result = buildRolePrompt(role);
expect(result).toContain("## Capabilities");
expect(result).toContain("- search");
expect(result).toContain("- code");
expect(result).toContain("- browse");
});
}); });
@@ -3,26 +3,27 @@ import type { RoleDefinition } from "@uncaged/workflow-protocol";
/** /**
* Build the role prompt from a RoleDefinition. * Build the role prompt from a RoleDefinition.
* *
* Assembles structured sections: Identity, Prepare, Execute, Report. * Assembles structured sections: Goal, Capabilities, Procedure, Output.
* Empty strings are omitted from the output. * Empty strings and empty arrays are omitted from the output.
*/ */
export function buildRolePrompt(role: RoleDefinition): string { export function buildRolePrompt(role: RoleDefinition): string {
const sections: string[] = []; const sections: string[] = [];
if (role.identity !== "") { if (role.goal !== "") {
sections.push(`## Identity\n\n${role.identity}`); sections.push(`## Goal\n\n${role.goal}`);
} }
if (role.prepare !== "") { if (role.capabilities.length > 0) {
sections.push(`## Prepare\n\n${role.prepare}`); const list = role.capabilities.map((c) => `- ${c}`).join("\n");
sections.push(`## Capabilities\n\n${list}`);
} }
if (role.execute !== "") { if (role.procedure !== "") {
sections.push(`## Execute\n\n${role.execute}`); sections.push(`## Procedure\n\n${role.procedure}`);
} }
if (role.report !== "") { if (role.output !== "") {
sections.push(`## Report\n\n${role.report}`); sections.push(`## Output\n\n${role.output}`);
} }
return sections.join("\n\n"); return sections.join("\n\n");
+4 -4
View File
@@ -131,13 +131,13 @@ export function createAgent(options: AgentOptions): () => Promise<void> {
fail(`unknown role: ${role}`); fail(`unknown role: ${role}`);
} }
const outputSchema = getSchema(ctx.meta.store, roleDef.outputSchema); const metaSchema = getSchema(ctx.meta.store, roleDef.meta);
if (outputSchema !== null) { if (metaSchema !== null) {
ctx.outputFormatInstruction = buildOutputFormatInstruction(outputSchema); ctx.outputFormatInstruction = buildOutputFormatInstruction(metaSchema);
} }
const agentResult = await runAgent(options, ctx); const agentResult = await runAgent(options, ctx);
const outputHash = await extractOutput(agentResult.output, roleDef.outputSchema, storageRoot, ctx); const outputHash = await extractOutput(agentResult.output, roleDef.meta, storageRoot, ctx);
const stepHash = await persistStep({ const stepHash = await persistStep({
ctx, ctx,
outputHash, outputHash,
+6 -6
View File
@@ -2,14 +2,14 @@ import type { JSONSchema } from "@uncaged/json-cas";
const ROLE_DEFINITION: JSONSchema = { const ROLE_DEFINITION: JSONSchema = {
type: "object", type: "object",
required: ["description", "identity", "prepare", "execute", "report", "outputSchema"], required: ["description", "goal", "capabilities", "procedure", "output", "meta"],
properties: { properties: {
description: { type: "string" }, description: { type: "string" },
identity: { type: "string" }, goal: { type: "string" },
prepare: { type: "string" }, capabilities: { type: "array", items: { type: "string" } },
execute: { type: "string" }, procedure: { type: "string" },
report: { type: "string" }, output: { type: "string" },
outputSchema: { type: "string", format: "cas_ref" }, meta: { type: "string", format: "cas_ref" },
}, },
additionalProperties: false, additionalProperties: false,
}; };
+5 -5
View File
@@ -18,11 +18,11 @@ export type StepRecord = {
export type RoleDefinition = { export type RoleDefinition = {
description: string; description: string;
identity: string; goal: string;
prepare: string; capabilities: string[];
execute: string; procedure: string;
report: string; output: string;
outputSchema: CasRef; meta: CasRef;
}; };
export type Transition = { export type Transition = {