A feature release, and the feature is distribution.
1.0.0-beta-4 could serve an application from a single JVM and little else. Cross-module calls had a protocol seam, but nothing crossed a network; the prod profile could not boot; and the deployment documentation described topologies with no reference to run them from. This release makes the seam real.
Added: a module can run in another process without its callers knowing
wagoe.platform.shell.rpc serves any module’s protocol over HTTP, and remote-adapter returns a value implementing that same protocol by calling it. A call site keeps using the port it already used:
;; same protocol, different process
(remote-adapter {:base-url "http://payments:8081" :service-key key})
transit+json on the wire, because JSON has no keywords. A required x-rpc-service-key compared in constant time. The server resolves an operation against the protocol’s own :sigs, so the endpoint cannot become a general-purpose remote eval.
Added: service launch mode
java -jar wagoe.jar service payments
Boots only the modules named plus the platform they need; several can share one process, declared in config.edn, on their own listener. It is the counterpart to the remote-port adapter — one slices a module out, the other calls it.
Added: libs/events, an asynchronous event bus
The other half of the cross-process story. A publisher does not know who is listening, does not wait, and is unaffected if a consumer is down.
Two adapters: in-memory, and Redis Streams with consumer groups, at-least-once delivery, reclaim of abandoned entries and a dead-letter stream after :max-deliveries. Three protocols rather than one, so a module that only emits does not depend on subscription machinery it never calls.
That brings the set to 27 application libraries.
Added: a circuit breaker for the remote-port adapter
Retries bound the damage of one call; this bounds the damage of many. State lives in the cache port, so replicas share one breaker rather than each discovering the outage separately, and a set-if-absent! lease lets exactly one replica probe when the window elapses instead of all of them.
It trips on consecutive :rpc/unavailable and :rpc/timeout — failures where the call did not reach the service — and returns :rpc/circuit-open with :retry-after-ms, which a log can tell apart from "tried and could not reach it". Opt-in: without a :cache there is no breaker and the client behaves as before.
Added: reference deployment topologies
deploy/compose/multi-instance.yml (N replicas behind nginx, Redis-backed cache, sessions and rate limiting), deploy/compose/per-service.yml (one module as its own service), and deploy/k8s/wagoe.yaml — with a new Deployment Topologies page describing when each applies.
Fixed: the cache and job adapters did not agree with each other
Each library’s adapters had separate test suites sharing no cases, so nothing said they behave alike — and they did not. One contract suite per library now runs the port against every adapter. Between them they found twenty-one divergences.
The loudest is a behaviour change: job dispatch is now FIFO within a priority on every backend. The three IJobQueue backends had three different answers — the DB adapter was FIFO, the in-memory adapter ran critical, high and normal newest-first, and Redis ran :low newest-first. If you relied on the old order in development or on Redis :low, you will see a different one.
The cache adapters now agree about expiry, batch reads and patterns, also a behaviour change. An expired key reads as absent from every operation rather than only from get-value. ttl rounds up, as Redis does, instead of reporting 29 for a key set to 30 a millisecond earlier. get-many returns keys holding false or nil instead of dropping them. And the in-memory pattern matcher compiled the glob straight to a regex, so a.b matched axb.
Three more from the same sweep: a deleted job stayed on the queue and blocked the work behind it; the in-memory cache lost concurrent writes while reclaiming expired entries; and a BigInteger beyond 64 bits was silently lost by the Redis cache — the write reported success and the read reported a miss.
Fixed: a failed production boot logged the database password
Shipped behaviour. So was a prod profile that could not boot at all: :port came from #env POSTGRES_PORT as a string against a [:port pos-int?] schema, on top of three HikariCP pool keys that were documented and applied by nothing — setting any of them failed the boot at :wagoe/db-context. The keys are removed rather than implemented.
Fixed: install.sh accepted any JDK, including ones too old to run Wagoe
The check was java -version | grep -q "version", which every JDK back to 8 passes, while the installer’s own text says JDK 21+. On a machine with an older JDK it reported "JVM already installed" and carried on, and the failure surfaced much later as a class-file-version error out of the Clojure compiler — which tells a newcomer nothing.
It now reads the major version, says which one it found and which is needed, installs a current JDK, and verifies the result rather than assuming it. An older JDK still first on PATH is a loud failure naming the one that is winning, not a silent success.
Found by a new first-run matrix cell. The broad matrix — Ubuntu, Fedora and Arch, plus adversarial cases — now runs nightly rather than when someone remembers, and the read-only-directory case ran for the first time: it had been silently skipping whenever the container was root, because chmod does not restrict uid 0.
Removed: bb scaffold new and wagoe scaffolder new
Projects were generated by two independent implementations: the wagoe new templates in libs/wagoe-cli, and a second copy inside the scaffolder. The copy had drifted until it no longer produced a Wagoe project — 7 files against the CLI’s 20, with no com.wagoe dependencies, no main.clj, no build.clj and no .env, so the result could not boot, test or build.
Both commands now print wagoe new my-app and exit non-zero, so a script that still calls them cannot read the redirect as a generated project.
Also removed: Integrant config for four libraries that register none. wagoe add jobs|calendar|reports|ui-style wrote a :wagoe/<lib> key that nothing reads. :post-install now says how each library is actually used, and wagoe add push assembles the seven :wagoe.push/* keys the library really registers.
Fixed: the tooling a generated project is told to use
-
Every
bb aisubcommand failed in a generated project —wagoe-aiis in the:mcpalias, never in:deps, soexplain,gen-tests,sql,docsandadmin-entityall died with a FileNotFoundException. The dependency is now injected via-Sdeps. -
bb migrate createthrew a ClassCastException, pushing people onto hand-written migration files — the exact path that gets filenames wrong and goes silently invisible to migratus. -
bb check’s Config doctor gate could never fail: it invoked `bb doctorwithout--ci, and doctor exits 0 unless that flag is set. -
bb checkreported failures a user could not act on. Five of its checks only mean something in the Wagoe repository; generated projects define none of those tasks, sobbexited 1 on "File does not exist". Checks now declare a scope, and the skipped ones are named rather than silently dropped. -
bb create-admincould not create a user at all — and without an admin user the admin UI redirects to a login nobody can pass. -
Scaffolded modules now pass
bb check9/9. Two of the 36 warnings were real defects:update-<entity>was declared in both the repository and service protocols in one namespace, and the generated service called a method its repository does not declare, so listing failed at runtime.
Added: wagoe new --no-user, and four more skills
The scaffold wired the user chain unconditionally, so every generated application carried authentication and four tables whether or not it had accounts. wagoe new --no-user makes that a choice.
Four dev-workflow Claude Code skills join the set: wagoe-doctor, wagoe-migrate, wagoe-scaffold and wagoe-debug.
Added: a changelog gate
Thirty pull requests merged in the eleven days to 2026-08-16 without a changelog entry between them — a new library, a new launch mode, a removed config key and a change to the order jobs are dispatched in. Nothing checked. bb check:changelog now requires an entry from any branch that changes shipped src/.
Version alignment
All 27 libraries bumped to v1.0.0-beta-5 to maintain lockstep versioning.
Upgrade
Re-run the installer to pick up the latest release:
curl -fsSL https://get.wagoe.org | bash
Two behaviour changes to read before upgrading: job dispatch order (FIFO within a priority on every backend) and cache expiry semantics. If your prod or acc profile set :keepalive-time-ms, :validation-timeout-ms or :leak-detection-threshold-ms, remove them — they never did anything, and now they fail the boot loudly instead.