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

type ColumnDef = {
  name: string;
  agent: AgentLike;
  output: OutputTarget;
  prompt?: (ctx: { item: unknown; column: string }) => string;
  task?: Omit<Partial<TaskProps>, "agent" | "children" | "id" | "key" | "output" | "smithersContext">;  // retries, timeoutMs, etc.
};

type KanbanProps = {
  id?: string;                                       // default: "kanban"
  columns: ColumnDef[];
  useTickets: () => Array<{ id: string; [key: string]: unknown }>;
  agents?: Record<string, AgentLike>;                // overrides column-level agents
  maxConcurrency?: number;                           // default: unlimited (no cap), per column
  onComplete?: OutputTarget;
  until?: boolean;                                   // default: false
  maxIterations?: number;                            // default: 5
  skipIf?: boolean;
  children?: ReactNode | Record<string, unknown>;    // content for onComplete task
};
const columns = [
  { name: "triage", agent: triageAgent, output: outputs.triage },
  { name: "work", agent: workerAgent, output: outputs.work },
  { name: "review", agent: reviewAgent, output: outputs.review },
];

<Workflow name="ticket-board">
  <Kanban
    columns={columns}
    useTickets={() => tickets}
    until={allDone}
    maxIterations={3}
  />
</Workflow>;

Notes

  • Item tasks default to continueOnFail={true}; use column.task to add retries or override.
  • useTickets is called at render time; return different items per iteration for dynamic sources.
  • Use until with ctx.outputMaybe() to exit when all items reach the final column.

Source

The <Kanban> 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("./ColumnDef.ts").ColumnDef} ColumnDef */
/** @typedef {import("./KanbanProps.ts").KanbanProps} KanbanProps */
// @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";
/**
 * <Kanban> — Process items through columns with pluggable ticket source.
 *
 * Composes Loop, Sequence, Parallel, and Task to create a board where items
 * flow through columns. Each column processes items via its assigned agent.
 * Items in the same column can be processed in parallel.
 * @param {KanbanProps} props
 */
export function Kanban(props) {
    if (props.skipIf)
        return null;
    const { id, columns, useTickets, agents, maxConcurrency, onComplete, until = false, maxIterations = 5, children, } = props;
    const prefix = id ?? "kanban";
    const tickets = useTickets();
    // Build a Sequence of columns. Each column processes all tickets in Parallel.
    const columnElements = columns.map((col, colIdx) => {
        const agent = agents?.[col.name] ?? col.agent;
        const taskElements = tickets.map((item) => {
            const taskId = `${prefix}-${col.name}-${item.id}`;
            const taskProps = col.task ?? {};
            const prompt = col.prompt
                ? col.prompt({ item, column: col.name })
                : `Process item ${item.id} in column "${col.name}".`;
            return React.createElement(Task, {
                ...taskProps,
                key: `${col.name}-${item.id}`,
                id: taskId,
                output: col.output,
                agent,
                continueOnFail: taskProps.continueOnFail ?? true,
                label: taskProps.label ?? `${col.name}: ${item.id}`,
                children: prompt,
            });
        });
        return React.createElement(Parallel, {
            key: `col-${colIdx}-${col.name}`,
            id: `${prefix}-col-${col.name}`,
            maxConcurrency,
        }, ...taskElements);
    });
    const sequence = React.createElement(Sequence, null, ...columnElements);
    const loop = React.createElement(Loop, {
        id: `${prefix}-loop`,
        until,
        maxIterations,
        onMaxReached: "return-last",
    }, sequence);
    if (!onComplete) {
        return loop;
    }
    return React.createElement(Sequence, null, loop, React.createElement(Task, {
        key: `${prefix}-complete`,
        id: `${prefix}-complete`,
        output: onComplete,
        label: "Board complete",
        children: children ?? null,
    }));
}