feat(councils): add scaffold prompt hooks and checked-in scaffold files
This commit is contained in:
@@ -863,6 +863,8 @@ const agentConfigsSchema = z.record(z.string(), agentConfigEntrySchema).default(
|
||||
const councilsGroupConfigSchema = z.object({
|
||||
arbiter_agent: z.string().min(1),
|
||||
freethinker_agent: z.string().min(1),
|
||||
grounder_agent: z.string().min(1).optional(),
|
||||
writer_agent: z.string().min(1).optional(),
|
||||
group_prompt_prefix: z.string().min(1),
|
||||
novelty_bias: z.enum(['low', 'medium', 'high']).default('medium'),
|
||||
risk_tolerance: z.enum(['low', 'medium', 'high']).default('medium'),
|
||||
@@ -883,6 +885,7 @@ const councilsSchema = z.object({
|
||||
}).default({}),
|
||||
strict_grounding: z.boolean().default(false),
|
||||
strict_meta_validation: z.boolean().default(true),
|
||||
scaffold_path: z.string().optional(),
|
||||
groups: z.object({
|
||||
D: councilsGroupConfigSchema.default({
|
||||
arbiter_agent: 'council_d_arbiter',
|
||||
@@ -910,6 +913,7 @@ const councilsSchema = z.object({
|
||||
}),
|
||||
}).default({}),
|
||||
meta_arbiter_agent: z.string().min(1).default('council_meta_arbiter'),
|
||||
meta_writer_agent: z.string().min(1).optional(),
|
||||
}).default({});
|
||||
|
||||
const routingSchema = z.object({
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { AgentConfigRegistry } from '../agents/registry.js';
|
||||
import type { AgentOrchestrator } from '../backends/native/orchestrator.js';
|
||||
import type { ModelTier } from '../models/router.js';
|
||||
import type { TokenUsage } from '../models/types.js';
|
||||
import type { CouncilScaffold } from './scaffold.js';
|
||||
import {
|
||||
COUNCIL_PIPELINE_VERSION,
|
||||
COUNCIL_SCHEMA_VERSION,
|
||||
@@ -60,11 +61,15 @@ export interface CouncilsConfig {
|
||||
P: CouncilGroupConfig;
|
||||
};
|
||||
meta_arbiter_agent: string;
|
||||
meta_writer_agent?: string;
|
||||
scaffold_path?: string;
|
||||
}
|
||||
|
||||
interface CouncilGroupConfig {
|
||||
arbiter_agent: string;
|
||||
freethinker_agent: string;
|
||||
grounder_agent?: string;
|
||||
writer_agent?: string;
|
||||
group_prompt_prefix: string;
|
||||
novelty_bias: 'low' | 'medium' | 'high';
|
||||
risk_tolerance: 'low' | 'medium' | 'high';
|
||||
@@ -223,16 +228,19 @@ export class CouncilsOrchestrator {
|
||||
private readonly _registry: AgentConfigRegistry;
|
||||
private readonly _delegateRunner: DelegateRunner;
|
||||
private readonly _config: CouncilsConfig;
|
||||
private readonly _scaffold?: CouncilScaffold;
|
||||
private readonly _trace: CouncilTraceEvent[] = [];
|
||||
|
||||
constructor(deps: {
|
||||
registry: AgentConfigRegistry;
|
||||
orchestrator: DelegateRunner;
|
||||
config: CouncilsConfig;
|
||||
scaffold?: CouncilScaffold;
|
||||
}) {
|
||||
this._registry = deps.registry;
|
||||
this._delegateRunner = deps.orchestrator;
|
||||
this._config = deps.config;
|
||||
this._scaffold = deps.scaffold;
|
||||
}
|
||||
|
||||
async run(rawInput: unknown): Promise<CouncilRunResult> {
|
||||
@@ -386,12 +394,14 @@ export class CouncilsOrchestrator {
|
||||
round?: number;
|
||||
promptPayload: unknown;
|
||||
modeDirective: string;
|
||||
scaffoldPrompt?: string;
|
||||
maxTokens?: number;
|
||||
}): Promise<AgentCallResult> {
|
||||
const agent = this.getAgent(opts.agentName);
|
||||
const message = canonicalStringify(opts.promptPayload);
|
||||
const promptHash = hashCanonical(opts.promptPayload);
|
||||
const systemPrompt = `${agent.systemPrompt}\n\n${opts.modeDirective}`;
|
||||
const scaffoldPrompt = opts.scaffoldPrompt ? `${opts.scaffoldPrompt}\n\n` : '';
|
||||
const systemPrompt = `${scaffoldPrompt}${agent.systemPrompt}\n\n${opts.modeDirective}`;
|
||||
|
||||
const result = await this._delegateRunner.delegate({
|
||||
tier: agent.tier,
|
||||
@@ -451,6 +461,7 @@ export class CouncilsOrchestrator {
|
||||
round,
|
||||
promptPayload: ideationPayload,
|
||||
modeDirective: 'Return JSON only: {"ideas":[IdeaContent,...]}. Do not include IDs. No prose.',
|
||||
scaffoldPrompt: this._scaffold?.prompts[group].free_thinker,
|
||||
});
|
||||
|
||||
const ideaOutput = parseJsonWithRepair(ideation.content, (value) => ideationOutputSchema.parse(value));
|
||||
@@ -482,6 +493,7 @@ export class CouncilsOrchestrator {
|
||||
promptPayload: assessmentPayload,
|
||||
modeDirective:
|
||||
'Return JSON only. Assess provided idea IDs only. No new IDs. Include convergence_signal/novelty_score/repetition_rate.',
|
||||
scaffoldPrompt: this._scaffold?.prompts[group].arbiter,
|
||||
});
|
||||
|
||||
const assessmentOutput = parseJsonWithRepair(assessmentRaw.content, (value) => assessmentOutputSchema.parse(value));
|
||||
@@ -524,11 +536,12 @@ export class CouncilsOrchestrator {
|
||||
constraints: input.constraints,
|
||||
};
|
||||
|
||||
const groundingAgent = groupConfig.grounder_agent ?? groupConfig.freethinker_agent;
|
||||
let groundingFailures = 0;
|
||||
let grounding = { grounded: [] as Array<{ idea_id: string; mve: string; constraints: string[]; falsifiability_checks: string[] }> };
|
||||
try {
|
||||
const groundingRaw = await this.callAgent({
|
||||
agentName: groupConfig.freethinker_agent,
|
||||
agentName: groundingAgent,
|
||||
callId: `${group}.r${round}.ft.ground`,
|
||||
phaseIndex: phaseBase + 2,
|
||||
group,
|
||||
@@ -536,6 +549,7 @@ export class CouncilsOrchestrator {
|
||||
promptPayload: groundingPayload,
|
||||
modeDirective:
|
||||
'Grounder mode. Return JSON only: {"grounded":[{"idea_id", "mve", "constraints", "falsifiability_checks"}]}. No prose.',
|
||||
scaffoldPrompt: this._scaffold?.prompts[group].grounder ?? this._scaffold?.prompts[group].free_thinker,
|
||||
});
|
||||
grounding = parseJsonWithRepair(groundingRaw.content, (value) => groundingOutputSchema.parse(value));
|
||||
} catch {
|
||||
@@ -686,6 +700,7 @@ export class CouncilsOrchestrator {
|
||||
promptPayload: payload,
|
||||
modeDirective:
|
||||
'Return JSON only following schema with selected_primary/selected_secondary/merges/rejections/open_questions/next_experiments. Use only known idea IDs.',
|
||||
scaffoldPrompt: this._scaffold?.prompts.meta_arbiter,
|
||||
});
|
||||
|
||||
return parseJsonWithRepair(metaRaw.content, (value) => metaSelectionSchema.parse(value));
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { z } from 'zod';
|
||||
|
||||
const councilPromptSetSchema = z.object({
|
||||
arbiter: z.string().min(1),
|
||||
free_thinker: z.string().min(1),
|
||||
grounder: z.string().min(1).optional(),
|
||||
writer: z.string().min(1).optional(),
|
||||
}).strict();
|
||||
|
||||
export const councilScaffoldSchema = z.object({
|
||||
generated_at: z.string().optional(),
|
||||
version: z.string().optional(),
|
||||
prompts: z.object({
|
||||
D: councilPromptSetSchema,
|
||||
P: councilPromptSetSchema,
|
||||
meta_arbiter: z.string().min(1),
|
||||
}).strict(),
|
||||
}).strict();
|
||||
|
||||
export type CouncilScaffold = z.infer<typeof councilScaffoldSchema>;
|
||||
|
||||
export function loadCouncilScaffold(path: string): CouncilScaffold {
|
||||
const raw = readFileSync(path, 'utf8');
|
||||
return councilScaffoldSchema.parse(JSON.parse(raw));
|
||||
}
|
||||
|
||||
export function loadCouncilScaffoldSafe(path?: string): CouncilScaffold | undefined {
|
||||
if (!path) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return loadCouncilScaffold(path);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user