Skip to content
boboddy.dev

Pipeline Graph

This is the same graph view boboddy pipelines studio renders locally over a live pipeline, embedded here over static, hand-authored fixtures instead of a running server. Each fixture below is authored with definePipeline() — the same builder used to write a real pipeline — which compiles down to a PipelineDefinitionSpec. That spec is translated into {nodes, edges} at build time (translateSpecToGraph, @boboddy/pipeline-studio-ui’s "." export), then rendered client-side by the same PipelineGraphView component the studio uses (its "./view" export). Drag to pan, scroll to zoom, and use the controls in the bottom-left corner to fit or reset the view.

These examples cover node kinds worth seeing on a graph — a plain linear chain and a choice branch so far, with fanOut, parallel, and loop examples still to come.

The simplest shape: a sequence of steps with no branching, ending in a terminal succeed node.

Triage a bug report

Reads the report, analyzes its severity, and writes a summary.

View fixture source
import { z } from "zod";
import { defineStep } from "@boboddy/sdk/definitions/steps";
import { definePipeline } from "@boboddy/sdk/definitions/pipelines";

/**
 * Linear chain — three sequential steps then a terminal `succeed`. The
 * simplest shape a pipeline graph can take: no branching, no fan-out, no
 * loop. Authored with `definePipeline()` — the same builder real pipelines
 * are written with — rather than the compiled `PipelineDefinitionSpec` wire shape it
 * produces. The steps below are illustrative stand-ins, never pushed or
 * executed (see `docs/research/pipeline-graph-docs-catalog.md` §7).
 */

const readReportStep = defineStep({
  key: "intake-step",
  name: "Read report",
  agentPrompt: "Read the incoming bug report and extract its key details.",
  result: z.object({ reportText: z.string() }),
});

const analyzeSeverityStep = defineStep({
  key: "analyze-step",
  name: "Analyze severity",
  agentPrompt: "Analyze how severe the bug report is.",
  result: z.object({ severity: z.string() }),
});

const writeSummaryStep = defineStep({
  key: "summarize-step",
  name: "Write summary",
  agentPrompt: "Write a short summary of the triage outcome.",
  result: z.object({ summary: z.string() }),
});

export const linearChainSpec = definePipeline({
  key: "catalog-linear-chain",
  name: "Triage a bug report",
  description: "Reads the report, analyzes its severity, and writes a summary.",
  startAt: "intake",
  states: {
    intake: { kind: "step", step: readReportStep, next: "analyze" },
    analyze: { kind: "step", step: analyzeSeverityStep, next: "summarize" },
    summarize: { kind: "step", step: writeSummaryStep, next: "done" },
    done: { kind: "succeed" },
  },
});

A choice node routes to one of several targets based on its choices[] conditions, falling back to default when none match.

Route an incident by severity

Classifies an incident, then pages on-call or files a ticket.

View fixture source
import { z } from "zod";
import { defineStep } from "@boboddy/sdk/definitions/steps";
import { definePipeline, Rule } from "@boboddy/sdk/definitions/pipelines";

/**
 * Choice — one step feeds a routing node with a matched case (`critical`)
 * and a `default` fallback, each continuing to its own terminal `succeed`.
 * Authored with `definePipeline()` — the same builder real pipelines are
 * written with — rather than the compiled `PipelineDefinitionSpec` wire
 * shape it produces (see `linear-chain.ts`'s note). A `choice` state's
 * `choices[].when` is the routing source of truth; `definePipeline()`
 * derives the graph's edges from it.
 */

const classifySeverityStep = defineStep({
  key: "classify-step",
  name: "Classify severity",
  agentPrompt: "Classify the incident's severity.",
  result: z.object({ severity: z.string() }),
  signals: [{ sourcePath: "severity", key: "severity" }],
});

const pageOncallStep = defineStep({
  key: "page-oncall-step",
  name: "Page on-call",
  agentPrompt: "Page the on-call engineer about this incident.",
  result: z.object({ paged: z.boolean() }),
});

const fileTicketStep = defineStep({
  key: "file-ticket-step",
  name: "File a ticket",
  agentPrompt: "File a tracking ticket for this incident.",
  result: z.object({ ticketId: z.string() }),
});

export const choiceSpec = definePipeline({
  key: "catalog-choice",
  name: "Route an incident by severity",
  description: "Classifies an incident, then pages on-call or files a ticket.",
  startAt: "classify",
  states: {
    classify: { kind: "step", step: classifySeverityStep, next: "routeBySeverity" },
    routeBySeverity: {
      kind: "choice",
      choices: [
        { when: Rule.when("severity", "equal", "critical"), next: "pageOncall" },
      ],
      default: "fileTicket",
    },
    pageOncall: { kind: "step", step: pageOncallStep, next: "paged" },
    fileTicket: { kind: "step", step: fileTicketStep, next: "ticketed" },
    paged: { kind: "succeed" },
    ticketed: { kind: "succeed" },
  },
});