// Props
import { Debate } from "smthrs";
type DebateProps = {
id?: string; // default: "debate"
proposer: AgentLike; // arguing FOR
opponent: AgentLike; // arguing AGAINST
judge: AgentLike; // renders final verdict
rounds?: number; // default: 2
argumentOutput: OutputTarget;
verdictOutput: OutputTarget;
topic: string | ReactNode;
skipIf?: boolean;
};
<Workflow name="architecture-debate">
<Debate
proposer={monolithAdvocate}
opponent={microservicesAdvocate}
judge={architectureJudge}
rounds={3}
argumentOutput={outputs.argument}
verdictOutput={outputs.verdict}
topic="Should we migrate from a monolith to microservices for the payments system?"
/>
</Workflow>
Notes
- Task ids:
{prefix}-proposer,{prefix}-opponent,{prefix}-judge, loop{prefix}-loop. - Loop runs exactly
roundsiterations,onMaxReached="return-last". - Proposer and opponent share
argumentOutput, differentiated by task id.
Source
The<Debate> implementation and the files it imports, straight from the package source. This section is generated; edit the source, not this block.
// @smithers-type-exports-begin
/** @typedef {import("./DebateProps.ts").DebateProps} DebateProps */
// @smithers-type-exports-end
import React from "react";
import { Sequence } from "./Sequence.js";
import { Parallel } from "./Parallel.js";
import { Loop } from "./Ralph.js";
import { Task } from "./Task.js";
import { useOptionalSmithersContext } from "./useOptionalSmithersContext.js";
/**
* <Debate> — Adversarial rounds with rebuttals, followed by a judge verdict.
*
* Composes: Sequence > Loop[Parallel(proposer, opponent)] > Task(judge)
* @param {DebateProps} props
*/
export function Debate(props) {
if (props.skipIf) return null;
const { id, proposer, opponent, judge, rounds = 2, argumentOutput, verdictOutput, topic } = props;
const prefix = id ?? "debate";
const ctx = useOptionalSmithersContext();
// Build round tasks inside a loop
// Each round: proposer and opponent argue in parallel
const proposerTask = React.createElement(Task, {
id: `${prefix}-proposer`,
output: argumentOutput,
agent: proposer,
label: "Proposer",
children: React.createElement(React.Fragment, null, "Argue FOR the following topic:\n\n", topic),
});
const opponentTask = React.createElement(Task, {
id: `${prefix}-opponent`,
output: argumentOutput,
agent: opponent,
label: "Opponent",
children: React.createElement(React.Fragment, null, "Argue AGAINST the following topic:\n\n", topic),
});
const roundParallel = React.createElement(Parallel, null, proposerTask, opponentTask);
const roundSequence = React.createElement(Sequence, null, roundParallel);
// Loop wraps the round sequence. `until` stays false: a Debate runs a fixed
// number of adversarial rounds (capped by maxIterations), with no early-exit
// condition — the judge rules once all rounds are done.
const loopEl = React.createElement(
Loop,
{
id: `${prefix}-loop`,
until: false,
maxIterations: rounds,
onMaxReached: "return-last",
},
roundSequence,
);
// Judge verdict after all rounds. The proposer/opponent arguments live inside
// the loop, so fold the most recent round's outputs into the judge's prompt
// via `latest` (the reader that resolves the newest iteration's rows); `needs`
// alone is cache-context only and injects no argument text.
const judgeNeeds = {
[`${prefix}-proposer`]: `${prefix}-proposer`,
[`${prefix}-opponent`]: `${prefix}-opponent`,
};
const latestProposer = ctx?.latest?.(argumentOutput, `${prefix}-proposer`);
const latestOpponent = ctx?.latest?.(argumentOutput, `${prefix}-opponent`);
const judgeTask = React.createElement(Task, {
id: `${prefix}-judge`,
output: verdictOutput,
agent: judge,
needs: judgeNeeds,
label: "Judge",
children: () =>
React.createElement(
React.Fragment,
null,
"Review all arguments from both sides and render a verdict on:\n\n",
topic,
"\n\n## Proposer's arguments\n",
latestProposer ? JSON.stringify(latestProposer) : "(no arguments)",
"\n\n## Opponent's arguments\n",
latestOpponent ? JSON.stringify(latestOpponent) : "(no arguments)",
),
});
return React.createElement(Sequence, null, loopEl, judgeTask);
}
import React from "react";
/** @typedef {import("./SequenceProps.ts").SequenceProps} SequenceProps */
/**
* @param {SequenceProps} props
*/
export function Sequence(props) {
if (props.skipIf) return null;
// Sequence carries only a display label; pass a sanitized bag (align with
// the sanitizing structural components) so control props don't leak through.
// `label` names the phase group in run views (graph, the Claude /workflows
// mirror) and is preserved in the persisted frame XML.
const next = {
...(props.label === undefined ? {} : { label: props.label }),
...(props.failurePolicy === undefined ? {} : { failurePolicy: props.failurePolicy }),
};
return React.createElement("smithers:sequence", next, props.children);
}
import React from "react";
/** @typedef {import("./ParallelProps.ts").ParallelProps} ParallelProps */
/**
* @param {ParallelProps} props
*/
export function Parallel(props) {
if (props.skipIf) return null;
// Align prop sanitization with other structural components. `label` names
// the phase group in run views (graph, the Claude /workflows mirror).
const next = {
maxConcurrency: props.maxConcurrency,
subtreeConcurrency: props.subtreeConcurrency,
id: props.id,
...(props.label === undefined ? {} : { label: props.label }),
...(props.priority === undefined ? {} : { priority: props.priority }),
...(props.failurePolicy === undefined ? {} : { failurePolicy: props.failurePolicy }),
};
return React.createElement("smithers:parallel", next, props.children);
}
// @smithers-type-exports-begin
/** @typedef {import("./RalphProps.ts").RalphProps} RalphProps */
// @smithers-type-exports-end
import React from "react";
/** @typedef {import("./LoopProps.ts").LoopProps} LoopProps */
/**
* @param {LoopProps} props
*/
export function Loop(props) {
if (props.skipIf) return null;
// Sanitize to the loop's host props (align with other structural components);
// key/skipIf are React/control props and children are passed separately.
const next = {
id: props.id,
until: props.until,
maxIterations: props.maxIterations,
onMaxReached: props.onMaxReached,
continueAsNewEvery: props.continueAsNewEvery,
};
return React.createElement("smithers:ralph", next, props.children);
}
/** @deprecated Use `Loop` instead. */
export const Ralph = Loop;
// @smithers-type-exports-begin
/**
* @template D
* @typedef {import("./InferDeps.ts").InferDeps<D>} InferDeps
*/
/** @typedef {import("./OutputTarget.ts").OutputTarget} OutputTarget */
// @smithers-type-exports-end
import { applyCliToolAllowlist } from "./cliToolAllowlist.js";
import { createTaskComponent } from "./taskCore.js";
export { renderPromptToText } from "./taskCore.js";
/**
* The Node/CLI-agent-aware `Task`. Every render-path behavior (deps
* resolution, agent-chain assembly, MDX prompt rendering, static/compute/agent
* branching) lives in `taskCore.js`; this file only supplies the CLI-agent
* tool-allowlist enforcement step (`applyCliToolAllowlist`, which statically
* imports `ClaudeCodeAgent`/`PiAgent`/`GeminiAgent`/`AntigravityAgent` — see
* `cliToolAllowlist.js`). `Task.browser.js` builds the same component with a
* no-op allowlist step instead, so it never pulls those Node-only
* (`node:child_process`-backed) classes into a browser bundle.
*/
export const Task = createTaskComponent({ applyCliToolAllowlist });
import React from "react";
import { SmithersContext } from "@smthrs/react-reconciler/context";
/**
* Read the workflow context when React is rendering the component, but allow
* direct structural expansion tests to call composite wrappers as plain
* functions. In that direct-call path React throws the standard invalid hook
* error; treating it as "no context yet" preserves the static element shape.
*/
export function useOptionalSmithersContext() {
try {
return React.useContext(SmithersContext);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (/Invalid hook call|dispatcher\.useContext|useContext/i.test(message)) {
return null;
}
throw error;
}
}
import type React from "react";
import type { AgentLike } from "@smthrs/agents/AgentLike";
import type { OutputTarget } from "./OutputTarget.ts";
export type DebateProps = {
id?: string;
proposer: AgentLike;
opponent: AgentLike;
judge: AgentLike;
rounds?: number;
argumentOutput: OutputTarget;
verdictOutput: OutputTarget;
topic: string | React.ReactNode;
skipIf?: boolean;
};