fix: workflow-authoring docs — type:object + const vs enum clarity (#123) #124
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
"@united-workforce/cli": patch
|
||||||
|
"@united-workforce/util": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: unify $status to const-only, drop enum support (#123)
|
||||||
|
|
||||||
|
Breaking: `$status` in frontmatter now requires `const` everywhere.
|
||||||
|
`enum` is no longer accepted and will be rejected by the validator.
|
||||||
|
|
||||||
|
- Validator: `hasStatusConst()` / `getConstStatuses()` replace enum-based checks
|
||||||
|
- Error message: "must define $status as const (or oneOf with const)"
|
||||||
|
- workflow-authoring docs: all examples use `const`, enum explicitly noted as unsupported
|
||||||
|
- bootstrap hello.yaml: `$status: { const: done }`
|
||||||
|
- All test fixtures migrated from enum to const/oneOf
|
||||||
@@ -28,9 +28,13 @@ roles:
|
|||||||
$status: "ready"
|
$status: "ready"
|
||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
type: object
|
||||||
|
oneOf:
|
||||||
|
- properties:
|
||||||
|
$status: { const: "ready" }
|
||||||
|
required: ["$status"]
|
||||||
|
- properties:
|
||||||
|
$status: { const: "not-ready" }
|
||||||
required: ["$status"]
|
required: ["$status"]
|
||||||
properties:
|
|
||||||
$status: { type: string, enum: ["ready", "not-ready"] }
|
|
||||||
roleB:
|
roleB:
|
||||||
description: Second role
|
description: Second role
|
||||||
goal: Do B
|
goal: Do B
|
||||||
@@ -42,7 +46,7 @@ roles:
|
|||||||
type: object
|
type: object
|
||||||
required: ["$status"]
|
required: ["$status"]
|
||||||
properties:
|
properties:
|
||||||
$status: { type: string, enum: ["done"] }
|
$status: { const: "done" }
|
||||||
graph:
|
graph:
|
||||||
$START:
|
$START:
|
||||||
new:
|
new:
|
||||||
@@ -82,9 +86,13 @@ roles:
|
|||||||
$status: "pass"
|
$status: "pass"
|
||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
type: object
|
||||||
|
oneOf:
|
||||||
|
- properties:
|
||||||
|
$status: { const: "pass" }
|
||||||
|
required: ["$status"]
|
||||||
|
- properties:
|
||||||
|
$status: { const: "fail" }
|
||||||
required: ["$status"]
|
required: ["$status"]
|
||||||
properties:
|
|
||||||
$status: { type: string, enum: ["pass", "fail"] }
|
|
||||||
roleB:
|
roleB:
|
||||||
description: Pass role
|
description: Pass role
|
||||||
goal: Do B
|
goal: Do B
|
||||||
@@ -96,7 +104,7 @@ roles:
|
|||||||
type: object
|
type: object
|
||||||
required: ["$status"]
|
required: ["$status"]
|
||||||
properties:
|
properties:
|
||||||
$status: { type: string, enum: ["done"] }
|
$status: { const: "done" }
|
||||||
roleC:
|
roleC:
|
||||||
description: Fail role
|
description: Fail role
|
||||||
goal: Do C
|
goal: Do C
|
||||||
@@ -108,7 +116,7 @@ roles:
|
|||||||
type: object
|
type: object
|
||||||
required: ["$status"]
|
required: ["$status"]
|
||||||
properties:
|
properties:
|
||||||
$status: { type: string, enum: ["done"] }
|
$status: { const: "done" }
|
||||||
graph:
|
graph:
|
||||||
$START:
|
$START:
|
||||||
new:
|
new:
|
||||||
@@ -155,7 +163,7 @@ roles:
|
|||||||
type: object
|
type: object
|
||||||
required: ["$status"]
|
required: ["$status"]
|
||||||
properties:
|
properties:
|
||||||
$status: { type: string, enum: ["done"] }
|
$status: { const: "done" }
|
||||||
graph:
|
graph:
|
||||||
$START:
|
$START:
|
||||||
new:
|
new:
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ roles:
|
|||||||
type: object
|
type: object
|
||||||
required: ["$status"]
|
required: ["$status"]
|
||||||
properties:
|
properties:
|
||||||
$status: { type: string, enum: ["ready"] }
|
$status: { const: "ready" }
|
||||||
graph:
|
graph:
|
||||||
$START:
|
$START:
|
||||||
new:
|
new:
|
||||||
@@ -114,7 +114,7 @@ roles:
|
|||||||
type: object
|
type: object
|
||||||
required: ["$status"]
|
required: ["$status"]
|
||||||
properties:
|
properties:
|
||||||
$status: { type: string, enum: ["ready"] }
|
$status: { const: "ready" }
|
||||||
graph:
|
graph:
|
||||||
$START:
|
$START:
|
||||||
new:
|
new:
|
||||||
@@ -161,7 +161,7 @@ roles:
|
|||||||
type: object
|
type: object
|
||||||
required: ["$status"]
|
required: ["$status"]
|
||||||
properties:
|
properties:
|
||||||
$status: { type: string, enum: ["ready"] }
|
$status: { const: "ready" }
|
||||||
graph:
|
graph:
|
||||||
$START:
|
$START:
|
||||||
new:
|
new:
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ roles:
|
|||||||
type: object
|
type: object
|
||||||
required: ["$status"]
|
required: ["$status"]
|
||||||
properties:
|
properties:
|
||||||
$status: { type: string, enum: ["ready"] }
|
$status: { const: "ready" }
|
||||||
graph:
|
graph:
|
||||||
$START:
|
$START:
|
||||||
new:
|
new:
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ roles:
|
|||||||
type: object
|
type: object
|
||||||
required: ["$status"]
|
required: ["$status"]
|
||||||
properties:
|
properties:
|
||||||
$status: { type: string, enum: ["ready"] }
|
$status: { const: "ready" }
|
||||||
graph:
|
graph:
|
||||||
$START:
|
$START:
|
||||||
new:
|
new:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function makeWorkflow(overrides?: Partial<WorkflowPayload>): WorkflowPayload {
|
|||||||
frontmatter: {
|
frontmatter: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
$status: { enum: ["done"] },
|
$status: { const: "done" },
|
||||||
plan: { type: "string" },
|
plan: { type: "string" },
|
||||||
},
|
},
|
||||||
required: ["$status", "plan"],
|
required: ["$status", "plan"],
|
||||||
@@ -85,7 +85,7 @@ describe("Suite 1: Role Reference Integrity", () => {
|
|||||||
output: "None",
|
output: "None",
|
||||||
frontmatter: {
|
frontmatter: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: { $status: { enum: ["done"] } },
|
properties: { $status: { const: "done" } },
|
||||||
required: ["$status"],
|
required: ["$status"],
|
||||||
} as unknown as string,
|
} as unknown as string,
|
||||||
};
|
};
|
||||||
@@ -187,7 +187,7 @@ describe("Suite 2: Graph Structure", () => {
|
|||||||
output: "Isolated",
|
output: "Isolated",
|
||||||
frontmatter: {
|
frontmatter: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: { $status: { enum: ["done"] } },
|
properties: { $status: { const: "done" } },
|
||||||
required: ["$status"],
|
required: ["$status"],
|
||||||
} as unknown as string,
|
} as unknown as string,
|
||||||
};
|
};
|
||||||
@@ -272,8 +272,8 @@ describe("Suite 3: Status-Edge Consistency", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Suite 3b: Enum-Based Multi-Exit", () => {
|
describe("Suite 3b: Enum-Based $status is Rejected", () => {
|
||||||
test("3b.1 enum multi-exit passes with matching graph keys", () => {
|
test("3b.1 enum multi-exit is rejected (must use oneOf + const)", () => {
|
||||||
const wf = makeWorkflow();
|
const wf = makeWorkflow();
|
||||||
wf.roles.reviewer = {
|
wf.roles.reviewer = {
|
||||||
...wf.roles.reviewer,
|
...wf.roles.reviewer,
|
||||||
@@ -291,52 +291,10 @@ describe("Suite 3b: Enum-Based Multi-Exit", () => {
|
|||||||
rejected: { role: "writer", prompt: "Fix: {{{comments}}}", location: null },
|
rejected: { role: "writer", prompt: "Fix: {{{comments}}}", location: null },
|
||||||
};
|
};
|
||||||
const errors = validateWorkflow(wf);
|
const errors = validateWorkflow(wf);
|
||||||
expect(errors).toEqual([]);
|
expect(errors.some((e) => e.includes("must define") && e.includes("const"))).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("3b.2 enum multi-exit with extra graph key", () => {
|
test("3b.2 enum single-exit is rejected (must use const)", () => {
|
||||||
const wf = makeWorkflow();
|
|
||||||
wf.roles.reviewer = {
|
|
||||||
...wf.roles.reviewer,
|
|
||||||
frontmatter: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
$status: { enum: ["approved", "rejected"] },
|
|
||||||
comments: { type: "string" },
|
|
||||||
},
|
|
||||||
required: ["$status", "comments"],
|
|
||||||
} as unknown as string,
|
|
||||||
};
|
|
||||||
wf.graph.reviewer = {
|
|
||||||
approved: { role: "$END", prompt: "Done", location: null },
|
|
||||||
rejected: { role: "writer", prompt: "Fix", location: null },
|
|
||||||
timeout: { role: "$END", prompt: "Timed out", location: null },
|
|
||||||
};
|
|
||||||
const errors = validateWorkflow(wf);
|
|
||||||
expect(errors.some((e) => e.includes("extra status keys: timeout"))).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("3b.3 enum multi-exit with missing graph key", () => {
|
|
||||||
const wf = makeWorkflow();
|
|
||||||
wf.roles.reviewer = {
|
|
||||||
...wf.roles.reviewer,
|
|
||||||
frontmatter: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
$status: { enum: ["approved", "rejected"] },
|
|
||||||
comments: { type: "string" },
|
|
||||||
},
|
|
||||||
required: ["$status", "comments"],
|
|
||||||
} as unknown as string,
|
|
||||||
};
|
|
||||||
wf.graph.reviewer = {
|
|
||||||
approved: { role: "$END", prompt: "Done", location: null },
|
|
||||||
};
|
|
||||||
const errors = validateWorkflow(wf);
|
|
||||||
expect(errors.some((e) => e.includes("missing status keys: rejected"))).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("3b.4 enum with single explicit value passes", () => {
|
|
||||||
const wf = makeWorkflow();
|
const wf = makeWorkflow();
|
||||||
wf.roles.writer = {
|
wf.roles.writer = {
|
||||||
...wf.roles.writer,
|
...wf.roles.writer,
|
||||||
@@ -351,28 +309,71 @@ describe("Suite 3b: Enum-Based Multi-Exit", () => {
|
|||||||
};
|
};
|
||||||
wf.graph.writer = { ready: { role: "reviewer", prompt: "Review: {{{plan}}}", location: null } };
|
wf.graph.writer = { ready: { role: "reviewer", prompt: "Review: {{{plan}}}", location: null } };
|
||||||
const errors = validateWorkflow(wf);
|
const errors = validateWorkflow(wf);
|
||||||
expect(errors).toEqual([]);
|
expect(errors.some((e) => e.includes("must define") && e.includes("const"))).toBe(true);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("3b.5 enum multi-exit mustache var not in frontmatter", () => {
|
describe("Suite 3c: Const-Based Flat Schema", () => {
|
||||||
|
test("3c.1 flat schema with const $status passes validation", () => {
|
||||||
const wf = makeWorkflow();
|
const wf = makeWorkflow();
|
||||||
wf.roles.reviewer = {
|
wf.roles.writer = {
|
||||||
...wf.roles.reviewer,
|
...wf.roles.writer,
|
||||||
frontmatter: {
|
frontmatter: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
$status: { enum: ["approved", "rejected"] },
|
$status: { const: "done" },
|
||||||
comments: { type: "string" },
|
plan: { type: "string" },
|
||||||
},
|
},
|
||||||
required: ["$status", "comments"],
|
required: ["$status", "plan"],
|
||||||
} as unknown as string,
|
} as unknown as string,
|
||||||
};
|
};
|
||||||
wf.graph.reviewer = {
|
const errors = validateWorkflow(wf);
|
||||||
approved: { role: "$END", prompt: "Done: {{{nonexistent}}}", location: null },
|
expect(errors).toEqual([]);
|
||||||
rejected: { role: "writer", prompt: "Fix: {{{comments}}}", location: null },
|
});
|
||||||
|
|
||||||
|
test("3c.2 flat schema with const $status detects extra graph key", () => {
|
||||||
|
const wf = makeWorkflow();
|
||||||
|
wf.roles.writer = {
|
||||||
|
...wf.roles.writer,
|
||||||
|
frontmatter: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
$status: { const: "done" },
|
||||||
|
plan: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["$status", "plan"],
|
||||||
|
} as unknown as string,
|
||||||
|
};
|
||||||
|
wf.graph.writer = {
|
||||||
|
done: { role: "reviewer", prompt: "Review.", location: null },
|
||||||
|
extra: { role: "$END", prompt: "Nope.", location: null },
|
||||||
};
|
};
|
||||||
const errors = validateWorkflow(wf);
|
const errors = validateWorkflow(wf);
|
||||||
expect(errors.some((e) => e.includes("nonexistent") && e.includes("not found"))).toBe(true);
|
expect(errors.some((e) => e.includes("extra status keys") && e.includes("extra"))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("3c.3 flat schema with const $status validates mustache vars", () => {
|
||||||
|
const wf = makeWorkflow();
|
||||||
|
wf.roles.writer = {
|
||||||
|
...wf.roles.writer,
|
||||||
|
frontmatter: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
$status: { const: "done" },
|
||||||
|
plan: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["$status", "plan"],
|
||||||
|
} as unknown as string,
|
||||||
|
};
|
||||||
|
wf.graph.writer = {
|
||||||
|
done: { role: "reviewer", prompt: "Review: {{{nonexistent}}}", location: null },
|
||||||
|
};
|
||||||
|
const errors = validateWorkflow(wf);
|
||||||
|
expect(
|
||||||
|
errors.some(
|
||||||
|
(e) => e.includes('prompt variable "nonexistent"') && e.includes('role "writer"'),
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -480,7 +481,7 @@ describe("Suite 6: Multiple Errors Collection", () => {
|
|||||||
output: "None",
|
output: "None",
|
||||||
frontmatter: {
|
frontmatter: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: { $status: { enum: ["done"] } },
|
properties: { $status: { const: "done" } },
|
||||||
required: ["$status"],
|
required: ["$status"],
|
||||||
} as unknown as string,
|
} as unknown as string,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ function makeMinimalPayload(name: string, description: string): WorkflowPayload
|
|||||||
frontmatter: {
|
frontmatter: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
$status: { type: "string", enum: ["done"] },
|
$status: { const: "done" },
|
||||||
},
|
},
|
||||||
required: ["$status"],
|
required: ["$status"],
|
||||||
} as unknown as CasRef,
|
} as unknown as CasRef,
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ roles:
|
|||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
$status: { enum: [done] }
|
$status: { const: done }
|
||||||
message: { type: string }
|
message: { type: string }
|
||||||
required: [$status, message]
|
required: [$status, message]
|
||||||
graph:
|
graph:
|
||||||
|
|||||||
@@ -24,22 +24,22 @@ function isOneOfSchema(fm: unknown): fm is SchemaObj & { oneOf: SchemaObj[] } {
|
|||||||
return Array.isArray(obj.oneOf);
|
return Array.isArray(obj.oneOf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check if a frontmatter schema declares "$status" as an enum (the required form for user roles). */
|
/** Check if a frontmatter schema declares "$status" as const (flat schema form). */
|
||||||
function hasStatusEnum(fm: unknown): boolean {
|
function hasStatusConst(fm: unknown): boolean {
|
||||||
if (typeof fm !== "object" || fm === null) return false;
|
if (typeof fm !== "object" || fm === null) return false;
|
||||||
const obj = fm as SchemaObj;
|
const obj = fm as SchemaObj;
|
||||||
const props = obj.properties as Record<string, SchemaObj> | undefined;
|
const props = obj.properties as Record<string, SchemaObj> | undefined;
|
||||||
if (!props?.$status) return false;
|
if (!props?.$status) return false;
|
||||||
return Array.isArray(props.$status.enum);
|
return typeof props.$status.const === "string";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Extract status values from an enum-based $status field. */
|
/** Extract status values from a const-based $status field. */
|
||||||
function getEnumStatuses(fm: SchemaObj): string[] {
|
function getConstStatuses(fm: SchemaObj): string[] {
|
||||||
const props = fm.properties as Record<string, SchemaObj> | undefined;
|
const props = fm.properties as Record<string, SchemaObj> | undefined;
|
||||||
if (!props?.$status) return [];
|
if (!props?.$status) return [];
|
||||||
const statusDef = props.$status;
|
const statusDef = props.$status;
|
||||||
if (!Array.isArray(statusDef.enum)) return [];
|
if (typeof statusDef.const === "string") return [statusDef.const];
|
||||||
return statusDef.enum as string[];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get property names from a schema object. */
|
/** Get property names from a schema object. */
|
||||||
@@ -248,21 +248,21 @@ function checkRoleConsistency(payload: WorkflowPayload, errors: string[]): void
|
|||||||
checkOneOfDiscriminant(roleName, variants, statuses, errors);
|
checkOneOfDiscriminant(roleName, variants, statuses, errors);
|
||||||
checkStatusEdges(roleName, graphKeys, new Set(statuses), errors);
|
checkStatusEdges(roleName, graphKeys, new Set(statuses), errors);
|
||||||
checkMultiExitMustache(roleName, graphEntry, variants, errors);
|
checkMultiExitMustache(roleName, graphEntry, variants, errors);
|
||||||
} else if (hasStatusEnum(fm)) {
|
} else if (hasStatusConst(fm)) {
|
||||||
const statuses = getEnumStatuses(fm as SchemaObj);
|
const statuses = getConstStatuses(fm as SchemaObj);
|
||||||
checkStatusEdges(roleName, graphKeys, new Set(statuses), errors);
|
checkStatusEdges(roleName, graphKeys, new Set(statuses), errors);
|
||||||
// For enum-based schemas, mustache vars come from the flat properties
|
// For const-based flat schemas, mustache vars come from the flat properties
|
||||||
checkEnumMustache(roleName, graphEntry, fm as SchemaObj, errors);
|
checkFlatMustache(roleName, graphEntry, fm as SchemaObj, errors);
|
||||||
} else {
|
} else {
|
||||||
errors.push(
|
errors.push(
|
||||||
`role "${roleName}" must define "$status" as an enum (or oneOf const) in frontmatter`,
|
`role "${roleName}" must define "$status" as const (or oneOf with const) in frontmatter`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check mustache vars in all edge prompts against flat schema properties. */
|
/** Check mustache vars in all edge prompts against flat schema properties. */
|
||||||
function checkEnumMustache(
|
function checkFlatMustache(
|
||||||
roleName: string,
|
roleName: string,
|
||||||
graphEntry: Record<string, { role: string; prompt: string }>,
|
graphEntry: Record<string, { role: string; prompt: string }>,
|
||||||
fm: SchemaObj,
|
fm: SchemaObj,
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ describe("buildOutputFormatInstruction", () => {
|
|||||||
{
|
{
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
$status: { type: "string", enum: ["approved"] },
|
$status: { const: "approved" },
|
||||||
branch: { type: "string" },
|
branch: { type: "string" },
|
||||||
},
|
},
|
||||||
required: ["$status"],
|
required: ["$status"],
|
||||||
@@ -151,7 +151,7 @@ describe("buildOutputFormatInstruction", () => {
|
|||||||
{
|
{
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
$status: { type: "string", enum: ["rejected"] },
|
$status: { const: "rejected" },
|
||||||
comments: { type: "string" },
|
comments: { type: "string" },
|
||||||
},
|
},
|
||||||
required: ["$status"],
|
required: ["$status"],
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ roles: # named actors
|
|||||||
2. Do that
|
2. Do that
|
||||||
output: "..." # what the agent should produce
|
output: "..." # what the agent should produce
|
||||||
frontmatter: # JSON Schema for structured output
|
frontmatter: # JSON Schema for structured output
|
||||||
|
type: object
|
||||||
oneOf:
|
oneOf:
|
||||||
- properties:
|
- properties:
|
||||||
$status: { const: "ready" }
|
$status: { const: "ready" }
|
||||||
@@ -71,10 +72,13 @@ The \`frontmatter\` field is a standard JSON Schema. It defines the structured f
|
|||||||
|
|
||||||
### \`$status\` Field
|
### \`$status\` Field
|
||||||
|
|
||||||
\`$status\` is the only standard field. Its value determines which graph edge the moderator follows. Use \`const\` to constrain each variant:
|
\`$status\` is the only standard field. Its value determines which graph edge the moderator follows.
|
||||||
|
|
||||||
|
**Multi-exit (oneOf)** — use \`const\` to constrain each variant:
|
||||||
|
|
||||||
\`\`\`yaml
|
\`\`\`yaml
|
||||||
frontmatter:
|
frontmatter:
|
||||||
|
type: object
|
||||||
oneOf:
|
oneOf:
|
||||||
- properties:
|
- properties:
|
||||||
$status: { const: "done" }
|
$status: { const: "done" }
|
||||||
@@ -86,26 +90,25 @@ frontmatter:
|
|||||||
required: [$status, error]
|
required: [$status, error]
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
### Custom Fields
|
**Single-exit (flat schema)** — same syntax, just no \`oneOf\` wrapper:
|
||||||
|
|
||||||
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, use \`enum\` with a single value:
|
|
||||||
|
|
||||||
\`\`\`yaml
|
\`\`\`yaml
|
||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
$status:
|
$status: { const: "done" }
|
||||||
type: string
|
|
||||||
enum: [done]
|
|
||||||
summary: { type: string }
|
summary: { type: string }
|
||||||
required: [$status, summary]
|
required: [$status, summary]
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
Note: \`$status: { const: "done" }\` is **not** valid in flat schemas — the validator requires \`enum\` or \`oneOf\` with \`const\`. Use \`const\` only inside \`oneOf\` variants.
|
**Important rules:**
|
||||||
|
- \`type: object\` is **required** at the top level of frontmatter (both flat and oneOf)
|
||||||
|
- \`$status\` always uses \`const: "value"\` — simple and consistent
|
||||||
|
- \`enum\` is **not supported** for \`$status\` — the validator will reject it
|
||||||
|
|
||||||
|
### Custom Fields
|
||||||
|
|
||||||
|
Add any fields you need for data passing between roles. These are available in edge prompts via Mustache templates.
|
||||||
|
|
||||||
## Graph Routing
|
## Graph Routing
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user