feat(councils): add deterministic councils engine and council.run tool

This commit is contained in:
William Valentin
2026-02-21 10:49:14 -08:00
parent c6731c8e20
commit bcb7e7b658
10 changed files with 1590 additions and 2 deletions
+249
View File
@@ -0,0 +1,249 @@
import { z } from 'zod';
export const COUNCIL_SCHEMA_VERSION = '1.0.0';
export const COUNCIL_PIPELINE_VERSION = '1.0.0';
export const councilGroupSchema = z.enum(['D', 'P']);
export type CouncilGroup = z.infer<typeof councilGroupSchema>;
export const stopReasonSchema = z.enum([
'max_rounds',
'convergence',
'bridge_validation_failed',
'grounding_failed',
'meta_validation_failed',
]);
export const validationFailureReasonSchema = z.enum([
'schema_invalid',
'unknown_id',
'cap_exceeded',
'repair_failed',
'parse_failed',
]);
export const droppedReasonSchema = z.enum([
'cap_top_ideas',
'cap_field_bullets',
'cap_entry_chars',
'cap_total_chars',
'invalid_reference',
'grounding_failed',
]);
export const rejectionReasonCodeSchema = z.enum([
'low_score',
'high_risk',
'insufficient_grounding',
'duplicate',
'out_of_scope',
'unknown_id',
'other',
]);
const schemaVersionField = z.literal(COUNCIL_SCHEMA_VERSION);
export const ideaContentSchema = z.object({
title: z.string().min(1),
hypothesis: z.string().min(1),
mechanism: z.string().min(1),
expected_outcome: z.string().min(1),
}).strict();
export const ideaGroundingSchema = z.object({
mve: z.string().min(1),
constraints: z.array(z.string().min(1)).min(1),
falsifiability_checks: z.array(z.string().min(1)).min(1),
}).strict();
export const ideaCardSchema = z.object({
schema_version: schemaVersionField,
idea_id: z.string().min(1),
group: councilGroupSchema,
round: z.number().int().min(1),
content: ideaContentSchema,
grounding: ideaGroundingSchema.optional(),
grounding_failed: z.boolean().optional(),
}).strict();
export const scoreSetSchema = z.object({
novelty: z.number().int().min(0).max(100),
feasibility: z.number().int().min(0).max(100),
impact: z.number().int().min(0).max(100),
testability: z.number().int().min(0).max(100),
}).strict();
export const ideaAssessmentSchema = z.object({
schema_version: schemaVersionField,
idea_id: z.string().min(1),
scores: scoreSetSchema,
decision: z.enum(['shortlist', 'hold', 'reject']),
notes: z.string().min(1),
}).strict();
export const bridgeIdeaSchema = z.object({
idea_id: z.string().min(1),
mechanism: z.string().min(1),
rationale: z.string().min(1),
}).strict();
export const bridgePacketSchema = z.object({
schema_version: schemaVersionField,
from_group: councilGroupSchema,
to_group: councilGroupSchema,
round: z.number().int().min(1),
top_ideas: z.array(bridgeIdeaSchema),
assumptions: z.array(z.string().min(1)),
risks: z.array(z.string().min(1)),
asks: z.array(z.string().min(1)),
what_to_steal: z.array(z.string().min(1)),
}).strict();
export const councilBriefSchema = z.object({
schema_version: schemaVersionField,
group: councilGroupSchema,
round: z.number().int().min(1),
ideas: z.array(ideaCardSchema),
assessments: z.array(ideaAssessmentSchema),
shortlist: z.array(z.string().min(1)),
assumptions: z.array(z.string().min(1)),
risks: z.array(z.string().min(1)),
asks: z.array(z.string().min(1)),
what_to_steal: z.array(z.string().min(1)),
convergence_signal: z.boolean(),
novelty_score: z.number().int().min(0).max(100),
repetition_rate: z.number().int().min(0).max(100),
}).strict();
export const councilDiffSchema = z.object({
schema_version: schemaVersionField,
group: councilGroupSchema,
from_round: z.number().int().min(1),
to_round: z.number().int().min(1),
idea_added: z.array(z.string().min(1)),
idea_removed: z.array(z.string().min(1)),
shortlist_added: z.array(z.string().min(1)),
shortlist_removed: z.array(z.string().min(1)),
score_changes: z.array(z.object({
idea_id: z.string().min(1),
from_total: z.number().int(),
to_total: z.number().int(),
}).strict()),
assumptions_added: z.array(z.string().min(1)),
assumptions_removed: z.array(z.string().min(1)),
mve_changed: z.array(z.string().min(1)),
}).strict();
export const mergeRecordSchema = z.object({
sources: z.array(z.string().min(1)).min(2),
result_title: z.string().min(1),
rationale: z.string().min(1),
}).strict();
export const metaSelectionSchema = z.object({
schema_version: schemaVersionField,
selected_primary: z.array(z.string().min(1)),
selected_secondary: z.array(z.string().min(1)),
merges: z.array(mergeRecordSchema).optional(),
rejections: z.array(z.object({
idea_id: z.string().min(1),
reason_code: rejectionReasonCodeSchema,
}).strict()),
open_questions: z.array(z.string().min(1)),
next_experiments: z.array(z.string().min(1)),
}).strict();
export const councilTraceEventSchema = z.object({
schema_version: schemaVersionField,
event_id: z.string().min(1),
phase_index: z.number().int().min(1),
call_id: z.string().min(1),
group: councilGroupSchema.optional(),
round: z.number().int().min(1).optional(),
prompt_payload_hash: z.string().length(64),
artifact_hash: z.string().length(64).optional(),
token_usage: z.object({
inputTokens: z.number().int().min(0),
outputTokens: z.number().int().min(0),
}).strict().optional(),
dropped_reason: droppedReasonSchema.optional(),
validation_failure: validationFailureReasonSchema.optional(),
}).strict();
export const stopSnapshotSchema = z.object({
stop_reason: stopReasonSchema,
round_reached: z.number().int().min(1),
final_shortlist_D: z.array(z.string().min(1)),
final_shortlist_P: z.array(z.string().min(1)),
bridge_validated: z.boolean(),
grounding_failures_count: z.number().int().min(0),
}).strict();
export const councilRunResultSchema = z.object({
pipeline_version: z.literal(COUNCIL_PIPELINE_VERSION),
input_hash: z.string().length(64),
brief_D_v1: councilBriefSchema,
brief_P_v1: councilBriefSchema,
brief_D_v2: councilBriefSchema,
brief_P_v2: councilBriefSchema,
diff_D: councilDiffSchema,
diff_P: councilDiffSchema,
bridge_D_to_P: bridgePacketSchema,
bridge_P_to_D: bridgePacketSchema,
meta: metaSelectionSchema,
stop_snapshot: stopSnapshotSchema,
trace: z.array(councilTraceEventSchema),
}).strict();
export const councilRunInputSchema = z.object({
task: z.string().min(1),
constraints: z.union([z.string().min(1), z.record(z.string(), z.unknown())]).optional(),
success_definition: z.string().min(1).optional(),
budget: z.record(z.string(), z.unknown()).optional(),
timebox: z.union([z.string().min(1), z.number().positive()]).optional(),
output_format: z.string().min(1).optional(),
max_rounds: z.number().int().min(1).max(6).optional(),
session_id: z.string().min(1).optional(),
}).strict();
export const ideationOutputSchema = z.object({
ideas: z.array(ideaContentSchema).min(1),
}).strict();
export const assessmentOutputSchema = z.object({
assessments: z.array(z.object({
idea_id: z.string().min(1),
scores: scoreSetSchema,
decision: z.enum(['shortlist', 'hold', 'reject']),
notes: z.string().min(1),
}).strict()),
assumptions: z.array(z.string().min(1)),
risks: z.array(z.string().min(1)),
asks: z.array(z.string().min(1)),
what_to_steal: z.array(z.string().min(1)),
convergence_signal: z.boolean(),
novelty_score: z.number().int().min(0).max(100),
repetition_rate: z.number().int().min(0).max(100),
}).strict();
export const groundingOutputSchema = z.object({
grounded: z.array(z.object({
idea_id: z.string().min(1),
mve: z.string().min(1),
constraints: z.array(z.string().min(1)).min(1),
falsifiability_checks: z.array(z.string().min(1)).min(1),
}).strict()),
}).strict();
export type StopReason = z.infer<typeof stopReasonSchema>;
export type ValidationFailureReason = z.infer<typeof validationFailureReasonSchema>;
export type DroppedReason = z.infer<typeof droppedReasonSchema>;
export type IdeaCard = z.infer<typeof ideaCardSchema>;
export type IdeaAssessment = z.infer<typeof ideaAssessmentSchema>;
export type BridgePacket = z.infer<typeof bridgePacketSchema>;
export type CouncilBrief = z.infer<typeof councilBriefSchema>;
export type CouncilDiff = z.infer<typeof councilDiffSchema>;
export type CouncilRunInput = z.infer<typeof councilRunInputSchema>;
export type CouncilRunResult = z.infer<typeof councilRunResultSchema>;
export type CouncilTraceEvent = z.infer<typeof councilTraceEventSchema>;
export type MetaSelection = z.infer<typeof metaSelectionSchema>;