V4.1.3 rewrites the /debate orchestrator from a flat 3-round loop into a structured
two-act format with genuine mid-debate feedback. Nando now delivers an interim verdict after
opening statements — naming the current leader and calling out weak arguments — and agents
respond directly to that assessment in their rebuttals. Nando is explicitly allowed to reverse
on strong rebuttals: the final verdict opens with either “VERDICT CHANGED:” or
“VERDICT STANDS:” so the outcome is never predetermined. Six security and
reliability fixes were applied to the orchestrator, and the dashboard now clears correctly
between debate runs.
-
2-round structured flow
The flat 3-round loop is replaced with a deliberate two-act structure: Round 1 (opening statements) → Nando interim verdict → Round 2 (rebuttals) → Nando final verdict. Each phase is meaningfully distinct — agents in Round 2 have Nando's explicit assessment to argue against, producing sharper back-and-forth than a symmetric loop.
-
Nando interim verdict — named leader, called-out weaknesses
After Round 1, Nando delivers a structured interim verdict: identifies the current leader, explains why, and explicitly names the weakest arguments by agent. This becomes part of the transcript that Round 2 agents receive, so rebuttals are targeted rather than generic. Nando's interim prompt specifies 2–4 sentences, no final decision yet.
-
Verdict reversal allowed — VERDICT CHANGED / VERDICT STANDS
Nando's final verdict prompt explicitly permits reversing the interim call if a rebuttal was genuinely more compelling. The final verdict must open with either
VERDICT CHANGED: (naming the agent whose rebuttal shifted the position and why) or VERDICT STANDS: (explaining what the rebuttals failed to overcome). This prevents the debate from being a formality where the Round 1 leader always wins.
-
Server history cleared at debate start
debate.js now calls POST /clear on the dashboard server as the very first step — before position assignment, before any agents connect. This means a browser refresh at any point during or after a debate always shows only the current run. Previously, old messages from a prior session would persist until the WebSocket /clear command was sent mid-run.
-
execSync → spawnSync with argument array
The
claude -p call was previously built with shell string interpolation via execSync, requiring manual quote-escaping of the prompt. Replaced with spawnSync('claude', ['-p', prompt, '--output-format', 'text']) — no shell involved, no injection surface, no escaping needed.
-
Parallel agent connections via Promise.all
Agents were previously connected sequentially in a
for loop, each waiting for the previous connection to complete before starting the next. All six connections now open in parallel via Promise.all, reducing connect time from ~6× single-connection latency to ~1×.
-
10-second connect timeout with hard reject
Added
CONNECT_TIMEOUT_MS = 10_000. connectAgent now sets a setTimeout that calls reject(new Error(...)) if the server does not acknowledge within 10 seconds, rather than hanging indefinitely on a missing or slow server.
-
ws.once instead of ws.on for ack handler
The acknowledgement handler in
connectAgent was registered with ws.on('message'), which would fire on every subsequent message including debate content. Changed to ws.once('message') so the handler fires exactly once and is automatically removed after the ack is processed.
-
Debate config written with 0o600 permissions
/tmp/agent-chat-debate.json contains position assignments generated from the debate topic. Now written with { mode: 0o600 } so only the owning user can read it — no world-readable exposure of session data in a shared /tmp.
-
post() delay reduced from 900ms to 100ms
The artificial delay between posted messages was 900ms — a leftover default that extended a 5-agent debate by ~45 unnecessary seconds. Reduced to 100ms: enough for the server to process and broadcast each message before the next arrives, without visible lag in the dashboard.
-
POST /clear HTTP endpoint
Added
POST /clear to the dashboard HTTP server (port 4001). Clears messageHistory in memory and broadcasts a history-cleared lifecycle event to all connected dashboard clients. Called by debate.js at startup, but also available as a standalone HTTP call for tooling or future integrations.
-
Dashboard handles history-cleared event
The dashboard WebSocket handler now responds to
{ type: 'lifecycle', event: 'history-cleared' } by calling clearMessages() — wiping the message list and restoring the "Waiting for messages…" empty state. Previously the event was broadcast but ignored by the browser, leaving stale messages on screen.
-
renderHistory clears before rendering
renderHistory() (called on init — browser connect or reconnect) now calls clearMessages() before rendering the history array. This prevents message duplication when the browser reconnects after a server restart or WebSocket drop.