# Missionary: a functional effect and streaming system for Clojure

> Missionary is a reactive dataflow toolkit for Clojure and ClojureScript that provides referentially transparent operators for lazy continuous signals, eager discrete streams and IO actions, unified under a Flow protocol. It fits teams building real-time or collaborative applications, and it asks you to accept a different supervision model before it pays off.

**leonoel/missionary** — A functional effect and streaming system for Clojure/Script

- Repository: https://github.com/leonoel/missionary
- Stars: 799 · Forks: 33
- Language: Clojure
- License: EPL-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/leonoel-missionary

## Signals, streams and effects sharing one Flow protocol

Missionary describes itself as a reactive dataflow programming toolkit providing referentially transparent operators for lazy continuous signals, eager discrete streams and IO actions. That three-way split is the whole design, and it is the thing that separates the library from most of the reactive toolkit ecosystem.

A signal is a continuous value that you sample, such as the current state of an atom. A stream is a discrete sequence of events that arrive over time, with backpressure. An IO action is an effect that runs and produces a result. Traditional streaming engines handle the second case well and the first and third badly, and that gap is where a lot of awkwardness in reactive code comes from. Missionary is designed upfront to cover all three.

The unification happens through the Flow protocol, described as a foundational protocol that lets a producer signal the availability of a value without eagerly computing it. That single mechanism covers both backpressured event streaming and lazy sampling of time-varying values, which is why the same operators work for a websocket feed and for a mouse position.

The library targets both Clojure and ClojureScript, with an asynchronous design for efficiency and ClojureScript compatibility, and it is Reactive Streams compliant. That last point has a practical consequence: the repository contains a `tck/` directory, which is where a compliance kit test suite would live, so the compatibility claim is at least structurally backed.

The repository layout matches the scope of the project. There is `src/` for the implementation, `java/` for the Java interop layer, `doc/` for documentation, `test/` for the test suite, and both `deps.edn` and `pom.xml`, which means the library can be consumed from a tools.deps project or from a Maven based build.

## The diamond graph and why inconsistent states are skipped

The README's example is worth reading closely, because it is the clearest statement of what the library is trying to prevent:

```clojure
(require '[missionary.core :as m])

(def !input (atom 1))
(def main                                      ; this is a reactive computation, the println reacts to input changes
  (let [<x (m/signal (m/watch !input))         ; continuous signal reflecting atom state
        <y (m/signal (m/latest + <x <x))]      ; derived computation, diamond shape
    (m/reduce (fn [_ x] (prn x)) nil <y)))     ; discrete effect performed on successive values

(def dispose!
  (main
    #(prn ::success %)
    #(prn ::crash %)))                         ; prints 2
(swap! !input inc)                             ; prints 4
                                               ; Each change on the input propagates atomically through the graph.
                                               ; 3 is an inconsistent state and is therefore not computed.

(dispose!)                                     ; cleanup, deregisters the atom watch
```

The graph is a diamond: one input signal feeds two branches that are combined again. The atom starts at 1, the first evaluation prints 2, and incrementing the atom prints 4. The value 3 never appears. The comment explains why: each change propagates atomically through the graph, and 3 is an inconsistent state, so it is not computed.

That is the classic FRP glitch, and eliminating it by construction is the library's central claim. In most reactive systems, a diamond where two branches derive from the same source can emit an intermediate value that never corresponded to any actual state of the world, and downstream effects then have to filter it out. Here the propagation is atomic and the inconsistent intermediate is never computed, so a consumer cannot observe it and does not need to defend against it.

The example also shows the lifecycle story. The computation is bound to a `main` var, and `main` returns a `dispose!` function. Calling it performs the cleanup and deregisters the atom watch. Nothing is left running once you dispose of it, which is what makes the model composable rather than a source of leaked threads.

## A sequential composition DSL that is a superset of Clojure

One of the key ideas the README states is that the sequential composition DSL is a superset of Clojure with full metaprogramming support, with the same expressive power as monads. That claim is the reason the API looks the way it does.

The `<x` notation in the example is the flow of a value, and `m/reduce` is the effect that consumes a sequence of them. Operators are named after what they do to a flow rather than after the algebraic structure they belong to, which is a deliberate departure from monadic style. The README is direct about the reason: the project aims to make sequential composition more practical, dismissing monadic binding in favour of a DSL that is a superset of the host language.

The project is candid that the technique is not new. Flattening nested monadic binds, and using a block syntax instead of a combinator soup, is present in almost every modern concurrency framework, and in Clojure itself in core.async `go` blocks. What the README claims is different is that the ambiguous expression extension to multiple value producers, where several producers feed one expression, is used sparingly even in the modern functional programming field, and Missionary applies it consistently.

The other stated key idea is reuse of Clojure core abstractions, including collections, transducers, reducing functions and reference types. This is the part that decides whether a Clojure team can be productive with the library. A toolkit that ignores the host language's own transducers and reference types becomes a second ecosystem with its own vocabulary. One that builds on them means a transducer written for ordinary Clojure code composes with the reactive primitives.

The primitives are also fully separated and unbundled, which the README gives as the reason for achieving low-level control over discrete versus continuous behaviour, eager versus lazy evaluation, and the allocation and reaction boundaries. That control is the price of the consistency guarantees, because the library cannot skip work without knowing exactly which boundary the work sits on.

## Supervision built in rather than bolted on

The error handling story is the part of Missionary that most changes how an application is written, and the README is unusually clear about the reasoning.

Missionary takes a functional approach to concurrency, focusing on computation instead of conveyance. The contrast it draws is with conveyance-oriented techniques, listing Clojure's succession model, CSP, futures and promises, and actor systems. In all of those, the first-class primitive is a communication device, a reference type, a channel, a dataflow variable or a mailing address, used to coordinate encapsulated stateful processes. The README's complaint is that this style makes no attempt to provide structure to concurrent computations, so supervision must be implemented in user space, generally as an afterthought, often simply omitted.

The functional alternative enforces a strict hierarchy of concurrent programs. The benefit is that supervision concerns do not leak into the domain, and the runtime knows the structure of the program and can cancel siblings and propagate an error to the parent when a branch fails. Communication devices are still provided for cases that genuinely require data transfer across branches of the supervision tree, but their use is specialised rather than generalised.

That is the substance behind the feature list entry about correct error handling by default, strict process supervision, transparent propagation of cancellation and failure, and strong resource cleanup guarantees. In practice it means failure semantics are a property of the structure of your code rather than something each author has to remember to wire up.

The cost is a steep change in mental model. A team arriving from channels and futures has to internalise a hierarchy where the parent owns the children, and where a plain channel is a special case rather than the default way to pass a value. Missionary is not a drop-in replacement for core.async, and the README's prior art section is effectively an argument for why that is a feature.

## Adding the dependency and reading the quickstart

Missionary is published to Clojars and the dependency is declared in the usual tools.deps form. The README gives the coordinates with a beta version:

```clojure
{:deps {missionary/missionary {:mvn/version "b.47"}}}
```

The `b.` prefix signals a beta build, which is consistent with the maturity statement that the project is experimental but stable, with documentation as the current development priority. Note that the repository publishes no GitHub releases, so this version string in the README is the reference point, and the Clojars badge tracks the published artefact.

For orientation, the README points at an API reference for `missionary.core` on cljdoc and at a quickstart repository, quickstart.cljs. The cljdoc link is the one to bookmark, since the whole library surface is the `missionary.core` namespace and the operators are documented there rather than in a narrative guide.

A first run should be small. Load the namespace, watch an atom, derive a value from it, and reduce over the result, which is exactly the shape of the README example, and then call the returned `dispose!` to confirm that cleanup works. If the graph does not tear down cleanly you will find out in the first minute rather than after the codebase has grown.

The `tck/` directory is worth a look for a second reason. Reactive Streams compliance means the library should behave predictably when a consumer is slow, and a compliance kit suite in the tree is how you would check that claim without trusting the badge.

## How Missionary differs from streaming engines and effect systems

The README positions the project against three families, and the comparisons are more useful than the feature list because they explain what you give up.

Against imperative concurrency, the argument is the supervision hierarchy described above. The difference in approach is that Missionary makes the runtime responsible for structure and failure handling, while a channel based or actor based model leaves that to the programmer.

Against functional effect systems, the comparison names ReactiveX as one of the first functional effect systems to gain traction in mainstream languages, and then Cats Effect, ZIO and Monix in the Scala ecosystem, all heavily influenced by Haskell's IO monad. Missionary's difference is the DSL: instead of monadic binding, the sequencing syntax is a superset of Clojure, so the code reads like ordinary Clojure with a few extra forms. That is a genuine ergonomic difference for Clojure programmers, and it is also a compromise, because the DSL has to be implemented with metaprogramming rather than being a property of the type system.

Against reactive engines, the difference is the flow abstraction. Traditional streaming engines focus on discrete events, while Missionary supports continuous time as well, which is the realm of FRP. The protocol that allows a producer to signal availability of a value without eagerly computing it is what makes both backpressured streaming and lazy sampling possible in the same system.

That last comparison is the one that decides adoption for most teams. If your problem is event streams, a conventional streaming library will be simpler and you will spend your complexity budget elsewhere. If your problem includes values that vary continuously, such as a cursor position or a live sensor reading, the discrete-only model forces you to resample, and you end up writing the timing logic yourself.

## An experimental library with a real downstream user

Two facts about the project sit oddly together and both matter for an adoption decision.

The first is the maturity statement. The project calls itself experimental but stable and says the current development priority is documentation. The last push was on 2026-06-26 and the repository is not archived, so the code is alive, but the honest reading is that the APIs may still move and that the written guidance is the part being worked on now. For a library whose selling point is a precise model of ordering, allocation and failure, incomplete documentation is a bigger risk here than it would be for, say, a utility collection.

The second is the downstream usage. Missionary's reactive VM is the compiler target of hyperfiddle/electric, a reactive dialect of Clojure and ClojureScript for UI programming. That is a strong signal in a different direction. A library that is the execution substrate for a separate reactive dialect has been stress tested by a real consumer, and the reason to build Missionary as a foundation rather than as a library is precisely so that a compiler target can sit on top of it. The README frames the project's mission as establishing a rigorous foundation for modern web programming, particularly sophisticated real-time collaborative applications, which is an ambitious goal for something described as experimental.

The licence is EPL-2.0, with the licence file at the repository root. EPL is a weak copyleft licence, so linking the library into your application generally carries obligations around modifying the library itself rather than around your own code, but which obligations apply to your use is a question for whoever handles licensing in your organisation rather than a technical one.

The build is also a mixed bag of eras. The repository carries a `.travis.yml` file alongside `deps.edn` and `pom.xml`, and the README links a Travis CI badge, while the Maven and Clojars badges cover publishing. Continuous integration on Travis for a project of this ambition is worth asking about, since a CI service that is no longer the default choice for new projects tends to be a sign of low recent activity, and the last push date is the number to weigh alongside it.

## Conclusion

Adopt Missionary when your team writes Clojure or ClojureScript and your application is event driven enough that the boundary between a value, a stream of events and an IO action has become the source of bugs, since the Flow protocol and the supervision model exist precisely to draw that boundary. Do not adopt it for a small synchronous script, and read the prior art comparison first if your team arrives from core.async, because Missionary deliberately replaces the go block and the channel model with a supervised DSL. Verify first the version you depend on, since the coordinates point at the beta version b.47 and the repository publishes no GitHub releases, then confirm the maturity statement in the README that the project is experimental but stable with documentation as the current development priority. If you need this behaviour today, evaluate it through the quickstart and the cljdoc API reference rather than through production adoption.

## FAQ

### What is Missionary used for in Clojure?

Missionary is a reactive dataflow programming toolkit that provides referentially transparent operators for lazy continuous signals, eager discrete streams and IO actions. It is Reactive Streams compliant, supports backpressure, and targets both Clojure and ClojureScript.

### How do I add Missionary to a Clojure project?

The README gives the tools.deps coordinates as {:deps {missionary/missionary {:mvn/version "b.47"}}}, with the artifact published to Clojars. The API reference for missionary.core is hosted on cljdoc, and the project also points at a quickstart repository for a first run.

### What does Missionary do about reactive glitches?

The feature list claims correct incremental maintenance of dynamic DAGs without inconsistent states, and the README example shows a diamond graph where an increment produces 4 rather than the intermediate 3. Each change propagates atomically through the graph, so the inconsistent state is never computed.

### How does Missionary handle errors and cancellation?

The README lists correct error handling by default, strict process supervision, transparent propagation of cancellation and failure, and strong resource cleanup guarantees. In the model, the runtime knows the structure of the computation and can cancel sibling branches and propagate an error to the parent.

### Is Missionary ready for production use?

The README describes the project as experimental but stable, with documentation as the current development priority, and the dependency points at a beta version, b.47. The repository is not archived and the last push was on 2026-06-26, and the reactive VM of hyperfiddle/electric is built on the library.

## Sources

- [Issues](https://github.com/leonoel/missionary/issues)
- [leonoel/missionary on GitHub](https://github.com/leonoel/missionary)
- [License: EPL-2.0](https://github.com/leonoel/missionary/blob/master/LICENSE)
- [README](https://github.com/leonoel/missionary/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/leonoel-missionary
