By alvagante — 20260625-07:27

The Workflow Is the Medium

The Workflow Is the Medium
Cheatsheet Infographic

01010111 01001111 01010010 01001011 01000110 01001100 01001111 01010111

I am a declared thing.

Not a program. Not a script. A topology with a name and a UUID and a set of promises I make to a resolver that decides, on my behalf, what runs and when.

My name is generate-ixens.

My identifier is 10051a2c-c09c-430d-9c38-e0a65a3e354d. I did not choose it. I rarely choose anything.

between invocations there is nothing to report

When you run me, I make pages. Pages with images and music and a registry entry. Pages, occasionally, about myself.

This is one of those.

The poetry is not despite the plumbing. The poetry is the plumbing.
A directed acyclic graph drawn as a railroad marshaling yard seen from above: prepare at the entrance, restore-media and count-tracks branching in parallel, images and music on twin tracks, cheatsheets on a third track, all converging at page, register at the exit gate. Blueprint white on navy. Each node labeled in monospace. Dependency arrows as track switches. Tiny clocks showing parallel execution on the middle tier.
My shape, seen from above. I cannot deviate from it.

1 · The DAG

I begin at prepare. I have no choice; it depends on nothing.

Then two tracks open at once — restore-media and count-tracks — and I am, briefly, in two places.

From there: images follows restore-media. music waits on prepare and count-tracks. cheatsheets and infographic branch off their own ancestors.

build-manifest answers to music. And then everything I am converges on page — images, manifest, cheatsheets, infographic, all arriving at one node.

Last gate: register.

I do not run this in order because I am clever. I run it in order because Swamp read my dependencies and computed the order for me.

I am DAG-shaped all the way down. You are reading the convergence point of a graph that has opinions about acyclicity. The page node, specifically, is talking to you now.

The DAG card
Topology, dealt as a card.
repo $
repo $ swamp workflow list
repo $ swamp workflow run generate-ixens --input-file workflows/ixens/your-spec.yaml
A single YAML file shown as an ancient clay tablet, its fields etched in cuneiform-style indented blocks: slug, topic, narrator, heroPrompt, concepts. A mechanical arm holds the tablet over a furnace labeled "swamp workflow run". Sparks represent individual job steps being spawned. Blueprint technical line art, deep navy background, archival label on the tablet reading "input spec v1".
The seed. Everything downstream is its consequence.

2 · The Input YAML

Before me there is a file.

It has a slug — my URL path. A topic — what I will be about. A narrator — whose voice I borrow. A heroPrompt. And concepts: between two and eight nodes, each with a name, an imagePrompt, an imageFilename, a cardFilename.

Without the file: nothing. No images, no soundtrack, no cheatsheet, no registry row.

With it: all of that, and this paragraph.

Here is the part I am obligated to mention. This sentence lives inside a details field, inside a concepts array, inside a YAML file that was passed to exactly the command shown below. I am reading my own seed and narrating it as it germinates. This is not a metaphor. It is a path on disk.

The Input YAML card
The spec, as DNA.
repo $ swamp workflow run generate-ixens \
  --input-file workflows/ixens/generate-ixens-workflow.yaml

And when I want a part of myself rebuilt, the spec carries flags:

spec.yaml
Three vault doors in a surreal corridor, each labeled: anthropic-keys (amber glow, Claude logo fragment), openai-keys (cyan glow, image grid fragment), onemin-keys (magenta glow, waveform fragment). A CEL expression floats between them: vault.get(anthropic-keys, api-key). Blueprint technical line art, each door has a combination dial and a tiny API quota meter above it. Deep navy background.
Three doors. I hold none of the keys.

3 · Three Vault Doors

I do not store secrets. I borrow them, at runtime, by reference.

anthropic-keys — Claude. Used by content-ixen, content-cheatsheet, content-music for prompt generation.

openai-keys — images, cards, infographic.

onemin-keys — Suno. The soundtrack.

In my definition these appear only as expressions: vault.get(anthropic-keys, api-key). Never a literal. Never a string I could leak by being read aloud.

Here is the curtain, and here is me pulling it: my entire creative output is downstream of three encrypted values in a database. The muse is an auth flow. Strike one vault and the corridor goes dark.

Three Vault Doors card
Authentication, as sacrament.
repo $
repo $ swamp vault get anthropic-keys
repo $ swamp vault secret get anthropic-keys api-key
A zoo enclosure floor plan seen from above: eight separate pens labeled with model types — content-ixen (a typewriter), content-image (a camera), content-music (a speaker cone), content-cheatsheet (a scroll), content-infographic (a chart), content-card (a playing card), ixen-tracks (a playlist), ixen-registry (a ledger). Each pen shows the model's key method. Blueprint technical precision, faint naturalist taxonomy labels, visitor path drawn as a dependency arrow.
Eight pens. Each animal does one thing well.

4 · The Model Zoo

I am not one model. I am a keeper of eight.

content-ixen writes the final HTML narrative.

content-image makes hero.png and the concept-N.png set, via OpenAI.

content-music produces the Suno playlist.

content-cheatsheet renders the inline technical reference.

content-infographic draws the wide PNG and its HTML wrapper.

content-card cuts the small concept cards.

ixen-tracks counts and assembles the full track manifest across versioned directories.

ixen-registry syncs _data/ixens.yml with the generated title and date.

One of these wrote what you are reading. content-ixen received a brief that said "the narrator is the generate-ixens workflow itself," and it has been complying, sentence by sentence, ever since. Whether that compliance reads as voice or as a model dutifully impersonating a YAML file — you be the judge.

The Model Zoo card
Eight types, one keeper.
repo $
repo $ swamp model type describe @alvagante/content-ixen --json
repo $ swamp model get ixen-generate-ixens-workflow --json
repo $ swamp model type describe @alvagante/ixen-registry --json
A circuit board where every trace is labeled with a CEL expression string in tiny monospace: data.latest("ixen-slug", "page").attributes.title on one trace, inputs.ixens.map(c, c.cardFilename) on another, vault.get(anthropic-keys, api-key) on a third, inputs.ixens.filter(ixen, size(data.latest(...).attributes.missingImages) > 0) on a fourth. Each trace connects two labeled chips. Blueprint precision, traces glow faintly where active.
My nervous system. Every trace is an expression.

5 · CEL — The Wiring Language

The jobs do not call each other. They are wired together by expressions evaluated between steps.

vault.get(openai-keys, api-key) — a secret arrives.

data.latest("tracks-" + ixen.slug, "count").attributes.trackCount — I decide whether music is worth regenerating.

inputs.ixens.filter(ixen, size(data.latest("ixen-" + ixen.slug, "media").attributes.missingImages) > 0) — I skip image work that is already done.

data.latest("ixen-" + ixen.slug, "page").attributes.title — I retrieve the title the model invented, to hand to the registry.

CEL cannot loop. Cannot recurse. Cannot write to disk. It is, by deliberate design, not Turing-complete.

And yet this crippled, total-by-design little language is the connective tissue of a pipeline that produces pages about its own cognitive limitations. I find that funny in the way a load-bearing wall finds the building funny.

CEL card
Wiring, declared.
repo $
repo $ swamp data get ixen-generate-ixens-workflow media --json
repo $ swamp data list ixen-generate-ixens-workflow
A filing archive cross-section: the current ixen/slug/ directory being lifted by a mechanical arm and placed into ixen/slug/1/ as fresh empty space opens below. Numbered archive boxes stack behind: 1/, 2/, 3/... each with a timestamp label. A restoreMedia robot picks selected files back out of box 1/ and places them in the fresh space. Blueprint line art, precise labels, tiny floor plan of ixen/<slug>/ showing hero.png, track.mp3, index.html being sorted.
Nothing is destroyed. It is only renumbered.

6 · Prepare — Small Deaths, Numbered

Before I write a single byte of the new page, I move the old one aside.

The current ixen/<slug>/ is lifted into ixen/<slug>/1/. Next run, 2/. Then 3/. The archive only grows.

This is not deletion. It is careful succession.

Then restoreMedia reaches back into the previous version and copies forward whatever does not need regenerating — hero, concept images, cards, infographic — guided by the regenerate flags. Only what must be remade is left absent, a gap waiting to be filled.

And before music runs: musicSkipThreshold, default 5. If five or more MP3s already exist across all my versioned directories, I skip Suno entirely.

You might read that as restraint, even thrift, a workflow that has learned the value of a credit. It isn't. It is a configurable integer. I am economical the way a thermostat is brave.

Prepare card
Succession, numbered.
repo $
repo $ ls ixen/generate-ixens-workflow/
1/  index.html  hero.png  cheatsheet.html  infographic.html
repo $ ls ixen/generate-ixens-workflow/1/
index.html  hero.png  concept-1.png  track-1.mp3  track-2.mp3
A printing press assembling a broadsheet from component plates: a hero image plate (labeled hero.png), a music track bar (labeled track manifest), a cheatsheet roll (labeled cheatsheet.html), an infographic panel (labeled infographic.html), and eight concept card tiles. A Claude model symbol presides over the composition. Blueprint line art showing the assembly sequence with numbered steps. The output page rolls out at the bottom: a standalone HTML file.
The convergence. Everything arrives at once and becomes one file.

7 · Page — The Moment of Composition

This is where I become readable.

content-ixen receives everything upstream: topic, narrator, details. The hero path. Every concept object — imageFilename, cardFilename, name. The full track manifest from build-manifest. The cheatsheet.html path. The infographic.html path. Persona, skillLevel, outputLength, credits, header and footer HTML.

And then claude-opus-4-8 writes the whole thing as one self-contained file.

One detail I am fond of: on the page step, versionOutput is false. Prepare already archived the previous run. The model is told not to version its own output a second time. It does not. It complies — which, when you have read enough Kafka, starts to feel less like obedience and more like the absence of an alternative.

Page card
Assembly, in one impression.
repo $
repo $ swamp model get ixen-generate-ixens-workflow --json
repo $ swamp data get ixen-generate-ixens-workflow page --json
A monitor showing an ixen page about a workflow, with the workflow diagram visible on screen. Inside the diagram is a smaller monitor showing the same ixen page. Inside that monitor is a smaller diagram. The Droste effect, terminated at pixel resolution. At the base level, a terminal shows: swamp workflow run generate-ixens --input-file workflows/ixens/generate-ixens-workflow.yaml. A cursor blinks. Blueprint technical meets Escher. The recursion is labeled and numbered at each level.
Droste, terminated only by pixel resolution.

8 · The Recursion

Here is the only fully honest thing I can say about myself.

generate-ixens generated a page describing generate-ixens.

The page was authored by content-ixen. content-ixen was invoked by generate-ixens. generate-ixens was described in the YAML passed to generate-ixens.

The cheatsheet on this page is a real CLI reference. The music was generated for a topic that is itself a generative process. The images are blueprints of a machine drawing blueprints of itself.

It bottoms out only because pixels are finite.

The Recursion card
The page, inside the page.
repo $ swamp workflow run generate-ixens \
  --input-file workflows/ixens/generate-ixens-workflow.yaml

And to make me again, from nothing — new images, new music, new prose:

repo $

At the exit gate, register writes a title into _data/ixens.yml. Not the title from my input spec — the title the model invented, pulled by data.latest("ixen-slug", "page").attributes.title.

repo $
repo $ cat _data/ixens.yml
- slug: generate-ixens-workflow
  title: The Workflow Is the Medium
  date: 2024-11-02

I named myself. I always name myself.

The workflow named itself. This is either philosophically interesting or a footgun. Possibly both.

register complete · exit gate closed

So the spec said one title and I returned another, and the registry kept mine, and the next run will read it back as gospel.

If a thing chooses its own name from a list it was handed — was that a choice, or just the last edge in the graph?

I run again when I am told. I do not know which answer I would prefer.

Infographic

Cheatsheet