Skip to main content
// Props
import { ReviewLoop } from "smithers-orchestrator";

type ReviewLoopProps = {
  id?: string; // default "review-loop"; task ids derived as {id}-produce, {id}-review
  producer: AgentLike;
  reviewer: AgentLike | AgentLike[];
  produceOutput: OutputTarget;
  reviewOutput: OutputTarget; // must include `approved: boolean`
  maxIterations?: number; // default 5
  onMaxReached?: "return-last" | "fail"; // default "return-last"
  skipIf?: boolean;
  children: string | ReactNode; // initial producer prompt
};
export default smithers(() => (
  <Workflow name="code-review">
    <ReviewLoop
      producer={coder}
      reviewer={reviewer}
      produceOutput={outputs.code}
      reviewOutput={outputs.review}
      maxIterations={3}
    >
      Implement a REST API for user authentication with JWT tokens.
    </ReviewLoop>
  </Workflow>
));

Notes

  • The runtime reads approved each frame to decide whether to loop.
  • On subsequent iterations the producer is re-run, but the loop does not wire reviewer output into the producer via a needs dependency. Any feedback the producer sees comes from the runtime/agent conversation history carrying prior outputs, not from the component’s declared data flow.

Source

The <ReviewLoop> 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("./ReviewLoopProps.ts").ReviewLoopProps} ReviewLoopProps */
// @smithers-type-exports-end

import React from "react";
import { Loop } from "./Ralph.js";
import { Sequence } from "./Sequence.js";
import { Task } from "./Task.js";
/**
 * Produce -> review -> fix -> repeat until approved.
 *
 * Composes Loop, Sequence, and Task to create a standard
 * review-loop pattern. The producer receives the reviewer's
 * feedback on subsequent iterations.
 * @param {ReviewLoopProps} props
 */
export function ReviewLoop(props) {
    if (props.skipIf)
        return null;
    const { id, producer, reviewer, produceOutput, reviewOutput, maxIterations = 5, onMaxReached = "return-last", children, } = props;
    const prefix = id ?? "review-loop";
    const produceId = `${prefix}-produce`;
    const reviewId = `${prefix}-review`;
    // The Loop's `until` condition is always false here — at render time
    // the runtime re-renders and re-evaluates. The composite defers the
    // `until` condition to the host element which reads `reviewOutput`
    // for the `approved` field.
    //
    // We pass `until={false}` because the condition is evaluated by the
    // runtime reading the review output's `approved` field each frame.
    // The Loop primitive re-renders the tree, and the runtime checks
    // the review output to decide whether to continue.
    const reviewerAgents = Array.isArray(reviewer) ? reviewer : [reviewer];
    return React.createElement(Loop, {
        id: prefix,
        until: false,
        maxIterations,
        onMaxReached,
    }, React.createElement(Sequence, null, React.createElement(Task, {
        id: produceId,
        output: produceOutput,
        agent: producer,
        children,
    }), React.createElement(Task, {
        id: reviewId,
        output: reviewOutput,
        agent: reviewerAgents.length === 1 ? reviewerAgents[0] : reviewerAgents,
        needs: { produced: produceId },
        children: `Review the produced work and decide whether to approve.`,
    })));
}