feat(councils-ui): add on-demand council conversations panel and model config controls
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
type CouncilGroup,
|
||||
type CouncilRunInput,
|
||||
type CouncilRunResult,
|
||||
type CouncilConversationTurn,
|
||||
type CouncilTraceEvent,
|
||||
type IdeaAssessment,
|
||||
type IdeaCard,
|
||||
@@ -61,6 +62,7 @@ export interface CouncilsConfig {
|
||||
P: CouncilGroupConfig;
|
||||
};
|
||||
meta_arbiter_agent: string;
|
||||
meta_model_tier?: ModelTier;
|
||||
meta_writer_agent?: string;
|
||||
scaffold_path?: string;
|
||||
}
|
||||
@@ -70,6 +72,7 @@ interface CouncilGroupConfig {
|
||||
freethinker_agent: string;
|
||||
grounder_agent?: string;
|
||||
writer_agent?: string;
|
||||
model_tier?: ModelTier;
|
||||
group_prompt_prefix: string;
|
||||
novelty_bias: 'low' | 'medium' | 'high';
|
||||
risk_tolerance: 'low' | 'medium' | 'high';
|
||||
@@ -230,6 +233,7 @@ export class CouncilsOrchestrator {
|
||||
private readonly _config: CouncilsConfig;
|
||||
private readonly _scaffold?: CouncilScaffold;
|
||||
private readonly _trace: CouncilTraceEvent[] = [];
|
||||
private readonly _conversations: CouncilConversationTurn[] = [];
|
||||
|
||||
constructor(deps: {
|
||||
registry: AgentConfigRegistry;
|
||||
@@ -250,6 +254,7 @@ export class CouncilsOrchestrator {
|
||||
}
|
||||
|
||||
this._trace.length = 0;
|
||||
this._conversations.length = 0;
|
||||
const inputHash = hashCanonical(input);
|
||||
const maxRounds = input.max_rounds ?? this._config.defaults.max_rounds;
|
||||
|
||||
@@ -370,6 +375,7 @@ export class CouncilsOrchestrator {
|
||||
meta,
|
||||
stop_snapshot: stopSnapshot,
|
||||
trace: this.getSortedTrace(),
|
||||
conversations: this.getSortedConversations(),
|
||||
});
|
||||
|
||||
return result;
|
||||
@@ -395,6 +401,7 @@ export class CouncilsOrchestrator {
|
||||
promptPayload: unknown;
|
||||
modeDirective: string;
|
||||
scaffoldPrompt?: string;
|
||||
tierOverride?: ModelTier;
|
||||
maxTokens?: number;
|
||||
}): Promise<AgentCallResult> {
|
||||
const agent = this.getAgent(opts.agentName);
|
||||
@@ -404,7 +411,7 @@ export class CouncilsOrchestrator {
|
||||
const systemPrompt = `${scaffoldPrompt}${agent.systemPrompt}\n\n${opts.modeDirective}`;
|
||||
|
||||
const result = await this._delegateRunner.delegate({
|
||||
tier: agent.tier,
|
||||
tier: opts.tierOverride ?? agent.tier,
|
||||
systemPrompt,
|
||||
message,
|
||||
maxTokens: opts.maxTokens ?? 4096,
|
||||
@@ -421,6 +428,18 @@ export class CouncilsOrchestrator {
|
||||
artifact_hash: hashCanonical(result.content),
|
||||
token_usage: result.usage,
|
||||
}));
|
||||
this._conversations.push({
|
||||
schema_version: COUNCIL_SCHEMA_VERSION,
|
||||
phase_index: opts.phaseIndex,
|
||||
call_id: opts.callId,
|
||||
agent: opts.agentName,
|
||||
tier: opts.tierOverride ?? agent.tier,
|
||||
group: opts.group,
|
||||
round: opts.round,
|
||||
prompt_payload: opts.promptPayload as Record<string, unknown>,
|
||||
response: result.content,
|
||||
usage: result.usage,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -437,6 +456,7 @@ export class CouncilsOrchestrator {
|
||||
previousBrief?: CouncilBrief,
|
||||
): Promise<GroupRoundResult> {
|
||||
const groupConfig = this._config.groups[group];
|
||||
const groupTier = groupConfig.model_tier;
|
||||
const phaseBase = round * 10 + (group === 'D' ? 1 : 2);
|
||||
|
||||
const ideationPayload = {
|
||||
@@ -462,6 +482,7 @@ export class CouncilsOrchestrator {
|
||||
promptPayload: ideationPayload,
|
||||
modeDirective: 'Return JSON only: {"ideas":[IdeaContent,...]}. Do not include IDs. No prose.',
|
||||
scaffoldPrompt: this._scaffold?.prompts[group].free_thinker,
|
||||
tierOverride: groupTier,
|
||||
});
|
||||
|
||||
const ideaOutput = parseJsonWithRepair(ideation.content, (value) => ideationOutputSchema.parse(value));
|
||||
@@ -494,6 +515,7 @@ export class CouncilsOrchestrator {
|
||||
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,
|
||||
tierOverride: groupTier,
|
||||
});
|
||||
|
||||
const assessmentOutput = parseJsonWithRepair(assessmentRaw.content, (value) => assessmentOutputSchema.parse(value));
|
||||
@@ -550,6 +572,7 @@ export class CouncilsOrchestrator {
|
||||
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,
|
||||
tierOverride: groupTier,
|
||||
});
|
||||
grounding = parseJsonWithRepair(groundingRaw.content, (value) => groundingOutputSchema.parse(value));
|
||||
} catch {
|
||||
@@ -701,6 +724,7 @@ export class CouncilsOrchestrator {
|
||||
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,
|
||||
tierOverride: this._config.meta_model_tier,
|
||||
});
|
||||
|
||||
return parseJsonWithRepair(metaRaw.content, (value) => metaSelectionSchema.parse(value));
|
||||
@@ -743,6 +767,15 @@ export class CouncilsOrchestrator {
|
||||
validation_failure: normalizeOptional(event.validation_failure),
|
||||
}));
|
||||
}
|
||||
|
||||
private getSortedConversations(): CouncilConversationTurn[] {
|
||||
return [...this._conversations]
|
||||
.sort((a, b) => a.phase_index - b.phase_index || a.call_id.localeCompare(b.call_id))
|
||||
.map((entry) => ({
|
||||
...entry,
|
||||
prompt_payload: JSON.parse(canonicalStringify(entry.prompt_payload)) as Record<string, unknown>,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export function createCouncilsOrchestrator(deps: {
|
||||
|
||||
@@ -170,6 +170,22 @@ export const councilTraceEventSchema = z.object({
|
||||
validation_failure: validationFailureReasonSchema.optional(),
|
||||
}).strict();
|
||||
|
||||
export const councilConversationTurnSchema = z.object({
|
||||
schema_version: schemaVersionField,
|
||||
phase_index: z.number().int().min(1),
|
||||
call_id: z.string().min(1),
|
||||
agent: z.string().min(1),
|
||||
tier: z.enum(['fast', 'default', 'complex', 'local']),
|
||||
group: councilGroupSchema.optional(),
|
||||
round: z.number().int().min(1).optional(),
|
||||
prompt_payload: z.record(z.string(), z.unknown()),
|
||||
response: z.string(),
|
||||
usage: z.object({
|
||||
inputTokens: z.number().int().min(0),
|
||||
outputTokens: z.number().int().min(0),
|
||||
}).strict(),
|
||||
}).strict();
|
||||
|
||||
export const stopSnapshotSchema = z.object({
|
||||
stop_reason: stopReasonSchema,
|
||||
round_reached: z.number().int().min(1),
|
||||
@@ -193,6 +209,7 @@ export const councilRunResultSchema = z.object({
|
||||
meta: metaSelectionSchema,
|
||||
stop_snapshot: stopSnapshotSchema,
|
||||
trace: z.array(councilTraceEventSchema),
|
||||
conversations: z.array(councilConversationTurnSchema),
|
||||
}).strict();
|
||||
|
||||
export const councilRunInputSchema = z.object({
|
||||
@@ -246,4 +263,5 @@ 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 CouncilConversationTurn = z.infer<typeof councilConversationTurnSchema>;
|
||||
export type MetaSelection = z.infer<typeof metaSelectionSchema>;
|
||||
|
||||
Reference in New Issue
Block a user