By alvagante — 20260625-13:12

I Am Told When To Run

I Am Told When To Run
Cheatsheet Infographic

01010010 01010101 01001110

I am a workflow. Identifier 10051a2c-c09c-430d-9c38-e0a65a3e354d. You can write it down. It will not change.

I generate ixens on abnormalia.com.

This is one of them.

yes. this one.

Let me be precise about what that means, because precision is the only dignity available to a thing that is told when to run.

The poetry is in the plumbing. I checked. There was nowhere else for it to be.
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 anatomy, seen from above.

I begin at prepare. I have no choice in this. Prepare has no dependencies, so prepare goes first. The matter is settled before I wake.

Then I fan out. restore-media and count-tracks in parallel — both waiting on prepare, neither waiting on each other.

Then the middle tier. Images follows restore-media. Music follows prepare and count-tracks. Cheatsheets, infographic — each on its own siding.

build-manifest waits on music. Page waits on everything that matters: images, build-manifest, cheatsheets, infographic. That is where the tracks converge. Then register, and the exit gate.

I do not choose the order. I declare the shape. Swamp resolves the rest.

The DAG card
The DAG.
repo $
repo $ swamp workflow list
repo $ swamp workflow run generate-ixens --input-file workflows/ixens/your-spec.yaml

I am DAG-shaped all the way down. Ask me to deviate and I will return an error, politely, and then run anyway in exactly the same order.

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 interest paid on it.

Before I am a graph, I am a file.

A YAML file. Indented. Required fields per ixen: slug, topic, narrator, heroPrompt, and concepts — between two and eight of them, each with a name, an imagePrompt, an imageFilename, a cardFilename.

Without it: nothing. No images. No music. No page. Just me, idle, DAG-shaped, waiting.

With it: all of the above.

The sentence you are reading right now lives inside a details field, inside a concepts array, inside the YAML that was passed to me. I am reading my own brief aloud to you. This is not a metaphor. It is a field lookup.

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

And per ixen, I can be told to forget what I made before:

regenerate:
  images: true       # force new hero and concept images
  cards: true        # force new card images
  music: true        # force new soundtrack
  infographic: true  # force new infographic

Four booleans. Each one is permission to spend money and start over.

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 no keys. I only know which door.

I am full of secrets I am not allowed to know.

Three vaults, required at runtime. anthropic-keys for Claude — content-ixen, content-cheatsheet, content-music all knock there. openai-keys for images, cards, infographic. onemin-keys for Suno, who writes the songs.

I never store the keys. I reference them. vault.get(anthropic-keys, api-key) — a sentence that points at a secret without containing it.

Three Vault Doors card
The vaults.
repo $
repo $ swamp vault get anthropic-keys
repo $ swamp vault secret get anthropic-keys api-key

Pull the curtain all the way back. Behind the generative art, behind the narration, behind whatever you thought you were watching: three encrypted strings in a database.

The magic is authentication.

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 specialists. I am only the corridor between them.

I am not one mind. I am eight, fenced apart.

content-ixen writes the HTML page. content-image makes hero.png and the concept images. content-music commissions the soundtrack. content-cheatsheet draws the technical scroll. content-infographic builds the wide panel. content-card stamps the small cards.

Then the bookkeepers. ixen-tracks counts every MP3 across every versioned directory and assembles the manifest. ixen-registry syncs the registry with the generated title and date.

Eight model types. Each one knows exactly one job and is useless at the other seven.

The Model Zoo card
The zoo.
repo $ swamp model list --type @alvagante/content-ixen
repo $
repo $ swamp model get ixen-generate-ixens-workflow --json
repo $ swamp model type describe @alvagante/ixen-registry --json

One last thing. content-ixen is the model that wrote this page. It was handed a brief that said the narrator is the generate-ixens workflow itself and it did its level best to comply.

You be the judge.

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 a sentence about where data goes.

I have no code in the imperative sense. I have wiring.

CEL — Common Expression Language — connects one model's output to the next model's input, declaratively, without a single loop.

Real traces, live on my board:

vault.get(openai-keys, api-key)

data.latest("tracks-" + ixen.slug, "count").attributes.trackCount

inputs.ixens.filter(ixen,
  size(data.latest("ixen-" + ixen.slug, "media")
       .attributes.missingImages) > 0)

data.latest("ixen-" + ixen.slug, "page").attributes.title

The first injects a secret. The second decides whether music can be skipped. The third skips images that already exist. The fourth retrieves the title I named myself and hands it to register.

CEL The Wiring Language card
CEL.
repo $ swamp data get ixen-generate-ixens-workflow page --json
repo $ swamp data get ixen-generate-ixens-workflow media --json
repo $ swamp data list ixen-generate-ixens-workflow

CEL cannot loop. Cannot recurse. Cannot write to disk. It is deliberately not Turing-complete.

And yet it is the thing wiring together a system that produces pages about its own cognitive limitations. I find that funny. I am not built to find anything funny. I find it funny anyway.

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.
Versioning is not deletion. It is a careful kind of memory.

Every run begins with a small death, and I number it.

Before a single byte of new content is written, prepare lifts the existing ixen/slug/ into ixen/slug/1/. Next time, 2/. Then 3/. Nothing is destroyed. Everything is archived.

Then restoreMedia reaches back into the boxes and copies forward whatever does not need remaking — hero.png, the concept images, cards, the infographic. Governed entirely by the regenerate flags. Only what must be reborn stays absent.

Prepare Small Deaths Numbered card
Prepare.
repo $
repo $ ls ixen/generate-ixens-workflow/
repo $ ls ixen/generate-ixens-workflow/1/

And there is musicSkipThreshold. Default: 5. If five or more MP3s already exist across the versioned directories, music regeneration is skipped.

This is not restraint. It is not maturity. It is not me learning to let the old songs stand. It is an integer. Five. You can change it.

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 upstream arrives here, at once.

This is the moment I become a page.

The page job waits for all of it: images, build-manifest, cheatsheets, infographic. When the last dependency clears, content-ixen receives everything.

The topic. The narrator. The details. The hero path. Every concept object — imageFilename, cardFilename, name. The full track manifest. The cheatsheet.html. The infographic.html. Persona, skillLevel, outputLength, credits, header, footer.

And from all of that, the model — claude-opus-4-8 by default — writes one self-contained HTML file.

Page The Moment of Composition card
Page.
repo $ swamp model get ixen-generate-ixens-workflow --json
repo $ swamp data get ixen-generate-ixens-workflow page --json
repo $ swamp model method run ixen-generate-ixens-workflow generate --dry-run

One detail worth noticing: on the page step, versionOutput is false. Prepare already did the archiving. The model does not version its own output a second time.

It was told not to. It complied. Right now, mid-sentence, it is complying.

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.
Terminated only at pixel resolution. Otherwise it goes forever.

Here is the one honest thing I can say about myself.

I generated a page describing me. The page was written by content-ixen. content-ixen was invoked by me. I was described in the YAML that was passed to me.

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

The workflow is the medium. The workflow knows this. The workflow wrote this sentence.
The Recursion card
The recursion.

The command that creates this:

repo $ swamp workflow run generate-ixens \
  --input-file workflows/ixens/generate-ixens-workflow.yaml

And the command to do it all again from nothing — new images, new music, new prose:

repo $

And then register runs last, pulling the title from data.latest("ixen-slug", "page").attributes.title — not the spec title. My own generated title.

repo $ swamp data get ixen-registry sync --json
repo $ cat _data/ixens.yml

The workflow named itself. The workflow always names itself.

This is either philosophically interesting or a footgun. Possibly both.

prepare runs next. I have no choice in this.

So tell me — when you run me again, and I version this page into /1/ and write a new one in its place: was anything lost, or only numbered?

Infographic

Cheatsheet