red is the TypeScript/Bun workflow SDK of Colors.
Green (Clojure/Babashka, canonical), red
and Blue (Python/uv) implement one model:
- DAG execution: steps take and return an options map; static
wireFngraphs and optional dynamicnextFnrouting support parallel fan-out, joins and failure propagation. - Subworkflows:
step(wf, { inFn, outFn })embeds a reusable workflow as an ordinary step. - Advice: workflow-scoped wrappers attach behaviour without changing steps, with ordering, inheritance and stack inspection.
- Configuration helpers: YAML parsing and
COLORS_PAR_*overlays.
Applications own their CLI, file selection, validation, planning, progress, logging and durable recovery. They may implement policies through advice; red does not bundle dry-run, progress or lifecycle helpers. Steps call CLIs or APIs directly. PocketDeploy is a deployment controller built on Blue, with its own state journal.
The full behavioral contract is in SPEC.md, also published at https://getcolors.github.io/red/. Shared semantics do not make application code mechanically portable between languages.
red has no runtime dependencies beyond Bun. It is not published to npm;
consumers must use a git dependency pinned to a published commit in
package.json, then import from red or its module paths.
bun install
bun test
bun run typecheckimport { adviceAddAll, run, step, workflow, type Opts, type StepFn } from "red";
const mark = (name: string): StepFn => (opts) => ({ ...opts, [name]: true });
const child = workflow({
start: "child/prepare",
wireFn: (name) => name === "child/prepare" ? [mark("prepared")] : undefined,
});
const graph = workflow({
start: "app/start",
wireFn: (name) => {
switch (name) {
case "app/start": return [mark("started"), "app/child", "app/other"];
case "app/child": return [step(child), "app/join"];
case "app/other": return [mark("other"), "app/join"];
case "app/join": return [(opts) => ({ ...opts, results: opts["red/branches"] })];
}
},
});
const observed = adviceAddAll(graph, "around", "app/log", async (base: StepFn, opts: Opts) => {
console.log(opts["red/step"]);
return await base(opts);
});
const result = await run(observed, {});The two branches join once. Parent advice also reaches child/prepare.
advicePlan shows the composed stack. Child failures propagate by default;
an outFn must preserve or deliberately handle failure keys when adapting
results. Workflow execution is in-memory, not a durable recovery journal.
import { loadYaml, readPars, parName } from "red/cli";
const config = readPars(loadYaml(await Bun.file("colors.yml").text()));The historical red/cli module path is retained for configuration helpers
only. loadYaml parses YAML text with Bun's YAML 1.2 reader; it does not
read files, validate a schema, or stamp control keys. readPars overlays
flat kebab-case keys from COLORS_PAR_*, coercing existing booleans and
integers. parName("do-token") returns COLORS_PAR_DO_TOKEN. Quote
version-like YAML values such as "3.10" and keep credentials outside YAML.
Applications choose their configuration filename and whether to apply overlays.
red/workflow: execution, composition, advice registration/inspection and types.red/advice: combinators and immutable registry operations.red/cli:loadYaml,readPars,parName.
The root red export exposes workflow/configuration names and the advice
namespace. The full advice system and engine-level start/end slicing
remain supported; the library supplies no generic CLI flags.
examples/hello is an offline application with its own CLI.
It validates desired state, discovers the host with uname, fans out one
embedded write/verify workflow per unique name, then joins to read the files back.
Application-defined advice logs stages (including child steps) and skips
writes in its preview mode. These policies live in the example, not in red.
cd examples/hello
./red create --dry-run
./red create
COLORS_PAR_MESSAGE=hi ./red create
./red deletetest/hello-example.test.ts drives this CLI in temporary directories and
checks composition, inherited advice, previews, configuration and failures.