← back to Whatsmystyle
scripts/debate.js
87 lines
/**
* Dream-team debate harness — wraps the local Ollama panel for design decisions.
*
* Usage:
* node scripts/debate.js "topic" "context blob"
*
* Fires three local LLMs (qwen3:14b, deepseek-r1:14b, phi4:14b) and asks each
* to defend a position, then writes the panel + a synthesized verdict to the
* SQLite `debates` table.
*
* Why local-only: Steve's yolo-overnight rule forbids metered LLM cost.
* The CCK / claude-codex relays are reserved for the morning review.
*/
const Database = require('better-sqlite3');
const path = require('path');
const fetch = require('node-fetch');
const OLLAMA = process.env.OLLAMA_URL || 'http://127.0.0.1:11434';
const PANEL = [
{ role: 'DEFENDER', model: process.env.PANEL_DEFENDER || 'qwen3:14b' },
{ role: 'PROSECUTOR',model: process.env.PANEL_PROSECUTOR|| 'deepseek-r1:14b' },
{ role: 'PRAGMATIST',model: process.env.PANEL_PRAGMATIST|| 'phi4:14b' },
];
async function ask(model, system, user) {
try {
const r = await fetch(`${OLLAMA}/api/chat`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
model,
stream: false,
messages: [
{ role: 'system', content: system },
{ role: 'user', content: user },
],
options: { temperature: 0.6 },
}),
});
if (!r.ok) return { error: `HTTP ${r.status}` };
const j = await r.json();
return { content: j.message?.content || j.response || '' };
} catch (e) {
return { error: e.message };
}
}
async function debate(topic, context) {
const transcripts = [];
for (const p of PANEL) {
const sys = `You are the ${p.role} in a design debate about WhatsMyStyle, an AI personal-stylist app.
Be concise (≤ 8 bullets). Identify risks the other two panelists will miss.
End with one line: VERDICT: <ship|hold|modify> — <one sentence>.`;
const usr = `TOPIC: ${topic}\n\nCONTEXT:\n${context}`;
const out = await ask(p.model, sys, usr);
transcripts.push({ role: p.role, model: p.model, output: out.content || `[${out.error}]` });
}
// synth
const synth = await ask(PANEL[0].model,
`You are the synthesizer. Read three panelist transcripts and produce a single VERDICT line plus 3 bullets of agreed actions.`,
transcripts.map(t => `### ${t.role} (${t.model})\n${t.output}`).join('\n\n'));
return { panel: transcripts, verdict: synth.content || '[synth failed]' };
}
async function main() {
const [, , topic, context] = process.argv;
if (!topic) {
console.error('usage: node debate.js "topic" "context"');
process.exit(1);
}
const dbPath = path.join(__dirname, '..', 'data', 'whatsmystyle.db');
const db = new Database(dbPath);
db.exec(`CREATE TABLE IF NOT EXISTS debates (
id INTEGER PRIMARY KEY, topic TEXT NOT NULL, context TEXT, verdict TEXT, panel TEXT,
created_at TEXT DEFAULT (datetime('now'))
)`);
const result = await debate(topic, context || '');
const { lastInsertRowid } = db.prepare(
'INSERT INTO debates (topic, context, verdict, panel) VALUES (?, ?, ?, ?)'
).run(topic, context, result.verdict, JSON.stringify(result.panel));
console.log(JSON.stringify({ id: lastInsertRowid, verdict: result.verdict }, null, 2));
}
if (require.main === module) main().catch(e => { console.error(e); process.exit(1); });
module.exports = { debate };