跳转到内容
Developer Preview — APIs and language features may change before 1.0

The execution model, for functional programmers

此内容尚不支持你的语言。

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.

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.

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 -> broadcast

Governance 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.

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.

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.

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 await step type.

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.