Merge pull request 'feat: add actor skill — frontmatter protocol + CAS reference' (#545) from feat/540-skill-actor into main

This commit is contained in:
2026-05-26 15:44:31 +00:00
5 changed files with 91 additions and 2 deletions
@@ -6,6 +6,7 @@ import { describe, expect, test } from "vitest";
const __dirname = dirname(fileURLToPath(import.meta.url));
import {
cmdSkillActor,
cmdSkillArchitecture,
cmdSkillCli,
cmdSkillList,
@@ -21,8 +22,8 @@ describe("skill commands", () => {
expect(result).toContain("architecture");
expect(result).toContain("yaml");
expect(result).toContain("moderator");
expect(result).toContain("actor");
for (const name of result) {
expect(typeof name).toBe("string");
expect(name).toMatch(/^\S+$/);
}
});
@@ -62,6 +63,15 @@ describe("skill commands", () => {
expect(result).toContain("uwf");
});
test("skill actor returns non-empty markdown string", () => {
const result = cmdSkillActor();
expect(typeof result).toBe("string");
expect(result).toContain("frontmatter");
expect(result).toContain("CAS");
expect(result).toContain("status");
expect(result.length).toBeGreaterThan(200);
});
test("skill help subcommand is suppressed", () => {
const output = execFileSync("bun", ["src/cli.ts", "skill", "--help"], {
cwd: join(__dirname, "..", ".."),
@@ -73,6 +83,7 @@ describe("skill commands", () => {
expect(output).toContain("architecture");
expect(output).toContain("yaml");
expect(output).toContain("moderator");
expect(output).toContain("actor");
expect(output).toContain("list");
});
});
+8
View File
@@ -17,6 +17,7 @@ import { cmdConfigGet, cmdConfigList, cmdConfigSet } from "./commands/config.js"
import { cmdLogClean, cmdLogList, cmdLogShow } from "./commands/log.js";
import { cmdSetup, cmdSetupInteractive } from "./commands/setup.js";
import {
cmdSkillActor,
cmdSkillArchitecture,
cmdSkillCli,
cmdSkillList,
@@ -503,6 +504,13 @@ skill
console.log(cmdSkillYaml());
});
skill
.command("actor")
.description("Print the actor reference (frontmatter protocol + CAS)")
.action(() => {
console.log(cmdSkillActor());
});
skill
.command("moderator")
.description("Print the moderator reference")
+2 -1
View File
@@ -1,11 +1,12 @@
export {
generateActorReference as cmdSkillActor,
generateArchitectureReference as cmdSkillArchitecture,
generateCliReference as cmdSkillCli,
generateModeratorReference as cmdSkillModerator,
generateYamlReference as cmdSkillYaml,
} from "@uncaged/workflow-util";
const SKILL_NAMES = ["cli", "architecture", "yaml", "moderator"] as const;
const SKILL_NAMES = ["cli", "architecture", "yaml", "moderator", "actor"] as const;
export function cmdSkillList(): ReadonlyArray<string> {
return [...SKILL_NAMES];
@@ -0,0 +1,68 @@
export function generateActorReference(): string {
return `# Actor Reference
You are executing a workflow role. Your system prompt defines your goal, procedure, and output requirements. This reference covers two things you need to know about the workflow engine.
## 1. Frontmatter Output Protocol
Your response **MUST** begin with a YAML frontmatter block at byte position 0 — no preamble text before it.
\`\`\`
---
status: done
myField: some value
---
... markdown body (your work, explanation, notes) ...
\`\`\`
### Standard Field
| Field | Values | Default | Description |
|-------|--------|---------|-------------|
| \`status\` | \`done\`, \`needs_input\`, \`in_progress\`, \`failed\` | \`done\` | Completion signal — determines which graph edge the moderator follows next |
### Schema-Defined Fields
Your role's output schema (shown in the system prompt under "Deliverable Format") defines additional fields. Output **only** the fields listed there — do not invent extra fields.
### Body
Everything after the closing \`---\` fence is the markdown body. Use it for explanations, logs, or human-readable notes. The body is stored but not parsed by the engine.
### Retry
If the engine cannot parse your frontmatter, it will ask you to retry (up to 2 times). Just output the corrected frontmatter block — don't panic.
## 2. CAS (Content-Addressable Store)
Your frontmatter output is automatically stored in CAS. You can also **use CAS directly** to store intermediate artifacts, build merkle DAGs for large outputs, or reference data from previous steps.
### Commands
\`\`\`
uwf cas put-text <text> # store plain text, print hash
uwf cas put <type-hash> <json> # store typed JSON data, print hash
uwf cas get <hash> # read a CAS node (type + payload)
uwf cas has <hash> # check if a hash exists
uwf cas refs <hash> # list direct references from a node
uwf cas walk <hash> # recursive traversal from a node
uwf cas schema list # list registered schemas
uwf cas schema get <hash> # show a schema definition
\`\`\`
### Merkle DAG Pattern
For large outputs, store parts individually and reference their hashes:
\`\`\`bash
# Store individual sections
HASH1=$(uwf cas put-text "section 1 content")
HASH2=$(uwf cas put-text "section 2 content")
# Reference hashes in your frontmatter or in a parent node
\`\`\`
This enables progressive loading — consumers can fetch the root and resolve children on demand.
`;
}
+1
View File
@@ -1,3 +1,4 @@
export { generateActorReference } from "./actor-reference.js";
export { generateArchitectureReference } from "./architecture-reference.js";
export { encodeUint64AsCrockford } from "./base32.js";
export { generateCliReference } from "./cli-reference.js";