The execution model, for functional programmers
Ce contenu n’est pas encore disponible dans votre langue.
This page is optional. You do not need it to build machines, the guide explains everything in plain terms. But if you come from Haskell, Elixir, Elm, or anywhere with a strong notion of effects, there is a one-sentence version of mashin’s execution model that will save you an afternoon of reading. Here it is, then the unpacking, then why it is built this way.
The one sentence
Section titled “The one sentence”mashinTalk’s execution model is a free monad over a closed effect algebra, with governance as the interpreter.
If that lands for you, most of the rest of the docs will read as consequences. If not, read on.
The correspondence
Section titled “The correspondence”A machine is a sequence of steps. Two kinds:
- Pure steps (
compute,decide) compute values. They cannot touch the outside world. This is the language’s foundational law: code computes, machines act. - Effectful steps do not perform effects either. They describe one. A step that calls a model, reads a file, or hits an API returns a directive, a plain data value naming the effect it wants. It does not run it.
So an executor is a pure function. It takes a step and the run context and returns a result plus a list of directives:
{:ok, result, [ {:http_request, %{...}}, {:llm_call, %{model: "...", ...}} ]}Directives are data. They describe intent without carrying it out. That is the free monad’s core move: a program is a value, a tree of effect descriptions, inert until something interprets it.
The interpreter is a single runtime function. It is the only place effects actually happen, and it runs every directive through the same pipeline:
trust ceiling -> permissions -> phase -> pre-hooks -> perform -> guardrails -> record -> broadcastGovernance is not something a machine calls. It lives inside the interpreter, on the one path from
a directive to its effect. There is no other path. (In the user-facing docs these effects are called
actions, @mashin/actions/*; in this page’s vocabulary they are governed effects. Same thing,
different register.)
The step chain is the sequencing. The directive set, http_request, file_op, llm_call,
db_op, exec_command, memory_op, call, and the rest, is a closed sum. It is a fixed
algebra of effect constructors, not something a program can extend.
The Haskell version
Section titled “The Haskell version”IO a is a description of an effect that the runtime runs; you build IO values and the runtime
interprets them at the edge. A mashin machine is the same shape: effectful steps build directives,
and the governed interpreter runs them at the edge. do notation threads them; the step chain does
that here.
One deliberate difference: the effect functor is closed. You cannot write instance for a new
effect and hand it to the runtime. That is not an omission. It is the whole design, and the next
section is why.
Why it is built this way
Section titled “Why it is built this way”Three properties fall out of this shape, and they are the reasons to care:
You cannot accidentally do an ungoverned effect. There is no ambient IO. If a step did not emit a directive, nothing happened. You cannot forget to route an effect through governance, because you do not perform effects at all, you describe them, and the one interpreter that runs them is the one that governs them. A new step type inherits this for free: it returns directives like every other, and it has no way to skip the checks.
One program, many interpreters. Because effects are data, the same machine can be run several ways without changing it. Run it for real and it performs effects and writes a receipt. Run it in a test mode and the interpreter performs nothing and records nothing, hermetic by construction. Replay it and the interpreter re-runs the recorded directives instead of live ones. Deterministic replay and honest tests are not features bolted on; they are just different interpreters over the same directive program.
Governance can mediate everything a machine can express. The effect functor is closed, so the set of effects a machine can produce is fixed and known. The interpreter has a case for each one. Nothing a machine can express falls outside what the runtime can decide on. This is the point of the closed set, and it is why the effect set is fixed rather than open.
Why there is no general monad in the language
Section titled “Why there is no general monad in the language”The obvious question from this audience: if it is a free monad, why not expose bind and let me
define my own effects?
Because a general monad keeps the effect functor open, you add new effect constructors, and that is exactly the property mashin gives up on purpose. The moment programs can define new effects, the interpreter can no longer have a case for all of them, and governance stops being able to mediate everything expressible. A general-purpose language keeps the functor open and disciplines effects with the monad. mashin closes the functor and governs it. Same problem, tame effects; different trade. The price of a runtime that can see and mediate every effect is that the effect set is fixed. That trade is the language.
Where the monad’s usual jobs went
Section titled “Where the monad’s usual jobs went”You reach for a monad to sequence, to short-circuit on error, to handle absence, to suspend and resume. Each is a concrete feature here, not an abstraction you assemble:
- Sequencing is the step chain.
- Error and short-circuit is failure handling: a failed step halts the run with its reason.
- Absence is the expression language’s null handling (it is JavaScript-like).
- Suspend and resume is the
awaitstep type.
The upshot
Section titled “The upshot”You get the discipline of an effect monad, effects explicit, sequenced, and interpreted, without writing any of the plumbing, and you get a runtime that can inspect and mediate every effect and replay any run, because the program is data all the way down. That is why your machines are testable without mocks that lie, replayable to the event, and unable to slip an effect past governance. Not because there is a clever type talking to you, but because the model never lets a machine do anything, only describe what it wants done, and one governed interpreter decides.
The formal treatment of this model lives in the architecture docs (the pure-execution model and the effect boundary) if you want the precise version.