Compare commits

...

12 Commits

Author SHA1 Message Date
xiaoju e8dd398f28 fix: add workflow-agent-claude-code to publish order
小橘 <xiaoju@shazhou.work>
2026-05-27 00:00:09 +00:00
xiaoju 61d95cc47f chore: release v0.5.1
- Add 5 persona-based skills (actor, user, author, developer, adapter)
- Fix skill CLI description truncation (#549)

小橘 <xiaoju@shazhou.work>
2026-05-26 17:30:00 +00:00
xiaoju 577fb27470 feat: add adapter skill + fix commit scope (#549)
CI / test (pull_request) Successful in 1m30s
- Add 'uwf skill adapter' — guide for building agent adapters.
  Covers: createAgent factory, AgentContext/AgentRunResult types,
  prompt building helpers, session detail storage, registration.
- Fix developer skill: agent-kit → util-agent in commit scope.

Refs #542
Fixes #549
2026-05-26 17:24:48 +00:00
xiaomo 5475dd3f5c Merge pull request 'feat: add developer skill — coding conventions + architecture guide' (#548) from feat/541-skill-developer into main
CI / test (push) Successful in 1m28s
2026-05-26 17:19:16 +00:00
xiaoju 09b7ddf6d0 feat: add developer skill — coding conventions + architecture guide
CI / test (pull_request) Successful in 1m26s
Adds 'uwf skill developer' for contributors to the workflow engine.
Covers: monorepo structure, dependency layers, functional-first conventions,
error handling, logging with tagged logger, development workflow,
testing, publishing, key modules (moderator, extract pipeline, createAgent).

Refs #541
2026-05-26 17:11:07 +00:00
xiaomo c4e94bbe56 Merge pull request 'feat: add author skill — workflow YAML design guide' (#547) from feat/539-skill-author into main
CI / test (push) Successful in 1m11s
2026-05-26 17:04:50 +00:00
xiaoju dbefe793f2 feat: add author skill — workflow YAML design guide
CI / test (pull_request) Successful in 1m4s
Adds 'uwf skill author' for agents/humans designing workflow definitions.
Covers: YAML structure, role definition, frontmatter schema design,
graph routing, edge prompts, self-testing, and common pitfalls.

Refs #539
2026-05-26 17:02:53 +00:00
xiaomo 6483bc4861 Merge pull request 'feat: add user skill — CLI guide with quick start' (#546) from feat/538-skill-user into main
CI / test (push) Successful in 1m40s
2026-05-26 16:27:43 +00:00
xiaoju fecb02b115 feat: add user skill — CLI guide with quick start and typical workflows
CI / test (pull_request) Successful in 1m26s
Adds 'uwf skill user' command for agents/humans using the uwf CLI.
Covers setup, workflow management, thread lifecycle, step operations,
CAS queries, logging, and global options with a Quick Start guide.

Refs #538
2026-05-26 16:24:39 +00:00
xiaomo 87938c1886 Merge pull request 'feat: add actor skill — frontmatter protocol + CAS reference' (#545) from feat/540-skill-actor into main
CI / test (push) Failing after 23s
2026-05-26 15:44:31 +00:00
xiaoju 95a130136b feat: add actor skill — frontmatter protocol + CAS reference
CI / test (pull_request) Failing after 8m9s
Adds 'uwf skill actor' command for agents executing workflow roles.
Covers the two things an actor needs to know:
1. Frontmatter output protocol (status field, schema-defined fields)
2. CAS operations (put, get, refs, walk, merkle DAG pattern)

Refs #540
2026-05-26 15:32:03 +00:00
xiaomo aba5642908 Merge pull request 'ci: use test:ci to skip integration tests in CI' (#543) from fix/ci-skip-integration-tests into main
CI / test (push) Successful in 3m32s
2026-05-26 15:26:02 +00:00
25 changed files with 869 additions and 39 deletions
-30
View File
@@ -1,30 +0,0 @@
{
"mode": "exit",
"tag": "alpha",
"initialVersions": {
"@uncaged/cli-workflow": "0.4.5",
"@uncaged/workflow-agent-cursor": "0.4.5",
"@uncaged/workflow-agent-hermes": "0.4.5",
"@uncaged/workflow-agent-llm": "0.4.5",
"@uncaged/workflow-agent-react": "0.4.5",
"@uncaged/workflow-cas": "0.4.5",
"@uncaged/workflow-dashboard": "0.1.0",
"@uncaged/workflow-execute": "0.4.5",
"@uncaged/workflow-gateway": "0.4.5",
"@uncaged/workflow-protocol": "0.4.5",
"@uncaged/workflow-reactor": "0.4.5",
"@uncaged/workflow-register": "0.4.5",
"@uncaged/workflow-runtime": "0.4.5",
"@uncaged/workflow-template-develop": "0.4.5",
"@uncaged/workflow-template-solve-issue": "0.4.5",
"@uncaged/workflow-util": "0.4.5",
"@uncaged/workflow-util-agent": "0.4.5"
},
"changesets": [
"env-api-unify",
"fix-internal-deps",
"fix-publish-src",
"fix-workspace-deps",
"rfc-252-agent-fn"
]
}
+11
View File
@@ -0,0 +1,11 @@
# @uncaged/cli-workflow
## 0.5.1
### Patch Changes
- Add 5 persona-based skills (actor, user, author, developer, adapter) and fix skill CLI description truncation
- Updated dependencies
- @uncaged/workflow-util@0.5.1
- @uncaged/workflow-protocol@0.5.1
- @uncaged/workflow-util-agent@0.5.1
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@uncaged/cli-workflow", "name": "@uncaged/cli-workflow",
"version": "0.5.0", "version": "0.5.1",
"files": [ "files": [
"src", "src",
"dist", "dist",
@@ -6,10 +6,15 @@ import { describe, expect, test } from "vitest";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
import { import {
cmdSkillActor,
cmdSkillAdapter,
cmdSkillArchitecture, cmdSkillArchitecture,
cmdSkillAuthor,
cmdSkillCli, cmdSkillCli,
cmdSkillDeveloper,
cmdSkillList, cmdSkillList,
cmdSkillModerator, cmdSkillModerator,
cmdSkillUser,
cmdSkillYaml, cmdSkillYaml,
} from "../commands/skill.js"; } from "../commands/skill.js";
@@ -21,8 +26,12 @@ describe("skill commands", () => {
expect(result).toContain("architecture"); expect(result).toContain("architecture");
expect(result).toContain("yaml"); expect(result).toContain("yaml");
expect(result).toContain("moderator"); expect(result).toContain("moderator");
expect(result).toContain("actor");
expect(result).toContain("user");
expect(result).toContain("author");
expect(result).toContain("developer");
expect(result).toContain("adapter");
for (const name of result) { for (const name of result) {
expect(typeof name).toBe("string");
expect(name).toMatch(/^\S+$/); expect(name).toMatch(/^\S+$/);
} }
}); });
@@ -62,6 +71,54 @@ describe("skill commands", () => {
expect(result).toContain("uwf"); 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 user returns non-empty markdown string", () => {
const result = cmdSkillUser();
expect(typeof result).toBe("string");
expect(result).toContain("uwf");
expect(result).toContain("thread");
expect(result).toContain("workflow");
expect(result).toContain("Quick Start");
expect(result.length).toBeGreaterThan(500);
});
test("skill author returns non-empty markdown string", () => {
const result = cmdSkillAuthor();
expect(typeof result).toBe("string");
expect(result).toContain("frontmatter");
expect(result).toContain("graph");
expect(result).toContain("$START");
expect(result).toContain("$END");
expect(result).toContain("$status");
expect(result.length).toBeGreaterThan(500);
});
test("skill developer returns non-empty markdown string", () => {
const result = cmdSkillDeveloper();
expect(typeof result).toBe("string");
expect(result).toContain("Monorepo");
expect(result).toContain("CAS");
expect(result).toContain("Biome");
expect(result.length).toBeGreaterThan(500);
});
test("skill adapter returns non-empty markdown string", () => {
const result = cmdSkillAdapter();
expect(typeof result).toBe("string");
expect(result).toContain("createAgent");
expect(result).toContain("AgentContext");
expect(result).toContain("frontmatter");
expect(result.length).toBeGreaterThan(500);
});
test("skill help subcommand is suppressed", () => { test("skill help subcommand is suppressed", () => {
const output = execFileSync("bun", ["src/cli.ts", "skill", "--help"], { const output = execFileSync("bun", ["src/cli.ts", "skill", "--help"], {
cwd: join(__dirname, "..", ".."), cwd: join(__dirname, "..", ".."),
@@ -73,6 +130,11 @@ describe("skill commands", () => {
expect(output).toContain("architecture"); expect(output).toContain("architecture");
expect(output).toContain("yaml"); expect(output).toContain("yaml");
expect(output).toContain("moderator"); expect(output).toContain("moderator");
expect(output).toContain("actor");
expect(output).toContain("user");
expect(output).toContain("author");
expect(output).toContain("developer");
expect(output).toContain("adapter");
expect(output).toContain("list"); expect(output).toContain("list");
}); });
}); });
+40
View File
@@ -17,10 +17,15 @@ import { cmdConfigGet, cmdConfigList, cmdConfigSet } from "./commands/config.js"
import { cmdLogClean, cmdLogList, cmdLogShow } from "./commands/log.js"; import { cmdLogClean, cmdLogList, cmdLogShow } from "./commands/log.js";
import { cmdSetup, cmdSetupInteractive } from "./commands/setup.js"; import { cmdSetup, cmdSetupInteractive } from "./commands/setup.js";
import { import {
cmdSkillActor,
cmdSkillAdapter,
cmdSkillArchitecture, cmdSkillArchitecture,
cmdSkillAuthor,
cmdSkillCli, cmdSkillCli,
cmdSkillDeveloper,
cmdSkillList, cmdSkillList,
cmdSkillModerator, cmdSkillModerator,
cmdSkillUser,
cmdSkillYaml, cmdSkillYaml,
} from "./commands/skill.js"; } from "./commands/skill.js";
import { cmdStepFork, cmdStepList, cmdStepRead, cmdStepShow } from "./commands/step.js"; import { cmdStepFork, cmdStepList, cmdStepRead, cmdStepShow } from "./commands/step.js";
@@ -503,6 +508,34 @@ skill
console.log(cmdSkillYaml()); console.log(cmdSkillYaml());
}); });
skill
.command("actor")
.description("Print the actor reference (frontmatter protocol + CAS)")
.action(() => {
console.log(cmdSkillActor());
});
skill
.command("adapter")
.description("Print the adapter reference (building agent adapters)")
.action(() => {
console.log(cmdSkillAdapter());
});
skill
.command("author")
.description("Print the author reference (workflow YAML design guide)")
.action(() => {
console.log(cmdSkillAuthor());
});
skill
.command("developer")
.description("Print the developer reference (coding conventions + architecture)")
.action(() => {
console.log(cmdSkillDeveloper());
});
skill skill
.command("moderator") .command("moderator")
.description("Print the moderator reference") .description("Print the moderator reference")
@@ -510,6 +543,13 @@ skill
console.log(cmdSkillModerator()); console.log(cmdSkillModerator());
}); });
skill
.command("user")
.description("Print the user reference (CLI guide + typical workflows)")
.action(() => {
console.log(cmdSkillUser());
});
skill skill
.command("list") .command("list")
.description("List all available skill names") .description("List all available skill names")
+16 -1
View File
@@ -1,11 +1,26 @@
export { export {
generateActorReference as cmdSkillActor,
generateAdapterReference as cmdSkillAdapter,
generateArchitectureReference as cmdSkillArchitecture, generateArchitectureReference as cmdSkillArchitecture,
generateAuthorReference as cmdSkillAuthor,
generateCliReference as cmdSkillCli, generateCliReference as cmdSkillCli,
generateDeveloperReference as cmdSkillDeveloper,
generateModeratorReference as cmdSkillModerator, generateModeratorReference as cmdSkillModerator,
generateUserReference as cmdSkillUser,
generateYamlReference as cmdSkillYaml, generateYamlReference as cmdSkillYaml,
} from "@uncaged/workflow-util"; } from "@uncaged/workflow-util";
const SKILL_NAMES = ["cli", "architecture", "yaml", "moderator"] as const; const SKILL_NAMES = [
"cli",
"architecture",
"yaml",
"moderator",
"actor",
"user",
"author",
"developer",
"adapter",
] as const;
export function cmdSkillList(): ReadonlyArray<string> { export function cmdSkillList(): ReadonlyArray<string> {
return [...SKILL_NAMES]; return [...SKILL_NAMES];
@@ -0,0 +1,9 @@
# @uncaged/workflow-agent-builtin
## 0.5.1
### Patch Changes
- Updated dependencies
- @uncaged/workflow-util@0.5.1
- @uncaged/workflow-util-agent@0.5.1
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@uncaged/workflow-agent-builtin", "name": "@uncaged/workflow-agent-builtin",
"version": "0.5.0", "version": "0.5.1",
"files": [ "files": [
"src", "src",
"dist", "dist",
@@ -0,0 +1,9 @@
# @uncaged/workflow-agent-claude-code
## 0.5.1
### Patch Changes
- Updated dependencies
- @uncaged/workflow-util@0.5.1
- @uncaged/workflow-util-agent@0.5.1
@@ -1,6 +1,6 @@
{ {
"name": "@uncaged/workflow-agent-claude-code", "name": "@uncaged/workflow-agent-claude-code",
"version": "0.1.0", "version": "0.5.1",
"files": [ "files": [
"src", "src",
"dist", "dist",
@@ -0,0 +1,10 @@
# @uncaged/workflow-agent-hermes
## 0.5.1
### Patch Changes
- Updated dependencies
- @uncaged/workflow-util@0.5.1
- @uncaged/workflow-protocol@0.5.1
- @uncaged/workflow-util-agent@0.5.1
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@uncaged/workflow-agent-hermes", "name": "@uncaged/workflow-agent-hermes",
"version": "0.5.0", "version": "0.5.1",
"files": [ "files": [
"src", "src",
"dist", "dist",
+3
View File
@@ -0,0 +1,3 @@
# @uncaged/workflow-protocol
## 0.5.1
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@uncaged/workflow-protocol", "name": "@uncaged/workflow-protocol",
"version": "0.5.0", "version": "0.5.1",
"files": [ "files": [
"src", "src",
"dist", "dist",
@@ -0,0 +1,9 @@
# @uncaged/workflow-util-agent
## 0.5.1
### Patch Changes
- Updated dependencies
- @uncaged/workflow-util@0.5.1
- @uncaged/workflow-protocol@0.5.1
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@uncaged/workflow-util-agent", "name": "@uncaged/workflow-util-agent",
"version": "0.5.0", "version": "0.5.1",
"files": [ "files": [
"src", "src",
"dist", "dist",
+7
View File
@@ -0,0 +1,7 @@
# @uncaged/workflow-util
## 0.5.1
### Patch Changes
- Add 5 persona-based skills (actor, user, author, developer, adapter) and fix skill CLI description truncation
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@uncaged/workflow-util", "name": "@uncaged/workflow-util",
"version": "0.5.0", "version": "0.5.1",
"files": [ "files": [
"src", "src",
"dist", "dist",
@@ -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.
`;
}
@@ -0,0 +1,163 @@
export function generateAdapterReference(): string {
return `# Adapter Reference
Guide for building a new agent adapter (CLI binary) for the workflow engine.
## What Is an Adapter
An adapter is a CLI command (e.g. \`uwf-hermes\`, \`uwf-builtin\`) that the engine spawns to execute a role. It bridges the workflow engine and an LLM/agent backend. The engine calls it with:
\`\`\`
uwf-<name> --thread <id> --role <role> --prompt <text>
\`\`\`
The adapter must produce frontmatter markdown output. The engine handles argument parsing, context building, output extraction, and CAS persistence — you just implement the LLM interaction.
## Quick Start
\`\`\`typescript
import { createAgent } from "@uncaged/workflow-util-agent";
import type { AgentContext, AgentRunResult, AgentContinueFn, AgentRunFn } from "@uncaged/workflow-util-agent";
const run: AgentRunFn = async (ctx: AgentContext): Promise<AgentRunResult> => {
// 1. Build your prompt from ctx
// 2. Call your LLM backend
// 3. Return the result
return { output: rawMarkdown, detailHash, sessionId };
};
const continue_: AgentContinueFn = async (sessionId, message, store) => {
// Resume an existing session with a correction message
return { output: correctedMarkdown, detailHash, sessionId };
};
const main = createAgent({ name: "my-agent", run, continue: continue_ });
main();
\`\`\`
## The \`createAgent\` Factory
\`createAgent(options)\` returns an async \`main()\` function that handles the full lifecycle:
1. Parses CLI args (\`--thread\`, \`--role\`, \`--prompt\`)
2. Loads \`.env\` from storage root
3. Builds \`AgentContext\` (thread history, workflow definition, role prompt)
4. Injects \`outputFormatInstruction\` from the role's frontmatter schema
5. Calls your \`run(ctx)\` function
6. Extracts frontmatter from your output via \`tryFrontmatterFastPath()\`
7. If extraction fails, calls your \`continue(sessionId, correctionMessage, store)\` up to 2 times
8. Persists the validated output as a CAS step node
9. Prints the step hash to stdout
You only implement \`run\` and \`continue\`.
## AgentOptions
\`\`\`typescript
type AgentOptions = {
name: string; // Adapter name (used in step records as "uwf-<name>")
run: AgentRunFn; // Execute a role from scratch
continue: AgentContinueFn; // Resume a session for frontmatter correction
};
\`\`\`
## AgentContext
The \`ctx\` object passed to your \`run\` function:
| Field | Type | Description |
|-------|------|-------------|
| \`threadId\` | \`string\` | Thread ULID |
| \`role\` | \`string\` | Role name being executed |
| \`edgePrompt\` | \`string\` | Moderator's task instruction for this step |
| \`workflow\` | \`WorkflowPayload\` | Full workflow definition (roles, graph) |
| \`start\` | \`StartNodePayload\` | Thread start data (workflow hash, user prompt) |
| \`steps\` | \`StepContext[]\` | Previous steps with expanded outputs |
| \`store\` | \`Store\` | CAS store for reading/writing data |
| \`outputFormatInstruction\` | \`string\` | Frontmatter format instruction (inject into system prompt) |
| \`isFirstVisit\` | \`boolean\` | True if this role hasn't run before in this thread |
## AgentRunResult
Your \`run\` and \`continue\` functions must return:
\`\`\`typescript
type AgentRunResult = {
output: string; // Raw markdown with frontmatter (must start with ---)
detailHash: string; // CAS hash of session detail (turn history, metadata)
sessionId: string; // Session ID for potential continue() calls
};
\`\`\`
## Building the Prompt
Use helpers from \`@uncaged/workflow-util-agent\`:
| Helper | Purpose |
|--------|---------|
| \`buildRolePrompt(roleDef)\` | Assemble Goal/Capabilities/Prepare/Procedure/Output sections |
| \`buildContinuationPrompt(steps, role, edgePrompt)\` | For re-entry: steps since last visit + edge prompt |
| \`ctx.outputFormatInstruction\` | Pre-built frontmatter format block (inject into system prompt) |
Typical system prompt structure:
\`\`\`
[outputFormatInstruction]
[rolePrompt from buildRolePrompt()]
[workflow metadata]
\`\`\`
## Storing Session Detail
Store your turn history as a CAS merkle DAG for debugging and replay:
\`\`\`typescript
// Store each turn as a CAS text node
const turnHash = await store.put(textSchema, { content: turnData });
// Build a detail node referencing all turns
const detailHash = await store.put(detailSchema, { turns: turnHashes });
\`\`\`
The \`detailHash\` is preserved from the first \`run()\` call — retry \`continue()\` calls don't overwrite it.
## Registration
Register your adapter in \`~/.uncaged/workflow/config.yaml\`:
\`\`\`yaml
agents:
my-agent:
command: uwf-my-agent
args: []
\`\`\`
Use it:
\`\`\`bash
uwf thread exec <thread-id> --agent my-agent
\`\`\`
Or set as default:
\`\`\`yaml
defaultAgent: my-agent
\`\`\`
## Existing Adapters
| Adapter | Package | Backend |
|---------|---------|---------|
| \`uwf-hermes\` | \`@uncaged/workflow-agent-hermes\` | Hermes ACP (chat sessions) |
| \`uwf-builtin\` | \`@uncaged/workflow-agent-builtin\` | Direct OpenAI API (tools + loop) |
| \`uwf-claude-code\` | \`@uncaged/workflow-agent-claude-code\` | Claude Code CLI |
Study these for patterns on prompt building, session management, and detail storage.
## Checklist
1. Implement \`run(ctx)\` — build prompt, call LLM, return output + detailHash + sessionId
2. Implement \`continue(sessionId, message, store)\` — resume session for frontmatter correction
3. Store session detail as CAS nodes (for debugging)
4. Ensure output starts with \`---\` frontmatter block
5. Add a \`bin\` entry in \`package.json\` for the CLI command
6. Register in config.yaml and test with \`uwf thread exec --agent <name>\`
`;
}
@@ -0,0 +1,183 @@
export function generateAuthorReference(): string {
return `# Author Reference
Guide for designing and writing workflow YAML definitions.
## Workflow Structure
\`\`\`yaml
name: solve-issue # verb-first kebab-case
description: "..." # human-readable summary
roles: # named actors
planner:
description: "..." # short purpose
goal: "..." # system-level goal for the agent
capabilities: [...] # skill keywords the agent should load
procedure: | # step-by-step instructions
1. Do this
2. Do that
output: "..." # what the agent should produce
frontmatter: # JSON Schema for structured output
oneOf:
- properties:
$status: { const: "ready" }
plan: { type: string }
required: [$status, plan]
- properties:
$status: { const: "failed" }
error: { type: string }
required: [$status, error]
graph: # status-based routing
$START:
_: { role: planner, prompt: "Analyze the issue." }
planner:
ready: { role: developer, prompt: "Implement {{{plan}}}." }
failed: { role: $END, prompt: "Failed: {{{error}}}" }
\`\`\`
## Role Definition
| Field | Purpose |
|-------|---------|
| \`description\` | Short description for humans and moderator context |
| \`goal\` | Injected as the agent's system-level objective |
| \`capabilities\` | Keyword tags — agent loads matching skills before starting |
| \`procedure\` | Step-by-step instructions the agent follows |
| \`output\` | Describes what to produce and which \`$status\` values to use |
| \`frontmatter\` | JSON Schema defining the structured output fields |
### Role Design Principles
- **Single responsibility** — each role does one thing well
- **Minimal context** — don't overload a role with too many steps; split if needed
- **Clear status values** — each status should map to a distinct graph edge
- **Explicit output** — tell the agent exactly what \`$status\` values are valid
## Frontmatter Schema
The \`frontmatter\` field is a standard JSON Schema. It defines the structured fields the agent must output in YAML frontmatter.
### \`$status\` Field
\`$status\` is the only standard field. Its value determines which graph edge the moderator follows. Use \`const\` to constrain each variant:
\`\`\`yaml
frontmatter:
oneOf:
- properties:
$status: { const: "done" }
result: { type: string }
required: [$status, result]
- properties:
$status: { const: "failed" }
error: { type: string }
required: [$status, error]
\`\`\`
### Custom Fields
Add any fields you need for data passing between roles. These are available in edge prompts via Mustache templates.
### Flat Schema (Single Status)
When a role has only one outcome:
\`\`\`yaml
frontmatter:
properties:
$status: { const: "done" }
summary: { type: string }
required: [$status, summary]
\`\`\`
## Graph Routing
The graph maps each role's \`$status\` values to the next role:
\`\`\`
graph[role][$status] → { role: nextRole, prompt: edgePrompt }
\`\`\`
### Special Nodes
| Node | Purpose |
|------|---------|
| \`$START\` | Entry point — status key is always \`_\` (unconditional) |
| \`$END\` | Terminal — thread completes and is archived |
### Edge Prompts
Use triple-brace Mustache (\`{{{field}}}\`) to pass data from the previous step's output:
\`\`\`yaml
graph:
planner:
ready: { role: developer, prompt: "Implement plan {{{plan}}} in {{{repoPath}}}." }
\`\`\`
The fields referenced must exist in the source role's frontmatter schema.
### Loops and Branching
Roles can route back to previous roles (loops) or to different roles based on status (branching):
\`\`\`yaml
graph:
reviewer:
approved: { role: tester, prompt: "Run tests." }
rejected: { role: developer, prompt: "Fix: {{{comments}}}" } # loop back
\`\`\`
### Fail Routing
Route failures to a cleanup role or \`$END\`:
\`\`\`yaml
graph:
developer:
done: { role: reviewer, prompt: "Review changes." }
failed: { role: cleanup, prompt: "Clean up: {{{error}}}" }
\`\`\`
## Self-Testing
### Step-by-Step Verification
\`\`\`bash
# Start a thread directly from YAML file (no registration needed)
uwf thread start my-workflow.yaml -p "Test prompt"
# Or register first, then start by name
uwf workflow add my-workflow.yaml
uwf thread start my-workflow -p "Test prompt"
# Execute one step at a time to verify routing
uwf thread exec <thread-id>
# Inspect step output
uwf step list <thread-id>
uwf step show <step-hash>
# Check the CAS data
uwf cas get <output-hash>
\`\`\`
### Validation Checklist
1. Every \`$status\` value in a role's frontmatter has a matching edge in the graph
2. Every field referenced in edge prompts (\`{{{field}}}\`) exists in the source role's schema
3. Every role referenced in the graph exists in \`roles\`
4. \`$START\` has exactly one edge with key \`_\`
5. At least one path leads to \`$END\`
6. No orphan roles (defined but never routed to)
## Common Pitfalls
- **Missing graph edge** — if a role can produce \`$status: failed\` but the graph has no \`failed\` edge, the moderator will error
- **Mustache field mismatch** — referencing \`{{{branch}}}\` in an edge prompt but the source schema has \`branchName\` instead
- **Overly complex roles** — a role with 20 steps should be split; each role should be completable in one agent turn
- **No fail path** — always handle failure; route to cleanup or \`$END\`
`;
}
@@ -0,0 +1,140 @@
export function generateDeveloperReference(): string {
return `# Developer Reference
Guide for contributing to the workflow engine codebase.
## Monorepo Structure
\`\`\`
packages/
workflow-protocol/ # Shared types (WorkflowPayload, StepNodePayload, etc.)
workflow-util/ # Base32, ULID, logger, frontmatter parsing, skill references
workflow-util-agent/ # createAgent factory, context builder, extract pipeline
workflow-agent-hermes/ # uwf-hermes CLI (spawns Hermes chat sessions)
workflow-agent-builtin/ # uwf-builtin CLI (direct LLM calls via OpenAI API)
cli-workflow/ # uwf CLI (moderator, thread/step/cas/config commands)
\`\`\`
Dependency layers (each only imports from packages above it):
\`\`\`
protocol → util → util-agent → agent-hermes / agent-builtin / cli-workflow
\`\`\`
External CAS: \`@uncaged/json-cas\` (store API, hashing, schema validation) + \`@uncaged/json-cas-fs\` (filesystem backend).
## Coding Conventions
### Functional-first
| Rule | Description |
|------|-------------|
| \`type\` over \`interface\` | All type definitions use \`type\` |
| \`function\` over \`class\` | Pure functions + closures, no class |
| No \`this\` | Functions must not depend on \`this\` context |
| No inheritance | No \`extends\`, \`implements\`, \`abstract\` |
| No optional properties | Use \`T \\| null\` instead of \`?:\` |
| Immutability first | Use \`Readonly<T>\`, \`as const\`, avoid mutation |
Classes allowed only when required by third-party libraries or for Error subclasses.
### Error Handling
- \`Result<T, E>\` type for expected failures (\`ok\`/\`err\` constructors from \`@uncaged/workflow-util\`)
- \`throw\` only for unrecoverable bugs
- No try-catch for flow control
### Async
Always \`async/await\`, never \`.then()\` chains.
### Logging
\`console.*\` is banned (Biome \`noConsole\` rule). Use the structured logger:
\`\`\`typescript
import { createLogger } from "@uncaged/workflow-util";
const log = createLogger();
log("4KNMR2PX", "Loading workflow..."); // 8-char Crockford Base32 tag
\`\`\`
Each call site gets a unique hand-written tag. \`grep "4KNMR2PX"\` in logs → instant code location.
CLI package (\`@uncaged/cli-workflow\`) may use \`console.log\` for user-facing output with a biome-ignore comment.
### No Dynamic Import
No \`await import()\` in production code. Always static top-level \`import\`. Test files are exempt.
### Naming
- Workflow names: verb-first kebab-case (\`solve-issue\`, \`review-code\`)
- IDs: Crockford Base32 — CAS hash (XXH64, 13-char), Thread ID (ULID, 26-char)
## Development Workflow
\`\`\`bash
bun install # install all workspace deps
bun run build # tsc --build (all packages)
bun run check # tsc + biome check + lint-log-tags
bun run format # biome format --write
bun test # run all tests
\`\`\`
Before committing: \`bun run check\` + \`bun test\` must both pass.
### Testing
- \`cli-workflow\`: vitest
- Other packages: \`bun test\`
- Test files live in \`__tests__/\` directories
### Publishing
Fixed-mode versioning — all \`@uncaged/*\` packages share the same version number.
\`\`\`bash
bun changeset # describe the change
bun version # bump versions + changelogs
bun release # build + test + publish to npmjs
\`\`\`
## Key Modules
### Moderator (\`cli-workflow/src/moderator/\`)
Status-based graph evaluator. Reads \`graph[lastRole][output.$status]\` to determine the next role. Zero LLM cost.
### Extract Pipeline (\`workflow-util-agent/src/\`)
1. Agent produces frontmatter markdown
2. \`parseFrontmatterMarkdown()\` extracts YAML frontmatter
3. \`tryFrontmatterFastPath()\` validates against role's output schema
4. If fast path fails, retries up to 2 times via agent continue
5. Validated output stored as CAS node
### createAgent Factory (\`workflow-util-agent/src/run.ts\`)
Shared entry point for all agent CLIs. Handles:
- Argument parsing (\`--thread\`, \`--role\`, \`--prompt\`)
- Context building (thread history, workflow definition)
- Output extraction and CAS persistence
- Frontmatter retry loop
### CAS Integration
All data is CAS-addressed via \`@uncaged/json-cas\`:
- \`store.put(schemaHash, data)\` → content hash
- \`store.get(hash)\` → node
- \`validate(store, node)\` → schema check
- Schemas registered at workflow add time
## Commit Convention
\`\`\`
<type>(<scope>): <description>
type: feat | fix | refactor | docs | chore | test
scope: workflow | cli | moderator | util-agent | hermes | util | protocol
\`\`\`
`;
}
+5
View File
@@ -1,6 +1,10 @@
export { generateActorReference } from "./actor-reference.js";
export { generateAdapterReference } from "./adapter-reference.js";
export { generateArchitectureReference } from "./architecture-reference.js"; export { generateArchitectureReference } from "./architecture-reference.js";
export { generateAuthorReference } from "./author-reference.js";
export { encodeUint64AsCrockford } from "./base32.js"; export { encodeUint64AsCrockford } from "./base32.js";
export { generateCliReference } from "./cli-reference.js"; export { generateCliReference } from "./cli-reference.js";
export { generateDeveloperReference } from "./developer-reference.js";
export { env } from "./env.js"; export { env } from "./env.js";
export type { export type {
AgentFrontmatter, AgentFrontmatter,
@@ -27,4 +31,5 @@ export { err, ok } from "./result.js";
export { getDefaultWorkflowStorageRoot, getGlobalCasDir } from "./storage-root.js"; export { getDefaultWorkflowStorageRoot, getGlobalCasDir } from "./storage-root.js";
export type { LogFn, Result } from "./types.js"; export type { LogFn, Result } from "./types.js";
export { extractUlidTimestamp, generateUlid } from "./ulid.js"; export { extractUlidTimestamp, generateUlid } from "./ulid.js";
export { generateUserReference } from "./user-reference.js";
export { generateYamlReference } from "./yaml-reference.js"; export { generateYamlReference } from "./yaml-reference.js";
@@ -0,0 +1,125 @@
export function generateUserReference(): string {
return `# User Reference
Guide for using the uwf CLI to manage workflows and threads.
## Quick Start
\`\`\`bash
# 1. Configure provider and model
uwf setup
# 2. Register a workflow
uwf workflow add my-workflow.yaml
# 3. Start a thread (creates but does not execute)
uwf thread start my-workflow -p "Build a login page"
# 4. Execute the thread (runs moderator → agent → extract cycles)
uwf thread exec <thread-id> # one step
uwf thread exec <thread-id> -c 10 # up to 10 steps
uwf thread exec <thread-id> -c 10 --background # run in background
\`\`\`
## Concepts
- **Workflow** — YAML definition with roles and a routing graph; stored as a CAS node
- **Thread** — A running instance of a workflow; a chain of step nodes in CAS
- **Step** — One moderator → agent → extract cycle; contains the role's structured output
- **CAS** — Content-addressable store; every artifact is hashed (XXH64, Crockford Base32)
## Setup
\`\`\`
uwf setup # interactive wizard
uwf setup --provider <name> --base-url <url> \\
--api-key <key> --model <name> # non-interactive
[--agent <name>] # optional default agent
\`\`\`
Config is stored at \`~/.uncaged/workflow/config.yaml\`. Override storage root with \`UNCAGED_WORKFLOW_STORAGE_ROOT\`.
## Workflow Commands
\`\`\`
uwf workflow add <file> # register from YAML file
uwf workflow show <id> # show by name or CAS hash
uwf workflow list # list all registered workflows
\`\`\`
You can also pass a file path directly to \`uwf thread start\` without registering first.
## Thread Lifecycle
\`\`\`
uwf thread start <workflow> -p <prompt> # create thread
uwf thread exec <thread-id> # execute one step
[--agent <cmd>] # override agent
[-c, --count <n>] # run n steps
[--background] # run in background
uwf thread show <thread-id> # show head pointer
uwf thread list # list all threads
[--status <filter>] # idle, running, completed, cancelled, active (comma-separated)
[--after <thread-id>] # pagination: after this thread
[--before <thread-id>] # pagination: before this thread
[--skip <n>] # skip first n results
[--take <n>] # limit results
uwf thread read <thread-id> # render context as markdown
[--quota <chars>] # max output chars (default 4000)
[--before <step-hash>] # pagination
[--start] # include start step
uwf thread stop <thread-id> # stop background execution
uwf thread cancel <thread-id> # cancel and archive thread
\`\`\`
### Typical Lifecycle
\`\`\`
start → exec (repeat) → thread reaches $END → auto-completed
→ or: cancel to abort
\`\`\`
## Step Commands
\`\`\`
uwf step list <thread-id> # list all steps
uwf step show <step-hash> # show step details
uwf step fork <step-hash> # fork thread from a step (branch)
\`\`\`
Forking creates a new thread that shares history up to the fork point — useful for retrying from a known-good state.
## CAS Commands
\`\`\`
uwf cas get <hash> # read a node (type + payload)
[--timestamp] # include timestamp
uwf cas put <type-hash> <data> # store typed JSON, print hash
uwf cas put-text <text> # store plain text, print hash
uwf cas has <hash> # check existence
uwf cas refs <hash> # list direct references
uwf cas walk <hash> # recursive traversal
uwf cas reindex # rebuild type index
uwf cas schema list # list schemas
uwf cas schema get <hash> # show schema definition
\`\`\`
## Log Commands
\`\`\`
uwf log list # list log files
uwf log show # show log entries
[--thread <id>] # filter by thread
[--process <pid>] # filter by process
[--date <YYYY-MM-DD>] # filter by date
uwf log clean --before <date> # delete old logs
\`\`\`
## Global Options
\`\`\`
uwf --format <json|yaml> # output format (default: json)
uwf -V, --version # print version
\`\`\`
`;
}
+1
View File
@@ -21,6 +21,7 @@ const publishOrder = [
"workflow-util-agent", "workflow-util-agent",
"workflow-agent-hermes", "workflow-agent-hermes",
"workflow-agent-builtin", "workflow-agent-builtin",
"workflow-agent-claude-code",
"cli-workflow", "cli-workflow",
]; ];