From 3f8d6d53575abc4b34018b12435bba8422c54d81 Mon Sep 17 00:00:00 2001 From: Lukas May Date: Sat, 31 Jan 2026 19:05:27 +0100 Subject: [PATCH] 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 --- src/agent/schema.ts | 239 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 237 insertions(+), 2 deletions(-) diff --git a/src/agent/schema.ts b/src/agent/schema.ts index 5e76b4f..0a29776 100644 --- a/src/agent/schema.ts +++ b/src/agent/schema.ts @@ -3,10 +3,19 @@ * * Defines structured output schema for Claude agents using discriminated unions. * 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'; +// ============================================================================= +// SHARED SCHEMAS +// ============================================================================= + /** * 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 */ -const questionItemSchema = z.object({ +export const questionItemSchema = z.object({ id: z.string(), question: z.string(), options: z.array(optionSchema).optional(), multiSelect: z.boolean().optional(), }); +export type QuestionItem = z.infer; + /** - * 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; + +/** + * 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; + +// ============================================================================= +// EXECUTE MODE SCHEMA (default) +// ============================================================================= + +/** + * Discriminated union for agent output in execute mode. * * Agent must return one of: * - 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; + +/** + * 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; + +/** + * 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'], + }, + ], +};