Skip to content
xiv

Workflows

A workflow is a durable, resumable agent pipeline that a single xiv command launches and the Smithers engine executes. This page explains what a workflow is in this repo, how the metadata header works, how CLI commands map onto workflows, and how workflows compose.

What a workflow is here

Workflows live in pack/workflows/*.tsx. Each one is a Smithers-orchestrator JSX graph: a render function that returns a <Workflow> element built from <Task>, <Sequence>, and <Loop> elements, wired together with typed outputs.

const { Workflow, Task, Sequence, smithers } = createSmithers({
  input: inputSchema,        // zod schema for the workflow's input
  issue: linearIssueSchema,  // one named, typed output table per step result
  output: finalizeSchema,    // the workflow's primary result
});
 
export default smithers((ctx) => (
  <Workflow name="linear-implement">
    <Sequence>
      <Task id="fetch-issue" output={linearIssueSchema} agent={...}>...</Task>
      <Loop id="impl:loop" until={done} maxIterations={3} onMaxReached="return-last">...</Loop>
    </Sequence>
  </Workflow>
));

The key ideas:

  • createSmithers declares typed tables. Every input and every step output is a zod schema. An agent whose output does not parse fails its <Task> instead of silently corrupting downstream state.
  • Tasks come in two kinds. A <Task> with an agent prop launches a coding agent against a prompt (the .mdx files in pack/prompts/); a <Task> whose child is a plain async function is deterministic engine code — no model, no cost, no nondeterminism. Gates, stack-map writes, and result composition are all deterministic tasks.
  • Control flow is data-driven. The render function re-runs as outputs land; reads like ctx.outputMaybe(...) and ctx.latest(...) feed loop exit conditions, skipIf props, and prompt text. A <Loop until={done}> exits when its condition computes true, and onMaxReached="return-last" means hitting the iteration cap returns the last attempt rather than failing the run.
  • Runs are durable. State lives in SMITHERS_HOME; a crashed run resumes from its recorded outputs, and each workflow executes against the repo you invoked xiv from (SMITHERS_TARGET_CWD).

The smithers-* metadata header

Every workflow file starts with a comment block the engine and CLI read as metadata:

// smithers-source: authored
// smithers-metadata-version: 1
// smithers-display-name: Linear Implement
// smithers-description: Take a Linear issue, plan it, implement it, and review ...
// smithers-tags: linear, coding, review
// smithers-aliases: li

display-name and description are what listings show; tags group workflows by domain; and aliases register a short name for each workflow with the Smithers engine (the full table is at the bottom of this page). The xiv commands themselves always address workflows by their full name.

How CLI commands map to workflows

Each user-facing command resolves to exactly one workflow and hands it a typed input:

CommandWorkflowPage
xiv implement <issueId>linear-implement/workflows/linear-implement
xiv ship <issueId>linear-to-pr/workflows/linear-to-pr
xiv pr refine [prNumber]pr-review-loop/workflows/pr-review-loop
xiv pr fix [prNumber]pr-fix/workflows/pr-fix
xiv stack plan / build / push / review / amendstack-planstack-amend/workflows/stack-workflows

xiv pr review is the exception: it is an interactive CLI flow, not a workflow — though it parses its review agent's output against the same localReviewSchema the implement loop uses (see /concepts/review-model).

For authoring and fast iteration, xiv check <workflow> renders a graph without running agents and xiv dev <workflow> runs it live from the in-repo pack/ — see /commands/authoring.

Composition: workflows as subflows

Workflows nest. linear-to-pr is nothing but two subflows — linear-implement followed by pr-review-loop — and stack-build runs one linear-implement subflow per stack entry. The SubflowLoose component (in pack/components/) embeds a child workflow as a step and reads the child's result.

One contract makes this work: a workflow's primary result table must be named exactly output. The engine populates the run result from the table with that name and no other; a subflow consuming a child whose result table is named anything else receives undefined and fails INVALID_OUTPUT validation. test/workflow-contract.test.ts guards this for the composed workflows.

Alias table

WorkflowAlias
linear-implementli
linear-to-prl2pr
pr-fixprf
pr-review-loopprl
stack-plansp
stack-buildsb
stack-pushspush
stack-reviewsr
stack-amendsa

Shared conventions

  • Never merge. Every workflow that can touch GitHub carries a hard system-prompt rule against merging (gh pr merge, auto-merge, or otherwise). Merging is a human's job, always.
  • Review happens locally, before push. Once a PR exists, workflows read only CI status and human comments — never a review bot. See /concepts/review-model.
  • Model assignment is per-task. Mechanical steps run cheap models; reasoning-heavy steps run capable ones, tiered by XIV_TIER. See /concepts/model-tiers.