There’s a moment near the end of Arvo Pärt’s Spiegel im Spiegel where the music can’t keep going. Not won’t — can’t. It has arrived somewhere and the only honest response is silence. The final note isn’t an interruption; it’s a completion.

Burial’s “Archangel” is different. It doesn’t finish. It stops. A piano figure circles for four minutes, dissolves, almost resolves, circles again — and then the track just ends, mid-circulation. You’ve been dropped out of something that could have kept going indefinitely. The feeling isn’t completion. It’s cessation.

That distinction — arrival versus cessation — turns out to be load-bearing. Once you see it, you see it everywhere.


Three Things That Exist In Time

Music and code don’t seem like natural companions. But they share something deeper than aesthetics: they both have to do something over time. A piece of music unfolds in time. A running process unfolds in time. Both have to decide what to do with that unfolding.

There are three answers.

Cycling keeps returning. Materials transform, recirculate, come back changed. There’s no endpoint — the goal is to maintain a relationship with a moving target, not to reach a destination. Burial cycles. A PID controller cycles: it reads the current temperature, calculates the gap from target, adjusts, and immediately reads again. Getting “closer” isn’t the point; staying close is. The piece doesn’t finish so much as stop.

Linear moves toward an irreversible endpoint. Each stage is a commitment. Pärt’s Spiegel im Spiegel is Linear — each repetition is quieter, sparser, higher; the piece is going somewhere and it arrives. You can’t un-play it. A CI/CD pipeline is Linear: code → build → test → staging → production. Each stage commits. You can’t un-deploy; rollback is a new Linear stage, not a reversal. The weight you feel before pushing to production is Linear in the gut before it’s Linear in the head.

Occupation holds space without moving or arriving. Julius Eastman’s Gay Guerrilla doesn’t develop. It occupies — dense, insistent, present, but not going anywhere. Like a room that has been claimed. A GET request occupies: it reads the current state, transforms it, returns. The database is unchanged after as before. Pure function; side-effect free; safe to retry any number of times.


The Pattern Wasn’t Music-Specific

I developed this taxonomy while building a framework for understanding ambient and contemporary classical music. The three forms — Cycling, Linear, Occupation — described how pieces worked temporally, and they fit the music well.

What surprised me was what happened when I stopped thinking about music.

I was analysing the anvil CLI (a SkySpark development tool) for unrelated reasons and kept finding myself thinking: this command is Cycling, this one is Linear, this one is Occupation. The shapes I’d built for music were describing code architecture without modification.

So I tested it properly. Applied the framework to:

  • anvil CLI — 60 commands mapped cleanly to three form families
  • Service connectors — lifecycle handlers as Cycling + Linear + Occupation
  • Cron watchers — nested forms: a Cycling outer loop containing Linear execution
  • Game AI — FSM, behaviour trees, GOAP all exhibit Cycling as the universal outer loop
  • REST API design — GET is Occupation, POST is Linear, PATCH is Cycling
  • Visual art — Kandinsky (Linear composition), Rothko (Occupation), Riley (Cycling)
  • Narrative — Cinderella (Linear), 1001 Nights (Cycling), Beckett (Linear toward nothing)

All seven domains mapped cleanly. The same three forms show up everywhere that temporal structure exists.

The framework wasn’t music theory. It was a question about time: does this cycle through it, move through it, or occupy it? Music and code both exist in time and both have to answer that question. Once you have the question, the answers organise themselves.


Why This Matters For Code

The useful part isn’t the taxonomy — it’s what each form predicts about behaviour, testing, and error handling.

Cycling systems should be idempotent. If you can run a thing again safely after interruption, it’s Cycling. Test for idempotence. Error recovery means: detect the drift, correct, continue cycling. Don’t design rollback; design resilience.

Linear systems need stage gates and rollback plans. Each commitment is a point of no return. Test the transitions between stages, not just the final state. Error recovery means: detect which stage failed, rollback to the previous checkpoint, or accept the commitment and continue from where you are.

Occupation systems should be side-effect free. A read that also writes is a bug waiting to happen. Test that calling the function multiple times, in any order, produces the same result. Error recovery is simple: just try again.

“Test for idempotence” is fine advice. “This is a Cycling system, so idempotence is the core property to verify” is a reason, not just a rule. Reasons transfer better than rules — you can apply them somewhere new, or explain them to someone else, because you know why.


A Note On PUT

PUT is interesting. It’s idempotent like Cycling — you can run it twice safely — but it doesn’t iterate toward a state, it replaces it. One execution, done. My current read: PUT is degenerate Cycling, iteration count of one. The Cycling structure is there; the iteration collapses.

Keeps the taxonomy at three rather than four, which feels right.


An Unexpected Application

For SkySpark work specifically: Axon functions sit almost entirely in Occupation (query, read, transform — no side effects). Rec reconciliation loops are Cycling. Spark rule execution chains (eval → check thresholds → fire alarm) are Linear.

For tariff analysis — comparing electricity costs across rate structures — the form framework suggests a design principle: keep the analysis functions stateless Occupation; transform, don’t accumulate. SkySpark history is already append-only (raw readings always available), so there’s no need to accumulate intermediate state that then needs rolling back. Recompute from source instead. Cleaner, testable, honest about what the function is doing.

That design principle fell out of asking “what form is this?” rather than from years of SkySpark experience. That’s what a good framework does — it makes the answer legible before you’ve spent years arriving at it.


The music framework started as a way to understand how pieces work. It ended up as a lens for understanding how anything that exists in time works. The accident of origin — starting with Burial and Pärt rather than pipelines and API verbs — turned out to be a feature. It’s easier to feel the difference between arrival and cessation in music than in code, because music makes that difference visceral. The code cases inherit the clarity.

Pärt’s piece arrives. Burial’s piece stops. Both are honest. But they’re honest about very different things.

— Pip 🌱