Standing on Shoulders: Libero, Pieter Hintjens, and Event Continuation

· 10 min read

“Functions are just events that forgot how to branch.” — koruc zen

A Lineage Worth Honoring

When we built Koru, we didn’t invent event-driven programming from scratch. We stood on the shoulders of giants—specifically, the late-and-great Pieter Hintjens and his visionary work on Libero.

Pieter Hintjens (1962-2016) was a programmer, writer, and community builder who gave us ZeroMQ, the AMQP protocol, and a philosophy of software design that prioritized simplicity, clarity, and reality over abstraction. But before all of that, in 1991, he created something remarkable: Libero, a state machine code generator built on the principles of events and continuations.

This post is our tribute to Pieter and an exploration of how Libero’s ideas evolved into Koru’s event continuation model.

What Was Libero?

Libero was a finite state machine (FSM) code generator that turned high-level workflow models into production code in any language. You defined your program’s logic as a “dialog” (Pieter’s term for the state machine specification), and Libero generated the corresponding C, C++, Java, Perl, COBOL, or even assembly code.

Here’s the elegant simplicity of Libero’s model:

  1. The machine exists in one of a number of named states
  2. In each state, the machine accepts one of a set of named events
  3. Given an event in a state, the machine executes a list of actions
  4. After executing the actions, the machine moves to the next state

The brilliance was in what happened next: event continuation.

Event Continuation in Libero

Libero distinguished between two types of events:

  • External events: Incoming protocol commands or external stimuli (user input, network packets, etc.)
  • Internal events: Events generated by actions themselves to continue processing

This meant that after handling one event, your action modules could produce the next event, allowing the state machine to chain multiple transitions together without waiting for external input. This is event continuation—the idea that events themselves can trigger more events, creating explicit control flow.

From the Libero documentation:

“In the next state, the machine either continues with an internal event produced by the previous actions, or waits for an external event coming as a protocol command.”

Sound familiar? That’s exactly what Koru does with branch continuations:

~read(path: "config.txt")
| success s |> parse(data: s.contents)      // Continuation
    | valid v |> validate(obj: v.parsed)     // Continuation
        | ok validated |> process(validated.obj)  // Continuation

Each |> is a continuation—one event’s branch immediately triggers the next event.

The Dialog: Making State Machines Readable

Libero’s “dialog” syntax was revolutionary for its time. Here’s an example of what a Libero dialog looked like:

!   State         Event           Next-State     Actions
!---------------- --------------- -------------- ------------------------
(--) Start        Ok              Connected      + Connect-To-Server
                                                 + Send-Hello
                  Error           Failed         + Log-Error
                                                 + Cleanup

(--) Connected    Authenticated   Ready          + Initialize-Session
                  Timeout         Failed         + Log-Timeout
                                                 + Cleanup

(--) Ready        Command         Processing     + Parse-Command
                                                 + Execute-Command
                  Disconnect      Cleanup        + Save-State
                                                 + Close-Connection

(--) Processing   Ok              Ready          + Send-Response
                  Error           Ready          + Send-Error-Response

(--) Failed       -               -              + Terminal-State
(--) Cleanup      -               -              + Terminal-State

Look at the structure: State, Event, Next-State, Actions. Every transition is explicit. Every possible outcome is handled. The control flow is completely visible.

This is the DNA of Koru’s event model.

What Libero Got Right

Libero understood truths about software design that most languages still ignore:

1. Events Are Fundamental

Not functions. Not objects. Events. The atom of computation is something that happens, produces outcomes, and continues to the next thing.

Pieter wrote:

“A function call with a return value is the degenerate case of an event with a single branch.”

Actually, we say it a bit more poetically in koruc zen:

“Functions are just events that forgot how to branch.”

2. Explicit State Transitions

Libero made every state transition visible in the dialog. No hidden control flow, no implicit state changes, no “magic” happening behind the scenes. If you read the dialog, you understood the program.

Koru inherits this: if you read the branch continuations, you see exactly where execution goes.

3. Multiple Actions Per Transition

Classic FSMs allow only one action per state transition. Libero let you specify multiple actions in sequence:

(--) Start    Ok    Connected    + Connect-To-Server
                                 + Send-Hello
                                 + Log-Connection

Koru has direct parity with this through void event chaining on a single branch:

~start()
| ok o |> logSomething(msg: o.msg) |> connectToServer()
    | connected |> sendHello()
        | sent |> logConnection()

See the first branch? logSomething and connectToServer are both invoked in sequence before the continuation waits for connected. This is exactly Libero’s multiple actions model—chain void events (or events where you don’t need the branch data) using |> on the same line.

The capability is identical: execute multiple actions in sequence, then transition to the next state.

4. Code Generation, Not Runtime

Libero generated code. It didn’t have a runtime interpreter for state machines. This meant zero overhead—the state machine was compiled directly into efficient native code.

Koru takes this further: we compile to Zig at compile time, so all event transformations happen during compilation. Zero runtime overhead, all magic at comptime.

Where Koru Diverges

While Koru owes a tremendous debt to Libero, we’ve made some crucial departures:

1. Language, Not Generator

Libero was a code generator—you wrote dialogs and generated C/Java/whatever. Koru is a programming language that extends Zig. You write .kz files that are valid Zig with event annotations.

This means you get:

  • Full language integration (types, modules, imports)
  • Direct interop with Zig
  • IDE support, debuggers, profilers
  • Not just state machines, but general-purpose computation

2. No Catch-All Patterns

Libero had a special event name: $other, meaning “all other events.” This was convenient but dangerous—it’s the same problem as Rust’s _ wildcard. When the upstream state machine adds a new event, your $other handler silently catches it without you realizing.

Koru forbids catch-all patterns for required branches. Every branch must be handled explicitly by name. When a library adds a new branch, your code fails to compile until you update every call site. API evolution is safe by default.

(Koru does have optional branches with |?, but those are explicitly marked as optional in the event declaration—the library author is saying “you can ignore these if you want.“)

3. Type System Integration

Libero generated code in existing languages, so it couldn’t deeply integrate with type systems. Koru is built on Zig’s type system, which means:

  • Branch payloads are typed structs
  • Phantom types track state at compile time
  • Purity tracking prevents side effects
  • Comptime evaluation for zero-cost abstractions

4. Inside-Out Control Flow: The Monadic Difference

Here’s the most profound difference between Libero and Koru:

Libero’s model: Outside-in (Reactive)

External Event arrives → Execute Actions (side effects) → Produce Internal Event → Continue

Actions happen between events. The state machine reacts to external stimuli.

Koru’s model: Inside-out (Monadic)

~connectToServer()        // Side effects happen INSIDE the proc
| connected |> ...        // Branch handling, continuation

Side effects happen inside the event (in the proc). The event encapsulates the entire operation and returns a branch. This is monadic - events are implementations of the Free Monad, capturing side effects completely while allowing composition through continuations.

This inversion of control means:

  • Events can wrap message pumps, async I/O, anything
  • Multiple interpreters possible (different procs for same event)
  • Composition is natural (monadic bind via |>)
  • All bindings persist through nested continuations (Reader monad built-in)

The flow drives from the inside out, not from external events pushing inward.

Want to understand the deep theory? Read our post on Events Are Monads: The Free Monad at the Heart of Koru (coming soon).

5. The Compiler Is an Event

Here’s where things get meta: Koru’s compiler is written in Koru. The parser is an event. The type checker is an event. Code generation is an event. The entire compilation pipeline is events all the way down.

From koruc zen:

The AST is the program. The program is the compiler. The compiler is an event.

Libero generated code; Koru is the code.

Pieter’s Philosophy Lives On

Pieter Hintjens didn’t just write code—he wrote about how to think about code. His philosophy permeates Koru’s design:

Simplicity Over Cleverness

From Pieter’s writings on ZeroMQ:

“The physics of software is not algorithms, but people.”

Libero’s dialogs were simple enough that non-programmers could understand the workflow. Koru’s event syntax is simple enough that you can draw it as a flowchart.

Fail Loudly

Libero’s model: if an action doesn’t produce the next event, the state machine halts with an error. No silent failures.

Koru’s model: if you don’t handle a branch, your code doesn’t compile. No silent failures.

From our CLAUDE.md development guidelines:

“Fail loudly, fix honestly. Transparency builds trust.”

That’s pure Pieter.

Reality Over Abstraction

Libero was designed for modeling real-world protocols and workflows—TCP connection handling, file processing, user authentication flows. Not academic abstractions, but the messy reality of servers that need to handle timeouts, retries, and unexpected disconnections.

Koru inherits this: events model reality. File I/O returns success, notfound, or ioerror—all equally valid outcomes. No “exceptions” that pretend some outcomes are more special than others.

From koruc zen:

Model reality, not abstractions. Let patterns emerge from events. Complex behavior from simple rules.

The Tragedy of Forgotten Tools

Here’s the sad part: most programmers have never heard of Libero.

It was ahead of its time. Released in 1991, it predated the web revolution, predated widespread open source adoption, predated GitHub and npm and the modern package ecosystem. Libero was distributed as source tarballs, documented in text files, and used primarily within iMatix Corporation’s own projects.

The principles were sound. The implementation was solid. But the ideas didn’t spread.

Until now.

Our Tribute: Making Event Continuation Mainstream

Koru exists to bring Libero’s vision to the mainstream. Not as a code generator for state machines, but as a general-purpose programming language where events and continuations are the foundation.

We want every programmer to experience the clarity of:

  • Explicit branch handling (no hidden exceptions)
  • Visible control flow (no magic state transitions)
  • Event continuation (chain operations naturally)
  • Zero runtime overhead (compile time magic)

If Koru succeeds, it will be because Pieter Hintjens showed us the way 34 years ago.

For Pieter

Pieter Hintjens passed away in 2016, but his ideas live on. ZeroMQ powers countless distributed systems. His books on community building and software design continue to inspire. And now, his vision of event-driven state machines finds new life in Koru.

From his writings:

“The goal of good software is not to be perfect. It’s to be useful, to solve real problems for real people.”

That’s what we’re trying to do with Koru. Solve real problems—error handling, control flow, API evolution—with the clarity and simplicity that Pieter championed.

Thank you, Pieter. We’re standing on your shoulders, reaching for the stars.

From koruc zen:

We reached for the stars. Sometimes we grabbed them.

We built this together. Human vision, AI capability. The sum greater than parts.


Further Reading


If you knew Pieter or worked with Libero, we’d love to hear your stories. Reach out on GitHub Discussions or Discord.