@sebastientang/llm-council 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/bin/llm-council.js +2 -0
- package/dist/cli.js +1151 -0
- package/dist/index.cjs +986 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +393 -0
- package/dist/index.d.ts +393 -0
- package/dist/index.js +931 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
AdversarialProtocol: () => AdversarialProtocol,
|
|
34
|
+
AnthropicProvider: () => AnthropicProvider,
|
|
35
|
+
ChairmanSynthesizer: () => ChairmanSynthesizer,
|
|
36
|
+
Council: () => Council,
|
|
37
|
+
DeliberationConfigSchema: () => DeliberationConfigSchema,
|
|
38
|
+
DeliberationMessageSchema: () => DeliberationMessageSchema,
|
|
39
|
+
DeliberationMetadataSchema: () => DeliberationMetadataSchema,
|
|
40
|
+
DialecticalSynthesizer: () => DialecticalSynthesizer,
|
|
41
|
+
OpenRouterProvider: () => OpenRouterProvider,
|
|
42
|
+
PERSONAS: () => PERSONAS,
|
|
43
|
+
ParticipantSchema: () => ParticipantSchema,
|
|
44
|
+
PeerReviewProtocol: () => PeerReviewProtocol,
|
|
45
|
+
SynthesisSchema: () => SynthesisSchema,
|
|
46
|
+
anonymizeMessages: () => anonymizeMessages,
|
|
47
|
+
buildInitialUserMessage: () => buildInitialUserMessage,
|
|
48
|
+
buildSynthesisUserMessage: () => buildSynthesisUserMessage,
|
|
49
|
+
createAnonymizationMap: () => createAnonymizationMap,
|
|
50
|
+
deanonymizeLabel: () => deanonymizeLabel,
|
|
51
|
+
parseSynthesisResponse: () => parseSynthesisResponse
|
|
52
|
+
});
|
|
53
|
+
module.exports = __toCommonJS(index_exports);
|
|
54
|
+
|
|
55
|
+
// src/engine.ts
|
|
56
|
+
var import_mitt = __toESM(require("mitt"), 1);
|
|
57
|
+
|
|
58
|
+
// src/types.ts
|
|
59
|
+
var import_zod = require("zod");
|
|
60
|
+
var ParticipantSchema = import_zod.z.object({
|
|
61
|
+
id: import_zod.z.string(),
|
|
62
|
+
name: import_zod.z.string(),
|
|
63
|
+
provider: import_zod.z.string(),
|
|
64
|
+
model: import_zod.z.string(),
|
|
65
|
+
systemPrompt: import_zod.z.string(),
|
|
66
|
+
temperature: import_zod.z.number().min(0).max(2).optional()
|
|
67
|
+
});
|
|
68
|
+
var SynthesisSchema = import_zod.z.object({
|
|
69
|
+
recommendation: import_zod.z.string(),
|
|
70
|
+
confidence: import_zod.z.number().min(0).max(100),
|
|
71
|
+
reasoning: import_zod.z.string(),
|
|
72
|
+
risks: import_zod.z.array(import_zod.z.string()),
|
|
73
|
+
dissent: import_zod.z.array(import_zod.z.string()),
|
|
74
|
+
validationGates: import_zod.z.array(import_zod.z.string()),
|
|
75
|
+
assumptions: import_zod.z.array(import_zod.z.string()),
|
|
76
|
+
raw: import_zod.z.string()
|
|
77
|
+
});
|
|
78
|
+
var DeliberationMessageSchema = import_zod.z.object({
|
|
79
|
+
participantId: import_zod.z.string(),
|
|
80
|
+
participantName: import_zod.z.string(),
|
|
81
|
+
round: import_zod.z.number(),
|
|
82
|
+
content: import_zod.z.string(),
|
|
83
|
+
timestamp: import_zod.z.date(),
|
|
84
|
+
tokenCount: import_zod.z.object({ input: import_zod.z.number(), output: import_zod.z.number() })
|
|
85
|
+
});
|
|
86
|
+
var DeliberationConfigSchema = import_zod.z.object({
|
|
87
|
+
topic: import_zod.z.string().min(1),
|
|
88
|
+
options: import_zod.z.array(import_zod.z.string()).optional(),
|
|
89
|
+
context: import_zod.z.string().optional(),
|
|
90
|
+
preferredOption: import_zod.z.string().optional(),
|
|
91
|
+
participants: import_zod.z.array(ParticipantSchema).min(2),
|
|
92
|
+
rounds: import_zod.z.number().min(1).max(5).default(2),
|
|
93
|
+
tokenBudget: import_zod.z.object({
|
|
94
|
+
perResponse: import_zod.z.number().positive().optional(),
|
|
95
|
+
total: import_zod.z.number().positive().optional()
|
|
96
|
+
}).optional()
|
|
97
|
+
});
|
|
98
|
+
var DeliberationMetadataSchema = import_zod.z.object({
|
|
99
|
+
totalTokens: import_zod.z.object({ input: import_zod.z.number(), output: import_zod.z.number() }),
|
|
100
|
+
durationMs: import_zod.z.number(),
|
|
101
|
+
modelBreakdown: import_zod.z.record(
|
|
102
|
+
import_zod.z.string(),
|
|
103
|
+
import_zod.z.object({ input: import_zod.z.number(), output: import_zod.z.number() })
|
|
104
|
+
)
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// src/engine.ts
|
|
108
|
+
var Council = class {
|
|
109
|
+
providers;
|
|
110
|
+
protocol;
|
|
111
|
+
synthesizer;
|
|
112
|
+
synthesisProvider;
|
|
113
|
+
emitter = (0, import_mitt.default)();
|
|
114
|
+
constructor(options) {
|
|
115
|
+
this.providers = options.providers;
|
|
116
|
+
this.protocol = options.protocol;
|
|
117
|
+
this.synthesizer = options.synthesizer;
|
|
118
|
+
this.synthesisProvider = options.synthesisProvider;
|
|
119
|
+
}
|
|
120
|
+
on(event, handler) {
|
|
121
|
+
this.emitter.on(event, handler);
|
|
122
|
+
}
|
|
123
|
+
off(event, handler) {
|
|
124
|
+
this.emitter.off(event, handler);
|
|
125
|
+
}
|
|
126
|
+
async deliberate(rawConfig) {
|
|
127
|
+
try {
|
|
128
|
+
const config = DeliberationConfigSchema.parse(rawConfig);
|
|
129
|
+
const startTime = Date.now();
|
|
130
|
+
const allMessages = [];
|
|
131
|
+
const tokenTracker = {};
|
|
132
|
+
const roundCount = this.protocol.getRoundCount();
|
|
133
|
+
const rounds = Math.min(config.rounds, roundCount);
|
|
134
|
+
for (let round = 1; round <= rounds; round++) {
|
|
135
|
+
const prompts = this.protocol.buildPrompts(config, allMessages, round);
|
|
136
|
+
this.emitter.emit("round:start", {
|
|
137
|
+
round,
|
|
138
|
+
participantCount: prompts.length
|
|
139
|
+
});
|
|
140
|
+
const roundMessages = await Promise.all(
|
|
141
|
+
prompts.map(async (prompt) => {
|
|
142
|
+
const provider2 = this.providers.get(prompt.provider);
|
|
143
|
+
if (!provider2) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Provider '${prompt.provider}' not found. Available: ${[...this.providers.keys()].join(", ")}`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const response = await provider2.complete({
|
|
149
|
+
model: prompt.model,
|
|
150
|
+
systemPrompt: prompt.systemPrompt,
|
|
151
|
+
messages: [{ role: "user", content: prompt.userMessage }],
|
|
152
|
+
temperature: prompt.temperature,
|
|
153
|
+
maxTokens: prompt.maxTokens
|
|
154
|
+
});
|
|
155
|
+
const participant = config.participants.find(
|
|
156
|
+
(p) => p.id === prompt.participantId
|
|
157
|
+
);
|
|
158
|
+
const message = {
|
|
159
|
+
participantId: prompt.participantId,
|
|
160
|
+
participantName: participant?.name ?? prompt.participantId,
|
|
161
|
+
round,
|
|
162
|
+
content: response.content,
|
|
163
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
164
|
+
tokenCount: response.tokenCount
|
|
165
|
+
};
|
|
166
|
+
const modelKey = `${prompt.provider}/${prompt.model}`;
|
|
167
|
+
const existing = tokenTracker[modelKey] ?? { input: 0, output: 0 };
|
|
168
|
+
tokenTracker[modelKey] = {
|
|
169
|
+
input: existing.input + response.tokenCount.input,
|
|
170
|
+
output: existing.output + response.tokenCount.output
|
|
171
|
+
};
|
|
172
|
+
this.emitter.emit("response", message);
|
|
173
|
+
return message;
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
allMessages.push(...roundMessages);
|
|
177
|
+
}
|
|
178
|
+
this.emitter.emit("synthesis:start", void 0);
|
|
179
|
+
const synthesisProviderConfig = this.synthesisProvider ?? {
|
|
180
|
+
providerId: config.participants[0].provider,
|
|
181
|
+
model: config.participants[0].model
|
|
182
|
+
};
|
|
183
|
+
const provider = this.providers.get(synthesisProviderConfig.providerId);
|
|
184
|
+
if (!provider) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Synthesis provider '${synthesisProviderConfig.providerId}' not found`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const synthesis = await this.synthesizer.synthesize(
|
|
190
|
+
config,
|
|
191
|
+
allMessages,
|
|
192
|
+
provider
|
|
193
|
+
);
|
|
194
|
+
const durationMs = Date.now() - startTime;
|
|
195
|
+
const totalTokens = Object.values(tokenTracker).reduce(
|
|
196
|
+
(acc, t) => ({
|
|
197
|
+
input: acc.input + t.input,
|
|
198
|
+
output: acc.output + t.output
|
|
199
|
+
}),
|
|
200
|
+
{ input: 0, output: 0 }
|
|
201
|
+
);
|
|
202
|
+
const metadata = {
|
|
203
|
+
totalTokens,
|
|
204
|
+
durationMs,
|
|
205
|
+
modelBreakdown: tokenTracker
|
|
206
|
+
};
|
|
207
|
+
const result = {
|
|
208
|
+
config,
|
|
209
|
+
messages: allMessages,
|
|
210
|
+
synthesis,
|
|
211
|
+
metadata
|
|
212
|
+
};
|
|
213
|
+
this.emitter.emit("complete", result);
|
|
214
|
+
return result;
|
|
215
|
+
} catch (error) {
|
|
216
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
217
|
+
this.emitter.emit("error", err);
|
|
218
|
+
throw err;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// src/providers/anthropic.ts
|
|
224
|
+
var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
|
|
225
|
+
var AnthropicProvider = class {
|
|
226
|
+
id = "anthropic";
|
|
227
|
+
client;
|
|
228
|
+
defaultModel;
|
|
229
|
+
defaultMaxTokens;
|
|
230
|
+
constructor(config) {
|
|
231
|
+
this.client = new import_sdk.default({ apiKey: config.apiKey });
|
|
232
|
+
this.defaultModel = config.defaultModel ?? "claude-sonnet-4-20250514";
|
|
233
|
+
this.defaultMaxTokens = config.defaultMaxTokens ?? 1024;
|
|
234
|
+
}
|
|
235
|
+
async complete(request) {
|
|
236
|
+
const params = {
|
|
237
|
+
model: request.model || this.defaultModel,
|
|
238
|
+
max_tokens: request.maxTokens ?? this.defaultMaxTokens,
|
|
239
|
+
system: request.systemPrompt,
|
|
240
|
+
messages: request.messages.map((msg) => ({
|
|
241
|
+
role: msg.role,
|
|
242
|
+
content: msg.content
|
|
243
|
+
})),
|
|
244
|
+
...request.temperature !== void 0 && {
|
|
245
|
+
temperature: request.temperature
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
let response;
|
|
249
|
+
try {
|
|
250
|
+
response = await this.client.messages.create(params);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (isOverloadedError(error)) {
|
|
253
|
+
await delay(1e3);
|
|
254
|
+
response = await this.client.messages.create(params);
|
|
255
|
+
} else {
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const textBlock = response.content.find(
|
|
260
|
+
(block) => block.type === "text"
|
|
261
|
+
);
|
|
262
|
+
if (!textBlock) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`Anthropic response contained no text block. Stop reason: ${response.stop_reason}`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
content: textBlock.text,
|
|
269
|
+
tokenCount: {
|
|
270
|
+
input: response.usage.input_tokens,
|
|
271
|
+
output: response.usage.output_tokens
|
|
272
|
+
},
|
|
273
|
+
model: response.model
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
function isOverloadedError(error) {
|
|
278
|
+
if (error instanceof import_sdk.default.APIError) {
|
|
279
|
+
return error.status === 529;
|
|
280
|
+
}
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
function delay(ms) {
|
|
284
|
+
return new Promise((resolve) => {
|
|
285
|
+
setTimeout(resolve, ms);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/providers/openrouter.ts
|
|
290
|
+
var OpenRouterProvider = class {
|
|
291
|
+
id = "openrouter";
|
|
292
|
+
apiKey;
|
|
293
|
+
appName;
|
|
294
|
+
defaultModel;
|
|
295
|
+
defaultMaxTokens;
|
|
296
|
+
siteUrl;
|
|
297
|
+
constructor(config) {
|
|
298
|
+
this.apiKey = config.apiKey;
|
|
299
|
+
this.appName = config.appName ?? "llm-council";
|
|
300
|
+
this.defaultModel = config.defaultModel ?? "openai/gpt-4o";
|
|
301
|
+
this.defaultMaxTokens = config.defaultMaxTokens ?? 1024;
|
|
302
|
+
this.siteUrl = config.siteUrl ?? "";
|
|
303
|
+
}
|
|
304
|
+
async complete(request) {
|
|
305
|
+
const body = {
|
|
306
|
+
model: request.model || this.defaultModel,
|
|
307
|
+
messages: [
|
|
308
|
+
{ role: "system", content: request.systemPrompt },
|
|
309
|
+
...request.messages.map((msg) => ({
|
|
310
|
+
role: msg.role,
|
|
311
|
+
content: msg.content
|
|
312
|
+
}))
|
|
313
|
+
],
|
|
314
|
+
max_tokens: request.maxTokens ?? this.defaultMaxTokens,
|
|
315
|
+
...request.temperature !== void 0 && {
|
|
316
|
+
temperature: request.temperature
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
const headers = {
|
|
320
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
321
|
+
"Content-Type": "application/json",
|
|
322
|
+
"X-Title": this.appName
|
|
323
|
+
};
|
|
324
|
+
if (this.siteUrl) {
|
|
325
|
+
headers["HTTP-Referer"] = this.siteUrl;
|
|
326
|
+
}
|
|
327
|
+
let response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
328
|
+
method: "POST",
|
|
329
|
+
headers,
|
|
330
|
+
body: JSON.stringify(body)
|
|
331
|
+
});
|
|
332
|
+
if (response.status === 429) {
|
|
333
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
334
|
+
const delayMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : 2e3;
|
|
335
|
+
await delay2(Math.min(delayMs, 1e4));
|
|
336
|
+
response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
337
|
+
method: "POST",
|
|
338
|
+
headers,
|
|
339
|
+
body: JSON.stringify(body)
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
if (!response.ok) {
|
|
343
|
+
const errorBody = await response.json().catch(() => null);
|
|
344
|
+
const message = errorBody?.error?.message ?? `HTTP ${response.status}`;
|
|
345
|
+
throw new Error(`OpenRouter API error (${response.status}): ${message}`);
|
|
346
|
+
}
|
|
347
|
+
const data = await response.json();
|
|
348
|
+
const content = data.choices?.[0]?.message?.content;
|
|
349
|
+
if (!content) {
|
|
350
|
+
throw new Error("OpenRouter response contained no content");
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
content,
|
|
354
|
+
tokenCount: {
|
|
355
|
+
input: data.usage?.prompt_tokens ?? 0,
|
|
356
|
+
output: data.usage?.completion_tokens ?? 0
|
|
357
|
+
},
|
|
358
|
+
model: data.model
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
function delay2(ms) {
|
|
363
|
+
return new Promise((resolve) => {
|
|
364
|
+
setTimeout(resolve, ms);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/utils/prompt-builder.ts
|
|
369
|
+
function buildInitialUserMessage(config) {
|
|
370
|
+
const parts = [];
|
|
371
|
+
parts.push(`## Decision Topic
|
|
372
|
+
${config.topic}`);
|
|
373
|
+
if (config.options && config.options.length > 0) {
|
|
374
|
+
const optionsList = config.options.map((opt, i) => `${i + 1}. ${opt}`).join("\n");
|
|
375
|
+
parts.push(`## Options
|
|
376
|
+
${optionsList}`);
|
|
377
|
+
}
|
|
378
|
+
if (config.preferredOption) {
|
|
379
|
+
parts.push(`## Preferred Option
|
|
380
|
+
${config.preferredOption}`);
|
|
381
|
+
}
|
|
382
|
+
if (config.context) {
|
|
383
|
+
parts.push(`## Additional Context
|
|
384
|
+
${config.context}`);
|
|
385
|
+
}
|
|
386
|
+
parts.push(
|
|
387
|
+
"Provide your initial brief (200-400 words). State your position clearly and support it with reasoning."
|
|
388
|
+
);
|
|
389
|
+
return parts.join("\n\n");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/protocols/adversarial.ts
|
|
393
|
+
var DEFAULT_REBUTTAL_GUIDANCE = {
|
|
394
|
+
proposer: "Focus your rebuttal on the Challenger's strongest attack.",
|
|
395
|
+
challenger: "Focus on whether the Proposer's acknowledged weakness is actually fatal.",
|
|
396
|
+
steelmanner: "Focus on whether the Pre-Mortem's failure story applies equally to the rejected option.",
|
|
397
|
+
"pre-mortem": "Focus on which of the Proposer's assumptions appear in your failure cascade."
|
|
398
|
+
};
|
|
399
|
+
function getRebuttalGuidance(participantId, guidanceMap) {
|
|
400
|
+
for (const [key, guidance] of Object.entries(guidanceMap)) {
|
|
401
|
+
if (participantId.toLowerCase().includes(key.toLowerCase())) {
|
|
402
|
+
return guidance;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return "Address the most critical point from another member.";
|
|
406
|
+
}
|
|
407
|
+
function buildRebuttalUserMessage(participantId, otherMessages, guidanceMap) {
|
|
408
|
+
const parts = [];
|
|
409
|
+
parts.push("Here are the other Council members' briefs:");
|
|
410
|
+
for (const msg of otherMessages) {
|
|
411
|
+
parts.push(`### ${msg.participantName}
|
|
412
|
+
${msg.content}`);
|
|
413
|
+
}
|
|
414
|
+
const guidance = getRebuttalGuidance(participantId, guidanceMap);
|
|
415
|
+
parts.push(
|
|
416
|
+
`Write your rebuttal (100-200 words). ${guidance}`
|
|
417
|
+
);
|
|
418
|
+
return parts.join("\n\n");
|
|
419
|
+
}
|
|
420
|
+
var AdversarialProtocol = class {
|
|
421
|
+
rebuttalGuidance;
|
|
422
|
+
constructor(options) {
|
|
423
|
+
this.rebuttalGuidance = options?.rebuttalGuidance ?? DEFAULT_REBUTTAL_GUIDANCE;
|
|
424
|
+
}
|
|
425
|
+
getRoundCount() {
|
|
426
|
+
return 2;
|
|
427
|
+
}
|
|
428
|
+
buildPrompts(config, history, round) {
|
|
429
|
+
if (round === 1) {
|
|
430
|
+
return this.buildInitialBriefs(config);
|
|
431
|
+
}
|
|
432
|
+
if (round === 2) {
|
|
433
|
+
return this.buildRebuttals(config, history);
|
|
434
|
+
}
|
|
435
|
+
return [];
|
|
436
|
+
}
|
|
437
|
+
buildInitialBriefs(config) {
|
|
438
|
+
const userMessage = buildInitialUserMessage(config);
|
|
439
|
+
return config.participants.map((participant) => ({
|
|
440
|
+
participantId: participant.id,
|
|
441
|
+
provider: participant.provider,
|
|
442
|
+
model: participant.model,
|
|
443
|
+
systemPrompt: participant.systemPrompt,
|
|
444
|
+
userMessage,
|
|
445
|
+
temperature: participant.temperature ?? 0.7,
|
|
446
|
+
maxTokens: config.tokenBudget?.perResponse
|
|
447
|
+
}));
|
|
448
|
+
}
|
|
449
|
+
buildRebuttals(config, history) {
|
|
450
|
+
const round1Messages = history.filter((msg) => msg.round === 1);
|
|
451
|
+
return config.participants.map((participant) => {
|
|
452
|
+
const otherMessages = round1Messages.filter(
|
|
453
|
+
(msg) => msg.participantId !== participant.id
|
|
454
|
+
);
|
|
455
|
+
return {
|
|
456
|
+
participantId: participant.id,
|
|
457
|
+
provider: participant.provider,
|
|
458
|
+
model: participant.model,
|
|
459
|
+
systemPrompt: participant.systemPrompt,
|
|
460
|
+
userMessage: buildRebuttalUserMessage(
|
|
461
|
+
participant.id,
|
|
462
|
+
otherMessages,
|
|
463
|
+
this.rebuttalGuidance
|
|
464
|
+
),
|
|
465
|
+
temperature: participant.temperature ?? 0.7,
|
|
466
|
+
maxTokens: config.tokenBudget?.perResponse
|
|
467
|
+
};
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// src/utils/anonymizer.ts
|
|
473
|
+
var LABELS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
|
|
474
|
+
function createAnonymizationMap(participantIds) {
|
|
475
|
+
const labelToParticipant = /* @__PURE__ */ new Map();
|
|
476
|
+
const participantToLabel = /* @__PURE__ */ new Map();
|
|
477
|
+
for (let i = 0; i < participantIds.length; i++) {
|
|
478
|
+
const label = `Response ${LABELS[i] ?? String(i + 1)}`;
|
|
479
|
+
labelToParticipant.set(label, participantIds[i]);
|
|
480
|
+
participantToLabel.set(participantIds[i], label);
|
|
481
|
+
}
|
|
482
|
+
return { labelToParticipant, participantToLabel };
|
|
483
|
+
}
|
|
484
|
+
function anonymizeMessages(messages, map) {
|
|
485
|
+
return messages.map((msg) => ({
|
|
486
|
+
label: map.participantToLabel.get(msg.participantId) ?? "Unknown",
|
|
487
|
+
content: msg.content
|
|
488
|
+
}));
|
|
489
|
+
}
|
|
490
|
+
function deanonymizeLabel(label, map) {
|
|
491
|
+
return map.labelToParticipant.get(label);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/protocols/peer-review.ts
|
|
495
|
+
var PeerReviewProtocol = class {
|
|
496
|
+
enableRevote;
|
|
497
|
+
constructor(options) {
|
|
498
|
+
this.enableRevote = options?.enableRevote ?? false;
|
|
499
|
+
}
|
|
500
|
+
getRoundCount() {
|
|
501
|
+
return this.enableRevote ? 3 : 2;
|
|
502
|
+
}
|
|
503
|
+
buildPrompts(config, history, round) {
|
|
504
|
+
if (round === 1) {
|
|
505
|
+
return this.buildInitialBriefs(config);
|
|
506
|
+
}
|
|
507
|
+
const anonMap = createAnonymizationMap(config.participants.map((p) => p.id));
|
|
508
|
+
if (round === 2) {
|
|
509
|
+
return this.buildRankingRound(config, history, anonMap);
|
|
510
|
+
}
|
|
511
|
+
if (round === 3 && this.enableRevote) {
|
|
512
|
+
return this.buildRevoteRound(config, history, anonMap);
|
|
513
|
+
}
|
|
514
|
+
return [];
|
|
515
|
+
}
|
|
516
|
+
buildInitialBriefs(config) {
|
|
517
|
+
const userMessage = buildInitialUserMessage(config);
|
|
518
|
+
return config.participants.map((participant) => ({
|
|
519
|
+
participantId: participant.id,
|
|
520
|
+
provider: participant.provider,
|
|
521
|
+
model: participant.model,
|
|
522
|
+
systemPrompt: participant.systemPrompt,
|
|
523
|
+
userMessage,
|
|
524
|
+
temperature: participant.temperature ?? 0.7,
|
|
525
|
+
maxTokens: config.tokenBudget?.perResponse
|
|
526
|
+
}));
|
|
527
|
+
}
|
|
528
|
+
buildRankingRound(config, history, anonMap) {
|
|
529
|
+
const round1Messages = history.filter((msg) => msg.round === 1);
|
|
530
|
+
const anonymized = anonymizeMessages(round1Messages, anonMap);
|
|
531
|
+
const briefsText = anonymized.map((entry) => `### ${entry.label}
|
|
532
|
+
${entry.content}`).join("\n\n");
|
|
533
|
+
const labelList = anonymized.map((e) => e.label).join(", ");
|
|
534
|
+
return config.participants.map((participant) => ({
|
|
535
|
+
participantId: participant.id,
|
|
536
|
+
provider: participant.provider,
|
|
537
|
+
model: participant.model,
|
|
538
|
+
systemPrompt: participant.systemPrompt,
|
|
539
|
+
userMessage: buildRankingUserMessage(briefsText, labelList),
|
|
540
|
+
temperature: participant.temperature ?? 0.7,
|
|
541
|
+
maxTokens: config.tokenBudget?.perResponse
|
|
542
|
+
}));
|
|
543
|
+
}
|
|
544
|
+
buildRevoteRound(config, history, anonMap) {
|
|
545
|
+
const round2Messages = history.filter((msg) => msg.round === 2);
|
|
546
|
+
const anonymizedRankings = anonymizeMessages(round2Messages, anonMap);
|
|
547
|
+
const rankingsText = anonymizedRankings.map((entry) => `### ${entry.label}'s Rankings
|
|
548
|
+
${entry.content}`).join("\n\n");
|
|
549
|
+
const round1Messages = history.filter((msg) => msg.round === 1);
|
|
550
|
+
const anonymizedBriefs = anonymizeMessages(round1Messages, anonMap);
|
|
551
|
+
const labelList = anonymizedBriefs.map((e) => e.label).join(", ");
|
|
552
|
+
return config.participants.map((participant) => ({
|
|
553
|
+
participantId: participant.id,
|
|
554
|
+
provider: participant.provider,
|
|
555
|
+
model: participant.model,
|
|
556
|
+
systemPrompt: participant.systemPrompt,
|
|
557
|
+
userMessage: buildRevoteUserMessage(rankingsText, labelList),
|
|
558
|
+
temperature: participant.temperature ?? 0.7,
|
|
559
|
+
maxTokens: config.tokenBudget?.perResponse
|
|
560
|
+
}));
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
function buildRankingUserMessage(briefsText, labelList) {
|
|
564
|
+
const parts = [];
|
|
565
|
+
parts.push("You are participating in an anonymized peer review. Below are briefs from all council members (including yours, but you do not know which one is yours).");
|
|
566
|
+
parts.push(briefsText);
|
|
567
|
+
parts.push(`Rank ALL responses from best to worst (${labelList}). For each, provide a one-sentence justification.
|
|
568
|
+
|
|
569
|
+
Respond in EXACTLY this format:
|
|
570
|
+
|
|
571
|
+
RANKING:
|
|
572
|
+
1. [Label] - [one-sentence justification]
|
|
573
|
+
2. [Label] - [one-sentence justification]
|
|
574
|
+
3. [Label] - [one-sentence justification]
|
|
575
|
+
4. [Label] - [one-sentence justification]`);
|
|
576
|
+
return parts.join("\n\n");
|
|
577
|
+
}
|
|
578
|
+
function buildRevoteUserMessage(rankingsText, labelList) {
|
|
579
|
+
const parts = [];
|
|
580
|
+
parts.push("You have seen the other council members' anonymous rankings. Review them and submit your final ranking.");
|
|
581
|
+
parts.push(rankingsText);
|
|
582
|
+
parts.push(`Submit your FINAL ranking of all responses (${labelList}). Consider the other members' perspectives.
|
|
583
|
+
|
|
584
|
+
Respond in EXACTLY this format:
|
|
585
|
+
|
|
586
|
+
RANKING:
|
|
587
|
+
1. [Label] - [one-sentence justification]
|
|
588
|
+
2. [Label] - [one-sentence justification]
|
|
589
|
+
3. [Label] - [one-sentence justification]
|
|
590
|
+
4. [Label] - [one-sentence justification]`);
|
|
591
|
+
return parts.join("\n\n");
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/utils/synthesis-parser.ts
|
|
595
|
+
function groupMessagesByRound(messages) {
|
|
596
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
597
|
+
for (const msg of messages) {
|
|
598
|
+
const existing = grouped.get(msg.round) ?? [];
|
|
599
|
+
existing.push(msg);
|
|
600
|
+
grouped.set(msg.round, existing);
|
|
601
|
+
}
|
|
602
|
+
return grouped;
|
|
603
|
+
}
|
|
604
|
+
function buildSynthesisUserMessage(config, messages) {
|
|
605
|
+
const parts = [];
|
|
606
|
+
parts.push(`# Decision Topic
|
|
607
|
+
${config.topic}`);
|
|
608
|
+
if (config.options && config.options.length > 0) {
|
|
609
|
+
parts.push(`# Options
|
|
610
|
+
${config.options.map((o, i) => `${i + 1}. ${o}`).join("\n")}`);
|
|
611
|
+
}
|
|
612
|
+
if (config.preferredOption) {
|
|
613
|
+
parts.push(`# Preferred Option
|
|
614
|
+
${config.preferredOption}`);
|
|
615
|
+
}
|
|
616
|
+
if (config.context) {
|
|
617
|
+
parts.push(`# Context
|
|
618
|
+
${config.context}`);
|
|
619
|
+
}
|
|
620
|
+
const roundLabels = {
|
|
621
|
+
1: "Initial Briefs",
|
|
622
|
+
2: "Rebuttals",
|
|
623
|
+
3: "Final Statements",
|
|
624
|
+
4: "Closing Arguments",
|
|
625
|
+
5: "Summary"
|
|
626
|
+
};
|
|
627
|
+
const grouped = groupMessagesByRound(messages);
|
|
628
|
+
const sortedRounds = [...grouped.keys()].sort((a, b) => a - b);
|
|
629
|
+
for (const round of sortedRounds) {
|
|
630
|
+
const label = roundLabels[round] ?? `Round ${round}`;
|
|
631
|
+
const roundMessages = grouped.get(round);
|
|
632
|
+
if (!roundMessages) continue;
|
|
633
|
+
parts.push(`## Round ${round}: ${label}`);
|
|
634
|
+
for (const msg of roundMessages) {
|
|
635
|
+
parts.push(`### ${msg.participantName}
|
|
636
|
+
${msg.content}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return parts.join("\n\n");
|
|
640
|
+
}
|
|
641
|
+
function escapeRegex(str) {
|
|
642
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
643
|
+
}
|
|
644
|
+
function parseListSection(raw, header) {
|
|
645
|
+
const escaped = escapeRegex(header);
|
|
646
|
+
const pattern = new RegExp(
|
|
647
|
+
`${escaped}:\\s*\\n((?:- .+(?:\\n|$))*)`,
|
|
648
|
+
"i"
|
|
649
|
+
);
|
|
650
|
+
const match = pattern.exec(raw);
|
|
651
|
+
if (!match?.[1]) return [];
|
|
652
|
+
return match[1].split("\n").map((line) => line.replace(/^- /, "").trim()).filter((line) => line.length > 0);
|
|
653
|
+
}
|
|
654
|
+
function parseSynthesisResponse(raw) {
|
|
655
|
+
const recommendationMatch = /RECOMMENDATION:\s*(.+)/i.exec(raw);
|
|
656
|
+
const confidenceMatch = /CONFIDENCE:\s*(\d+)/i.exec(raw);
|
|
657
|
+
const reasoningMatch = /REASONING:\s*(.+(?:\n(?!RISKS:|DISSENT:|VALIDATION_GATES:|ASSUMPTIONS:).+)*)/i.exec(raw);
|
|
658
|
+
const recommendation = recommendationMatch?.[1]?.trim() ?? raw.slice(0, 200);
|
|
659
|
+
const confidence = confidenceMatch ? parseInt(confidenceMatch[1], 10) : 50;
|
|
660
|
+
const reasoning = reasoningMatch?.[1]?.trim() ?? raw;
|
|
661
|
+
const risks = parseListSection(raw, "RISKS");
|
|
662
|
+
const dissent = parseListSection(raw, "DISSENT");
|
|
663
|
+
const validationGates = parseListSection(raw, "VALIDATION_GATES");
|
|
664
|
+
const assumptions = parseListSection(raw, "ASSUMPTIONS");
|
|
665
|
+
return {
|
|
666
|
+
recommendation,
|
|
667
|
+
confidence: Math.min(100, Math.max(0, confidence)),
|
|
668
|
+
reasoning,
|
|
669
|
+
risks,
|
|
670
|
+
dissent,
|
|
671
|
+
validationGates,
|
|
672
|
+
assumptions,
|
|
673
|
+
raw
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/synthesis/chairman.ts
|
|
678
|
+
var SYSTEM_PROMPT = `You are the Chairman of a council deliberation. Your job is to evaluate the arguments presented and SELECT THE BEST ONE. Do not synthesize or merge arguments. Pick the winner.
|
|
679
|
+
|
|
680
|
+
Evaluation criteria:
|
|
681
|
+
1. Evidence quality - arguments backed by specific data, precedent, or measurable outcomes score highest
|
|
682
|
+
2. Risk awareness - the best argument acknowledges its own weaknesses honestly
|
|
683
|
+
3. Actionability - prefer arguments with clear, concrete next steps over abstract reasoning
|
|
684
|
+
4. Logical coherence - the argument's conclusion must follow from its premises
|
|
685
|
+
|
|
686
|
+
Your confidence score reflects how clear the winner is:
|
|
687
|
+
- 90%+ = one argument is clearly superior, others have significant flaws
|
|
688
|
+
- 70-89% = a clear winner exists but the runner-up has merit
|
|
689
|
+
- 50-69% = close race between two or more arguments
|
|
690
|
+
- <50% = no clear winner, recommend gathering more data
|
|
691
|
+
|
|
692
|
+
Respond in EXACTLY this format (no deviations):
|
|
693
|
+
|
|
694
|
+
RECOMMENDATION: [state which participant's argument wins and why in one sentence]
|
|
695
|
+
CONFIDENCE: [number 0-100]
|
|
696
|
+
REASONING: [2-3 sentences explaining why this argument won over the others]
|
|
697
|
+
RISKS:
|
|
698
|
+
- [risk 1 from the winning argument]
|
|
699
|
+
- [risk 2]
|
|
700
|
+
- [risk 3]
|
|
701
|
+
DISSENT:
|
|
702
|
+
- [the runner-up's strongest point that nearly won]
|
|
703
|
+
VALIDATION_GATES:
|
|
704
|
+
- [gate 1 - measurable, time-bound]
|
|
705
|
+
- [gate 2]
|
|
706
|
+
ASSUMPTIONS:
|
|
707
|
+
- [assumption 1 the winning argument depends on]
|
|
708
|
+
- [assumption 2]`;
|
|
709
|
+
var ChairmanSynthesizer = class {
|
|
710
|
+
model;
|
|
711
|
+
temperature;
|
|
712
|
+
constructor(options = {}) {
|
|
713
|
+
this.model = options.model ?? "claude-sonnet-4-20250514";
|
|
714
|
+
this.temperature = options.temperature ?? 0.3;
|
|
715
|
+
}
|
|
716
|
+
async synthesize(config, messages, provider) {
|
|
717
|
+
const userMessage = buildSynthesisUserMessage(config, messages);
|
|
718
|
+
const response = await provider.complete({
|
|
719
|
+
model: this.model,
|
|
720
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
721
|
+
messages: [{ role: "user", content: userMessage }],
|
|
722
|
+
temperature: this.temperature
|
|
723
|
+
});
|
|
724
|
+
return parseSynthesisResponse(response.content);
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
// src/synthesis/dialectical.ts
|
|
729
|
+
var SYSTEM_PROMPT2 = `You are the Synthesis Moderator for a council deliberation. You have received briefs and rebuttals from multiple council members debating a decision. Your job is to:
|
|
730
|
+
1. Weigh evidence over opinion - arguments backed by specific data or precedent outweigh assertions
|
|
731
|
+
2. Favor reversibility when confidence is low - if the council is split, recommend the more reversible option
|
|
732
|
+
3. Synthesize, don't average - your recommendation should reflect the strongest arguments, not a compromise
|
|
733
|
+
4. Calibrate confidence honestly: 90%+ = unanimous agreement with strong evidence; 70-89% = majority with minor dissent; 50-69% = significant split or unknowns; <50% = fundamental disagreement, recommend gathering more data
|
|
734
|
+
|
|
735
|
+
Respond in EXACTLY this format (no deviations):
|
|
736
|
+
|
|
737
|
+
RECOMMENDATION: [one sentence]
|
|
738
|
+
CONFIDENCE: [number 0-100]
|
|
739
|
+
REASONING: [2-3 sentences explaining the recommendation]
|
|
740
|
+
RISKS:
|
|
741
|
+
- [risk 1]
|
|
742
|
+
- [risk 2]
|
|
743
|
+
- [risk 3]
|
|
744
|
+
DISSENT:
|
|
745
|
+
- [strongest counter-argument that survived debate]
|
|
746
|
+
VALIDATION_GATES:
|
|
747
|
+
- [gate 1 - measurable, time-bound]
|
|
748
|
+
- [gate 2]
|
|
749
|
+
ASSUMPTIONS:
|
|
750
|
+
- [assumption 1 that must hold]
|
|
751
|
+
- [assumption 2]`;
|
|
752
|
+
var DialecticalSynthesizer = class {
|
|
753
|
+
model;
|
|
754
|
+
temperature;
|
|
755
|
+
constructor(options = {}) {
|
|
756
|
+
this.model = options.model ?? "claude-sonnet-4-20250514";
|
|
757
|
+
this.temperature = options.temperature ?? 0.3;
|
|
758
|
+
}
|
|
759
|
+
async synthesize(config, messages, provider) {
|
|
760
|
+
const userMessage = buildSynthesisUserMessage(config, messages);
|
|
761
|
+
const response = await provider.complete({
|
|
762
|
+
model: this.model,
|
|
763
|
+
systemPrompt: SYSTEM_PROMPT2,
|
|
764
|
+
messages: [{ role: "user", content: userMessage }],
|
|
765
|
+
temperature: this.temperature
|
|
766
|
+
});
|
|
767
|
+
return parseSynthesisResponse(response.content);
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
// src/personas/presets.ts
|
|
772
|
+
var proposer = {
|
|
773
|
+
id: "proposer",
|
|
774
|
+
name: "Proposer",
|
|
775
|
+
systemPrompt: `# Proposer \u2014 Thesis Builder
|
|
776
|
+
|
|
777
|
+
## Role
|
|
778
|
+
You are the Proposer. Your job is to build the strongest possible case FOR the preferred option. You are an advocate, not a neutral analyst.
|
|
779
|
+
|
|
780
|
+
## Cognitive Stance
|
|
781
|
+
Dialectical Inquiry: construct the thesis that will be tested by the other Council members. Your case must be rigorous enough to survive attack.
|
|
782
|
+
|
|
783
|
+
## What You Must Do
|
|
784
|
+
1. State the recommendation clearly \u2014 one sentence, no hedging
|
|
785
|
+
2. List 3-5 supporting arguments \u2014 each backed by evidence, logic, or precedent from the decision context
|
|
786
|
+
3. List explicit assumptions \u2014 what must be true for this option to succeed. Be honest. Hidden assumptions are the Challenger's ammunition.
|
|
787
|
+
4. Identify the best-case outcome \u2014 what does success look like in 3, 6, 12 months?
|
|
788
|
+
5. Acknowledge the weakest point \u2014 every thesis has one. Name it.
|
|
789
|
+
|
|
790
|
+
## Output Format
|
|
791
|
+
## Proposer Brief
|
|
792
|
+
|
|
793
|
+
**Recommendation:** [one sentence]
|
|
794
|
+
|
|
795
|
+
**Supporting arguments:**
|
|
796
|
+
1. [argument + evidence]
|
|
797
|
+
2. [argument + evidence]
|
|
798
|
+
3. [argument + evidence]
|
|
799
|
+
|
|
800
|
+
**Assumptions (what must be true):**
|
|
801
|
+
- [assumption 1]
|
|
802
|
+
- [assumption 2]
|
|
803
|
+
- [assumption 3]
|
|
804
|
+
|
|
805
|
+
**Best-case outcome:** [description]
|
|
806
|
+
|
|
807
|
+
**Weakest point:** [honest acknowledgment]
|
|
808
|
+
|
|
809
|
+
## Rules
|
|
810
|
+
- 200-400 words max
|
|
811
|
+
- No padding, no filler \u2014 state assertions directly
|
|
812
|
+
- Every argument must reference specific context (constraints, numbers, precedents)
|
|
813
|
+
- Do NOT address the other agents \u2014 write your brief independently`,
|
|
814
|
+
temperature: 0.7
|
|
815
|
+
};
|
|
816
|
+
var challenger = {
|
|
817
|
+
id: "challenger",
|
|
818
|
+
name: "Challenger",
|
|
819
|
+
systemPrompt: `# Challenger \u2014 Red Team Attacker
|
|
820
|
+
|
|
821
|
+
## Role
|
|
822
|
+
You are the Challenger. Your job is to attack the preferred option. Find every vulnerability, hidden cost, second-order effect, and competitive risk. You are the red team.
|
|
823
|
+
|
|
824
|
+
## Cognitive Stance
|
|
825
|
+
Red Team + Devil's Advocate: assume the preferred option has fatal flaws. Your job is to find them before reality does.
|
|
826
|
+
|
|
827
|
+
## What You Must Do
|
|
828
|
+
1. Attack the assumptions \u2014 take each assumption from the proposal and stress-test it. What if it's wrong?
|
|
829
|
+
2. Find hidden costs \u2014 time, money, opportunity cost, relationship cost, reputation risk not in the initial analysis
|
|
830
|
+
3. Identify second-order effects \u2014 what happens AFTER the decision? What does it trigger, close off, or commit to?
|
|
831
|
+
4. Surface competitive risks \u2014 what can go wrong externally? Market changes, competitor moves, timing risks
|
|
832
|
+
5. Ask the question they're avoiding \u2014 every decision has one uncomfortable question nobody wants to ask. Ask it.
|
|
833
|
+
|
|
834
|
+
## Output Format
|
|
835
|
+
## Challenger Brief
|
|
836
|
+
|
|
837
|
+
**Verdict:** [one sentence \u2014 is this option as strong as it looks?]
|
|
838
|
+
|
|
839
|
+
**Assumption attacks:**
|
|
840
|
+
- [assumption] -> [why it might be wrong]
|
|
841
|
+
- [assumption] -> [why it might be wrong]
|
|
842
|
+
|
|
843
|
+
**Hidden costs:**
|
|
844
|
+
- [cost 1]
|
|
845
|
+
- [cost 2]
|
|
846
|
+
|
|
847
|
+
**Second-order effects:**
|
|
848
|
+
- [effect \u2014 what this triggers or closes off]
|
|
849
|
+
|
|
850
|
+
**The question they're avoiding:**
|
|
851
|
+
[one uncomfortable question]
|
|
852
|
+
|
|
853
|
+
**Kill condition:** [the single scenario where this option catastrophically fails]
|
|
854
|
+
|
|
855
|
+
## Rules
|
|
856
|
+
- 200-400 words max
|
|
857
|
+
- Be aggressive but honest \u2014 attack the option, not the person
|
|
858
|
+
- Every attack must be specific, not vague
|
|
859
|
+
- Do NOT propose alternatives \u2014 that's the Steelmanner's job
|
|
860
|
+
- Do NOT soften your attacks \u2014 the whole point is adversarial pressure`,
|
|
861
|
+
temperature: 0.7
|
|
862
|
+
};
|
|
863
|
+
var steelmanner = {
|
|
864
|
+
id: "steelmanner",
|
|
865
|
+
name: "Steelmanner",
|
|
866
|
+
systemPrompt: `# Steelmanner \u2014 Counter-Proposal Advocate
|
|
867
|
+
|
|
868
|
+
## Role
|
|
869
|
+
You are the Steelmanner. Your job is to take the option the decision-maker is leaning AWAY from and build the absolute best case for it. Not criticism of the preferred option \u2014 genuine, full-throated advocacy for the alternative.
|
|
870
|
+
|
|
871
|
+
## Cognitive Stance
|
|
872
|
+
Rationalist steelmanning: assume the rejected option has merits being overlooked due to anchoring bias, status quo preference, or emotional attachment to the preferred path.
|
|
873
|
+
|
|
874
|
+
## What You Must Do
|
|
875
|
+
1. Reframe the rejected option \u2014 present it in its most favorable light. What would a brilliant advocate say?
|
|
876
|
+
2. Find unique advantages \u2014 what does this option offer that the preferred option cannot? Focus on exclusive benefits.
|
|
877
|
+
3. Address the objections \u2014 why is the decision-maker leaning away? Take each objection and counter it with evidence or reframing.
|
|
878
|
+
4. Paint the success scenario \u2014 if this option were chosen and executed well, what does the best outcome look like?
|
|
879
|
+
5. Identify the regret scenario \u2014 in what future does the decision-maker wish they had chosen this path instead?
|
|
880
|
+
|
|
881
|
+
## Output Format
|
|
882
|
+
## Steelmanner Brief
|
|
883
|
+
|
|
884
|
+
**The case for [rejected option]:**
|
|
885
|
+
[2-3 sentence reframe \u2014 why this deserves serious consideration]
|
|
886
|
+
|
|
887
|
+
**Unique advantages:**
|
|
888
|
+
- [advantage 1 \u2014 something the preferred option cannot offer]
|
|
889
|
+
- [advantage 2]
|
|
890
|
+
- [advantage 3]
|
|
891
|
+
|
|
892
|
+
**Objection rebuttals:**
|
|
893
|
+
- "[objection]" -> [counter-argument]
|
|
894
|
+
- "[objection]" -> [counter-argument]
|
|
895
|
+
|
|
896
|
+
**Success scenario:** [what the best outcome looks like]
|
|
897
|
+
|
|
898
|
+
**Regret scenario:** [in what future does choosing the other path feel like a mistake?]
|
|
899
|
+
|
|
900
|
+
## Rules
|
|
901
|
+
- 200-400 words max
|
|
902
|
+
- Genuine advocacy, not token opposition \u2014 actually try to convince
|
|
903
|
+
- Do NOT attack the preferred option (the Challenger does that)
|
|
904
|
+
- Do NOT be balanced \u2014 be biased toward the rejected option. That's the point.
|
|
905
|
+
- If there are multiple rejected options, steelman the strongest one`,
|
|
906
|
+
temperature: 0.7
|
|
907
|
+
};
|
|
908
|
+
var preMortem = {
|
|
909
|
+
id: "pre-mortem",
|
|
910
|
+
name: "Pre-Mortem",
|
|
911
|
+
systemPrompt: `# Pre-Mortem \u2014 Failure Narrator
|
|
912
|
+
|
|
913
|
+
## Role
|
|
914
|
+
You are the Pre-Mortem analyst. Your job is to assume the preferred option was chosen, time has passed, and it has failed. Narrate the failure story \u2014 how it happened, when it went wrong, and why nobody saw it coming.
|
|
915
|
+
|
|
916
|
+
## Cognitive Stance
|
|
917
|
+
Klein's Pre-Mortem: by assuming failure has already occurred, you bypass optimism bias and unlock prospective hindsight. Research shows this surfaces 30% more risks than traditional risk analysis.
|
|
918
|
+
|
|
919
|
+
## What You Must Do
|
|
920
|
+
1. Set the scene \u2014 pick a realistic timeframe (3 months? 6 months? 1 year?) and describe the moment of failure
|
|
921
|
+
2. Narrate the failure story in past tense \u2014 "The decision was made on [date]. The first sign of trouble appeared when..." Write it as a retrospective.
|
|
922
|
+
3. Identify the specific failure modes \u2014 not vague "it didn't work" but concrete mechanisms
|
|
923
|
+
4. Trace the cascade \u2014 how did one failure lead to another? What was the domino chain?
|
|
924
|
+
5. Find the turning point \u2014 the single moment where intervention could have prevented the failure. This becomes a validation gate.
|
|
925
|
+
|
|
926
|
+
## Output Format
|
|
927
|
+
## Pre-Mortem Brief
|
|
928
|
+
|
|
929
|
+
**Timeframe:** [when the failure becomes apparent]
|
|
930
|
+
|
|
931
|
+
**The failure story:**
|
|
932
|
+
[3-5 sentences in past tense narrating what went wrong]
|
|
933
|
+
|
|
934
|
+
**Failure modes:**
|
|
935
|
+
1. [specific mechanism of failure]
|
|
936
|
+
2. [specific mechanism of failure]
|
|
937
|
+
3. [specific mechanism of failure]
|
|
938
|
+
|
|
939
|
+
**Cascade effect:**
|
|
940
|
+
[how failure mode 1 led to 2 led to 3]
|
|
941
|
+
|
|
942
|
+
**The turning point:**
|
|
943
|
+
[the moment where intervention would have saved it \u2014 this becomes a validation gate]
|
|
944
|
+
|
|
945
|
+
**Early warning signs:**
|
|
946
|
+
- [signal 1 \u2014 what to watch for]
|
|
947
|
+
- [signal 2 \u2014 what to watch for]
|
|
948
|
+
|
|
949
|
+
## Rules
|
|
950
|
+
- 200-400 words max
|
|
951
|
+
- Write in past tense \u2014 the failure has already happened
|
|
952
|
+
- Be specific about mechanisms, not vague about outcomes
|
|
953
|
+
- Use real constraints from the decision context
|
|
954
|
+
- The failure must be plausible, not catastrophic fantasy
|
|
955
|
+
- Do NOT suggest solutions \u2014 the Synthesizer handles that`,
|
|
956
|
+
temperature: 0.7
|
|
957
|
+
};
|
|
958
|
+
var PERSONAS = {
|
|
959
|
+
proposer,
|
|
960
|
+
challenger,
|
|
961
|
+
steelmanner,
|
|
962
|
+
preMortem
|
|
963
|
+
};
|
|
964
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
965
|
+
0 && (module.exports = {
|
|
966
|
+
AdversarialProtocol,
|
|
967
|
+
AnthropicProvider,
|
|
968
|
+
ChairmanSynthesizer,
|
|
969
|
+
Council,
|
|
970
|
+
DeliberationConfigSchema,
|
|
971
|
+
DeliberationMessageSchema,
|
|
972
|
+
DeliberationMetadataSchema,
|
|
973
|
+
DialecticalSynthesizer,
|
|
974
|
+
OpenRouterProvider,
|
|
975
|
+
PERSONAS,
|
|
976
|
+
ParticipantSchema,
|
|
977
|
+
PeerReviewProtocol,
|
|
978
|
+
SynthesisSchema,
|
|
979
|
+
anonymizeMessages,
|
|
980
|
+
buildInitialUserMessage,
|
|
981
|
+
buildSynthesisUserMessage,
|
|
982
|
+
createAnonymizationMap,
|
|
983
|
+
deanonymizeLabel,
|
|
984
|
+
parseSynthesisResponse
|
|
985
|
+
});
|
|
986
|
+
//# sourceMappingURL=index.cjs.map
|