feat(11-01): create mode-specific output schemas
- Export questionItemSchema and add QuestionItem type - Add Decision type for discuss mode (topic/decision/reason) - Add PhaseBreakdown type for breakdown mode - Create discussOutputSchema (questions/context_complete/error) - Create breakdownOutputSchema (questions/breakdown_complete/error) - Add discussOutputJsonSchema for Claude CLI --json-schema - Add breakdownOutputJsonSchema for Claude CLI --json-schema
This commit is contained in:
@@ -3,10 +3,19 @@
|
|||||||
*
|
*
|
||||||
* Defines structured output schema for Claude agents using discriminated unions.
|
* Defines structured output schema for Claude agents using discriminated unions.
|
||||||
* Replaces broken AskUserQuestion detection with explicit agent status signaling.
|
* Replaces broken AskUserQuestion detection with explicit agent status signaling.
|
||||||
|
*
|
||||||
|
* Mode-specific schemas:
|
||||||
|
* - execute: Standard task execution (done/questions/error)
|
||||||
|
* - discuss: Gather context through questions, output decisions
|
||||||
|
* - breakdown: Decompose initiative into phases
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SHARED SCHEMAS
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Option for questions - allows agent to present choices to user
|
* Option for questions - allows agent to present choices to user
|
||||||
*/
|
*/
|
||||||
@@ -18,15 +27,46 @@ const optionSchema = z.object({
|
|||||||
/**
|
/**
|
||||||
* Individual question item with unique ID for answer matching
|
* Individual question item with unique ID for answer matching
|
||||||
*/
|
*/
|
||||||
const questionItemSchema = z.object({
|
export const questionItemSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
question: z.string(),
|
question: z.string(),
|
||||||
options: z.array(optionSchema).optional(),
|
options: z.array(optionSchema).optional(),
|
||||||
multiSelect: z.boolean().optional(),
|
multiSelect: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type QuestionItem = z.infer<typeof questionItemSchema>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Discriminated union for agent output.
|
* A decision captured during discussion.
|
||||||
|
* Prompt instructs: { "topic": "Auth", "decision": "JWT", "reason": "Stateless" }
|
||||||
|
*/
|
||||||
|
const decisionSchema = z.object({
|
||||||
|
topic: z.string(),
|
||||||
|
decision: z.string(),
|
||||||
|
reason: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Decision = z.infer<typeof decisionSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A phase from breakdown output.
|
||||||
|
* Prompt instructs: { "number": 1, "name": "...", "description": "...", "dependencies": [0] }
|
||||||
|
*/
|
||||||
|
const phaseBreakdownSchema = z.object({
|
||||||
|
number: z.number().int().positive(),
|
||||||
|
name: z.string().min(1),
|
||||||
|
description: z.string(),
|
||||||
|
dependencies: z.array(z.number().int()).optional().default([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PhaseBreakdown = z.infer<typeof phaseBreakdownSchema>;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// EXECUTE MODE SCHEMA (default)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discriminated union for agent output in execute mode.
|
||||||
*
|
*
|
||||||
* Agent must return one of:
|
* Agent must return one of:
|
||||||
* - done: Task completed successfully
|
* - done: Task completed successfully
|
||||||
@@ -111,3 +151,198 @@ export const agentOutputJsonSchema = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// DISCUSS MODE SCHEMA
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discuss mode output schema.
|
||||||
|
* Agent asks questions OR completes with decisions.
|
||||||
|
*
|
||||||
|
* Prompt tells agent:
|
||||||
|
* - Output "questions" status with questions array when needing input
|
||||||
|
* - Output "context_complete" status with decisions array when done
|
||||||
|
*/
|
||||||
|
export const discussOutputSchema = z.discriminatedUnion('status', [
|
||||||
|
// Agent needs more information
|
||||||
|
z.object({
|
||||||
|
status: z.literal('questions'),
|
||||||
|
questions: z.array(questionItemSchema),
|
||||||
|
}),
|
||||||
|
// Agent has captured all decisions
|
||||||
|
z.object({
|
||||||
|
status: z.literal('context_complete'),
|
||||||
|
decisions: z.array(decisionSchema),
|
||||||
|
summary: z.string(), // Brief summary of all decisions
|
||||||
|
}),
|
||||||
|
// Unrecoverable error
|
||||||
|
z.object({
|
||||||
|
status: z.literal('unrecoverable_error'),
|
||||||
|
error: z.string(),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type DiscussOutput = z.infer<typeof discussOutputSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Schema for discuss mode (passed to Claude CLI --json-schema)
|
||||||
|
*/
|
||||||
|
export const discussOutputJsonSchema = {
|
||||||
|
type: 'object',
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
status: { const: 'questions' },
|
||||||
|
questions: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string' },
|
||||||
|
question: { type: 'string' },
|
||||||
|
options: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
label: { type: 'string' },
|
||||||
|
description: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['label'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
multiSelect: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
required: ['id', 'question'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['status', 'questions'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
status: { const: 'context_complete' },
|
||||||
|
decisions: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
topic: { type: 'string' },
|
||||||
|
decision: { type: 'string' },
|
||||||
|
reason: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['topic', 'decision', 'reason'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
summary: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['status', 'decisions', 'summary'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
status: { const: 'unrecoverable_error' },
|
||||||
|
error: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['status', 'error'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// BREAKDOWN MODE SCHEMA
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Breakdown mode output schema.
|
||||||
|
* Agent asks questions OR completes with phases.
|
||||||
|
*
|
||||||
|
* Prompt tells agent:
|
||||||
|
* - Output "questions" status when needing clarification
|
||||||
|
* - Output "breakdown_complete" status with phases array when done
|
||||||
|
*/
|
||||||
|
export const breakdownOutputSchema = z.discriminatedUnion('status', [
|
||||||
|
// Agent needs clarification
|
||||||
|
z.object({
|
||||||
|
status: z.literal('questions'),
|
||||||
|
questions: z.array(questionItemSchema),
|
||||||
|
}),
|
||||||
|
// Agent has decomposed initiative into phases
|
||||||
|
z.object({
|
||||||
|
status: z.literal('breakdown_complete'),
|
||||||
|
phases: z.array(phaseBreakdownSchema),
|
||||||
|
}),
|
||||||
|
// Unrecoverable error
|
||||||
|
z.object({
|
||||||
|
status: z.literal('unrecoverable_error'),
|
||||||
|
error: z.string(),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type BreakdownOutput = z.infer<typeof breakdownOutputSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Schema for breakdown mode (passed to Claude CLI --json-schema)
|
||||||
|
*/
|
||||||
|
export const breakdownOutputJsonSchema = {
|
||||||
|
type: 'object',
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
status: { const: 'questions' },
|
||||||
|
questions: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string' },
|
||||||
|
question: { type: 'string' },
|
||||||
|
options: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
label: { type: 'string' },
|
||||||
|
description: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['label'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['id', 'question'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['status', 'questions'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
status: { const: 'breakdown_complete' },
|
||||||
|
phases: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
number: { type: 'integer', minimum: 1 },
|
||||||
|
name: { type: 'string', minLength: 1 },
|
||||||
|
description: { type: 'string' },
|
||||||
|
dependencies: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'integer' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['number', 'name', 'description'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['status', 'phases'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
status: { const: 'unrecoverable_error' },
|
||||||
|
error: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['status', 'error'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user