Native Apps with ClojureScript, React and Static Hermes
A mix of tech that makes it possible to build fully native apps with JavaScript, React and native libraries such as ImGui, thanks to JavaScript AOT compilation in Static Hermes
A mix of tech that makes it possible to build fully native apps with JavaScript, React and native libraries such as ImGui, thanks to JavaScript AOT compilation in Static Hermes
I've been working on/preparing for migrating Biff to XTDB v2 since that became generally
available in June. After investigating the deployment
options and performance characteristics, I've added some XTDB v2 helper functions to the Biff
library (under a new com.biffweb.experimental namespace) and I've made a version of the starter
project that uses XTDB v2.
You can create a new XTDB v2 Biff project by running clj -M -e '(load-string (slurp "https://biffweb.com/new.clj"))' -M xtdb2. See this
gist for a diff between the
old/main starter project and this new one.
To give you a quick overview of what Biff provides:
use-xtdb2 and use-xtdb2-listener components, roughly the same as we have already for
XTDB v1.ctx map will have a :biff/conn key in it (a Hikari connection pool object) which you can
pass to xtdb.api/q to do queries.com.biffweb.experimental/submit-tx, which will apply Malli validation to any
:put-docs / :patch-docs operations.There's still plenty of work to do before XTDB v2 support in Biff is officially released and becomes the default:
Since those next two steps will take a while, I wanted to do this "pre-release" for anyone who would like to get a head start on trying out Biff with XTDB v2. If you do so, let me know whatever questions/comments you have. Just note that the new functions in Biff's API are still experimental and might have breaking changes before I do the official release.
And for anyone who would rather not deal with migrating an existing app, Biff will still support XTDB v1. It's totally fine to stay on that.
Finally: I'll be at Clojure/Conj next week, at least if my flight doesn't get canceled. Come say hi.
Recreate the classic Galaga arcade game with formation patterns, swooping enemies, and intense space combat - all running in the browser with zero build tools!
Create a classic Asteroids arcade game with physics simulation, collision detection, canvas graphics, and retro sound effects - now with mobile touch controls! All running in the browser with zero build tools!
So how does your tiny ClojureScript program transform into runnable JavaScript?
(ns app.core)
(println "Howdy!")
First of all, you need a compiler! ClojureScript is a hosted language, which means you have to compile it into JavaScript, unless you want to ship the compiler into a browser to be able to interpret ClojureScript at runtime, which is slow and in most cases doesn't make sense (but maybe you wanna build online REPL, that's ok).
This is not going to be easy. ClojureScript sits on top of JavaScript, the compiler is a library written in Clojure, which in turn hosts on JVM. This means you need Java installed, huh.
I prefer to use sdkman to manage my Java installations.
sdk install java
Next step is to install Clojure, head over to installation guide at clojure.org. If the following command returns 2, you are good!
clj -M -e "(inc 1)"
Create project directory somewhere and put deps.edn file into it with the following contents. deps.edn is kind of package.json, if you are coming from JavaScript world.
{:deps {org.clojure/clojurescript {:mvn/version "1.12.42"}}}
I hope you have Node installed? Now we can run ClojureScript REPL in Node environment. This line runs JVM process that loads Clojure, that loads ClojureScript compiler, starts Node process and runs it in REPL mode.
clj -M -m cljs.main --target node --repl
If this runs, it means you are hooked into Node process, nice!
(js/console.log "hello")
Try to list files in the current directory using Node'js fs module.
(def fs (js/require "fs"))
(.readdirSync fs "./")
It works? Great, let's move onto actually compiling a project into JavaScript files. Put our tiny program into src/app/core.cljs file. src is your project's source root directory. Also note that namespace name app.core (that's how modules are called in Clojure) in the code looks exactly like app/core.cljs. That's a convention in Clojure projects: namespace names should reflect directory structure starting from project's source root directory.
(ns app.core)
(println "Howdy!")
Now let's compile the program into Node script
clj -M -m cljs.main --target node --output-dir ./out --compile app.core
and run the script
node out/main.js
Do you see the output? Great, let's stop here and inspect compiler's output. Specified out directory includes whole bunch of files.
ls out
app cljs cljs_deps.js cljsc_opts.edn goog main.js nodejs.js nodejs.js.map nodejscli.js nodejscli.js.map
The actual compiled JavaScript program from your src/app/core.cljs file is in out/app/core.js, let's see what's inside
cat out/app/core.js
// Compiled by ClojureScript 1.12.42 {:target :nodejs, :nodejs-rt true, :optimizations :none}
goog.provide("app.core");
goog.require("cljs.core");
cljs.core.println.call(null, "Howdy!");
//# sourceMappingURL=core.js.map
cljs.core.println.call(null, "Howdy!"); is your (println "Howdy!"), where println is actually a part of implicit cljs.core namespace, which provides stdlib.
goog.provide and goog.require is a part of JavaScript module format of Google's Closure Compiler. Wait, what? Another compiler? Yeah, you see... ClojureScript compiler takes your source and outputs equivalent JavaScript code. Similarly to out/app/core.js, there's also out/cljs/core.js file which is compiled stdlib of ClojureScript. This just means that ClojureScript compiler is a source to source compiler. But how do we bundle all those JavaScript files into production ready bundle?
That's the job of Closure Compiler. Closure is an optimizing JavaScript compiler that ClojureScript is using since its initial release, in 2011. At the time JavaScript didn't have standard module format, remember AMD, UMD, RequireJS and CommonJS? Closure folks at Google invented another one, where goog.provide declares a module and goog.require imports another module.
ClojureScript emits Closure's module format so that the compiler can pick up and optimize generated JavaScript. Putting everything together, that's the stack you have to deal with:
To plug Closure into the equasion and produce optimized JavaScript bundle run the following command.
--optimizations advanced option is what tells Closure to gather all generated JavaScript files into a single bundle and optimize the program by removing unused code. Closure is quite powerful compiler, you can learn more about all kinds of optimizations it performs in this handbook that I created a while ago.
clj -M -m cljs.main --target node --output-dir ./out --optimizations advanced --compile app.core
Output out/main.js file is now a self-contained JavaScript program.
Let's make something cool now. Put the following code into src/app/core.clj
(ns app.core
(:require [clojure.java.shell :as sh]))
(defmacro version []
(:out (sh/sh "git" "rev-parse" "--short" "HEAD")))
and update your src/app/core.cljs to
(ns app.core
(:require-macros [app.core :refer [version]]))
(println "Howdy!" (version))
Now init a git repo in your project directory and make the first commit
git init
git add src
git commit -m "first commit"
Compile your ClojureScript project
clj -M -m cljs.main --target node --output-dir ./out --optimizations advanced --compile app.core
and run it
node out/main.js
The script should print last git commit hash. Now inspect JavaScript generated from src/app/core.cljs
cat out/app/core.js
The hash is embedded in the code!
// Compiled by ClojureScript 1.12.42 {:static-fns true, :optimize-constants true, :target :nodejs, :nodejs-rt true, :optimizations :advanced}
goog.provide("app.core");
goog.require("cljs.core");
goog.require("cljs.core.constants");
cljs.core.println.cljs$core$IFn$_invoke$arity$variadic(
cljs.core.prim_seq.cljs$core$IFn$_invoke$arity$2(["Howdy!", "7f7e8c3\n"], 0)
);
That's the cool part about Lisps. You can write code in your program that runs at compile time on a program itself. It's almost like a compiler inside of a compiler, except that you don't need special plugins to traverse AST, in Clojure and Lisps in general you create macros, special functions that take code, transform it and return new code.
Now, everything that you learned here is only a part of the story. We haven't touched on the language itself, how real projects are built and how ClojureScript integrates with JavaScript's ecosystem of NPM packages. ClojureScript is definitely not the easiest beast to pick up, but if you are doing front-end development and feel like digging into alternatives and learning from other languages, then there are definitely a few things you can learn from!
For those that don’t know, Jank is a Clojure implementation but instead of targeting Java, it targets LLVM (Low-Level Virtual Machine). That means, Jank compiles to native code, and interops directly with C++.
Jank already have ways to call C++, but I wanted to do the opposite – to call Jank’s code from C. The reason might not be obvious, so here is an why: writing libraries.
Not all things need to be “stand-alone executables”. One example is libraries for Node, for Ruby, or Python. These languages are amazing on the levels of abstraction they support, and it’s easy to interact directly with code and their runtime (in Node, using Devtools, in Ruby, using something like pry or Lazuli, my own plug-in). They are also quite slow, and in some cases, we might need to call some native API that these languages don’t support. So what now? The canonical way is to write some extension in C or C++; now we have to manually manipulate memory and deal with safety issues (and before people say something about it “not being that hard”, it is. Most of CVEs happen because of manual memory manipulation in C – every cast, every printf, every strcpy can cause ACE and/or privilege escalation issues). They are also not interactive so if you’re trying to easily hack some solution, you need to write the code, compile, make a shared library, use the library via the Ruby/Node/Python code, see if it does the thing you want, repeat.
It’s tedious. Maybe with Jank we can speed up this process?
First a disclaimer: Jank currently doesn’t seem to officially support what I want to do. It seems that its creator wants to support the use-case I want later, but right now, this is just a happy coincidence that I can do what I do. So let’s start with a base code:
(ns jank-test) (defn some-code [] (println "HELLO?"))
Save that to jank_test.jank and let’s compile it with Jank, but instead of making an executable, let’s instruct it to make a library with jank --module-path . compile-module jank-test.
This will generate some build files – in my case, in directory target/x86_64-unknown-linux-gnu-6edc6e02e1bf8d875f77f87b5820996901c1894b142485e01a7785f173afb8df/jank_test.o. You might notice that this is not a shared library – as I said earlier, Jank doesn’t really support what I want to do right now but it will in the future. For now, we can either create a shared library from this .o file or we can create a final binary by linking it together with our code. So let’s do this second choice, because it’s easier: you will now create a C++ file containing:
// (1)
extern "C" {
void jank_load_jank_test();
}
#include <jank/c_api.h>
// (2)
using jank_object_ref = void*;
using jank_bool = char;
using jank_usize = unsigned long long;
extern "C" jank_object_ref jank_load_clojure_core_native();
extern "C" jank_object_ref jank_load_clojure_core();
extern "C" jank_object_ref jank_var_intern_c(char const *, char const *);
extern "C" jank_object_ref jank_deref(jank_object_ref);
int main(int argc, const char** argv)
{
// (4)
auto const fn{ [](int const argc, char const **argv) {
// (5)
jank_load_clojure_core_native();
jank_load_clojure_core();
// (6)
jank_load_jank_test();
auto const the_function(jank_var_intern_c("jank-test", "some-code"));
jank_call0(jank_deref(the_function));
return 0;
} };
// (3)
jank_init(argc, argv, true, fn);
return 0;
}
Lots of things here, so let’s go one by one: in (1), we declared an “external” reference. When Jank compiles a code, it’ll generate these jank_load_<namespace> which will do what it’s supposed to do: load the namespace. Unfortunately, it won’t actually load Clojure Core’s namespace, nor it will load any dependencies (I told you this isn’t officially supported yet! You have been warned!). The “external” will be resolved at linker time, and right now it resides only on the .o intermediate file. In (2) we’ll define some data we’ll need to use later in more “extern” declarations. These are used, again, to refer the Jank library that will be linked with the code.
Now, in (3) (which is the second to last line of actual code) we’ll init the Jank runtime. This will boostrap the “Clojure”-ish classes defined in Jank, and we’ll need to pass a fn argument, that is defined previously in `(4). Without this step, you will get a segfault trying to run Jank code, so this is absolutely necessary.
In (5) we will load “Native core” and “Clojure core”, meaning we’ll start the core libraries that are builtin in Jank (native) and these will be used to implement the clojure.core namespace using Clojure code; in (6), we’ll also load our namespace – the one we defined in our .jank file. And finally, after loading this namespace we’re ready to call our function – we’ll first create a “jank var” using jank_var_intern_c, that will essentially resolver to '#jank-test/some-code, and then we’ll deref it to get back the function. We use jank_call0 to call a function with arity 0, and obviously we can use jank_call1 or jank_call2 if the function have arity 1 or 2, for example.
Finally, to compile your final binary:
clang++ \ -L/usr/local/lib/jank/0.1/lib/ \ -I/usr/local/lib/jank/0.1/include \ test.cpp \ target/x86_64-unknown-linux-gnu-6edc6e02e1bf8d875f77f87b5820996901c1894b142485e01a7785f173afb8df/jank_test.o \ -lclang-cpp \ -lLLVM \ -lz \ -lzip \ -lcrypto \ -l jank-standalone \ -o program
So, test.cpp is the code we just created, and target/x86..../jank_test.o is the intermediate file that Jank compiled. The -L and -I point to directories where Jank was installed, -o program instructs the compiler to output to program, and the rest are just libraries that we need to link to generate the final binary. And that is it – running that binary will print HELLO on the screen!
But of course, Jank can do that by itself, so…
Supposing we’re working in some language – for example Ruby – and we want to optimize some code, or integrate with some native library. The canonical way to do that is to use C or C++, sometimes Rust, to make the library. Now, how would we create a class – let’s say Jank – on C++, for it to be usable in Ruby? It’s quite simple, in fact:
#include <ruby.h>
extern "C" void Init_jank_impl() {
rb_define_class("Jank2", rb_cObject)
}
That’s literally just it. Now, suppose we want to send this to Jank, so we will define the class in Jank – how could we do that? Well, it’s also very simple: we will use the same technique in this post, but instead of defining a main code, we’ll keep the extern... code and move all the code that was supposed to be on main to this Init_jank_impl code. Then, on Jank side, we’ll add:
(ns jank-impl) (defn init-extension [] (cpp/rb_define_class "Jank" cpp/rb_cObject))
That’s it. Can we create Ruby methods, and do more stuff with this? Hopefully! But not right now: while I was trying this approach, I found some bugs in Jank, so until these bugs get fixed (which I suspect, based on how fast the language is evolving, will be very fast) we can’t.
But this might even open some very interesting possibilities, that I expect to expand on a future post!
Having hard time figuring out how to build your ClojureScript project? This guide helps you to understand how the code is compiled
Welcome to the Clojure Deref! This is a weekly link/news roundup for the Clojure ecosystem (feed: RSS).
The State of ClojureScript 2025 Survey is live!
If you ever wondered what’s happening in cljs world, this is your chance to contribute and learn back from the community. Take a few minutes to fill out the survey and share it in your circles.
Clojure/Conj 2025: Nov 12-14
Clojure DSP Study Group: Nov 8
Dutch Clojure Days 2026: May 9th. Free registration! The CFP is open until Jan 15th.
Streamed data transformation in JavaScript and Clojure via Iterators and Transducers - Functional Cave
Creating code on the fly using Clojure eval - Clojure Diary
Si-frame presentation - Vincent Cantin
Effective Open Source Maintenance Maintenance (2025) - Peter Taoussanis
Reagent API’s most used functions - mccormix
Apropos with Jordan Miller - November 6, 2025 - apropos clojure
2025 Board Election Results. Annual Meeting October 29. - Kathy Davis
Reagami: a Reagent-like library in less than 100 lines of Squint CLJS - Michiel Borkent
Clojure Runs ONNX AI Models Now - Join the AI fun! - Dragan Djuric
Bioscoop, a DSL for FFmpeg – Clojure Civitas - Daniel Szmulewicz
Rendering surfaces – Clojure Civitas - Tomasz Sulej
Control planes in BigConfig - Alberto Miorin
Which Reagent API Calls are Most Used? - Chris McCormick
Get Ready for Clojure, GPU, and AI in 2026 with CUDA 13.0 - Dragan Djuric
mcp-sdk: an Introduction to creating an MCP service with Clojure - Joanne Cheng
Enabling JavaScript autocompletion for ClojureScript in Cursive editor - Roman Liutikov
Growing explanations together – Clojure Civitas - Teodor Heggelund
OSS updates September and October 2025 - Michiel Borkent
Gaiwan: October Recap - Joanne Cheng
Functional programming, demystified: What we learned at Nu Engineering Meetup - Nubank Editorial
Building Browser-Native Presentations with Scittle – Clojure Civitas - Burin Choomnuan
Funktionale Programmierung - Transducer: Composition, Abstraction, Performance - Marco Schneider
Goodbye Circles, Hello Squircles: Perfect Corners in CSS & Canvas - Pavel Klavík
Horizontals – Clojure Civitas - Tomasz Sulej
Python + ClojureScript: Pyodide Integration with Scittle – Clojure Civitas - Burin Choomnuan
Demystifying the control plane: the easy upgrade path from GitOps with BigConfig - Alberto Miorin
Advanced Beginner’s guide to ClojureScript | Roman Liutikov, Software Engineer - Roman Liutikov
Calling Jank from C - Maurício Szabo
Free Weather Data with National Weather Service API – Clojure Civitas - Burin Choomnuan
Debut release
moon - RPG Maker & Engine
reagami - A minimal zero-deps Reagent-like for Squint and CLJS
aero-1p - Bridge between Aero and 1Password
litellm-clj - A universal translator for LLM models
muutos - Muutos is a zero-dependency Clojure library for reacting to changes in a PostgreSQL database.
webserial-starter - WebSerial API starter with Clojurescript and Replicant
clj-threats - Clojure implementation of Threagile
qclojure-ml - Quantum Machine Learning based on QClojure
DSCloj - Structured LLM prompts in Clojure
clojure-mcp-light - Experimental Clojure tooling for Claude Code - automatic delimiter fixing via hooks and parinfer
Updates
tools.build 0.10.11 - Clojure builds as Clojure programs
statecharts 1.2.24 - A Statechart library for CLJ(S)
fulcro-inspect 4.1.0 - A tool for inspecting and debugging Fulcro applications during development.
fulcro-devtools-remote 0.2.8 - An adapter for writing development tooling that runs as a Chrome extension or an electron app.
test-filter 1.0.6 - A tool for reducing CI times by finding minimal test set based on code analysis.
nexus 2025.10.1 - Data-driven action dispatch for Clojure(Script): Build systems that are easier to test, observe, and extend
powerpack 2025.10.22 - A batteries-included static web site toolkit for Clojure
clj-kondo 2025.10.23 - Static analyzer and linter for Clojure code that sparks joy
component 1.2.0 - Managed lifecycle of stateful objects in Clojure
spacemacs-config 2025-10-25 - rich Clojure & LSP config for Spacemacs
dompa 1.1.0 - A zero-dependency, runtime-agnostic HTML parser and builder.
markdown 0.7.196 - A cross-platform clojure/script parser for Markdown
cli 1.27.121 - Opinionated command line argument handling, with excellent support for subcommands
edamame 1.5.33 - Configurable EDN/Clojure parser with location metadata
thneed 1.1.4 - An eclectic set of Clojure utilities that I’ve found useful enough to keep around.
calva-backseat-driver 0.0.24 - VS Code AI Agent Interactive Programming. Tools for CoPIlot and other assistants. Can also be used as an MCP server.
qclojure 0.23.0 - A functional quantum computer programming library for Clojure with backend protocols, simulation backends and visualizations.
malli 0.20.0-alpha3 - High-performance data-driven data specification library for Clojure/Script.
clay 2.0.2 - A REPL-friendly Clojure tool for notebooks and datavis
kindly 4-beta21 - A small library for defining how different kinds of things should be rendered
durable-queue 0.2.0 - a disk-backed queue for clojure
joyride 0.0.71 - Making VS Code Hackable like Emacs since 2022
reagent 2.0.1 - A minimalistic ClojureScript interface to React.js
sente 1.21.0 - Realtime web comms library for Clojure/Script
http-kit 2.9.0-beta3 - Simple, high-performance event-driven HTTP client+server for Clojure
cider 1.20.0 - The Clojure Interactive Development Environment that Rocks for Emacs
eca 0.77.1 - Editor Code Assistant (ECA) - AI pair programming capabilities agnostic of editor
clojure-mcp 0.1.12 - Clojure MCP
manifold 0.4.4 - A compatibility layer for event-driven abstractions
calva 2.0.540 - Clojure & ClojureScript Interactive Programming for VS Code
cursive 2025.2.1-eap4 - Cursive: The IDE for beautiful Clojure code
In 2025, Nubank appears for the first time among the five most attractive companies to work for in Brazil. The list is organized by Merco Talento, one of the leading corporate reputation and employer brand rankings in Latin America.
Climbing ten positions from 2024 to 2025 reinforces that investing in autonomy, trust, and a clear purpose isn’t just a cultural choice — it’s what drives real impact inside and outside the company.
As our CEO and Founder, David Vélez, puts it:
“Culture attracts people — and people build the products that attract customers. In the end, customers are consumers of culture. […] Culture is like the company’s spirit — it permeates everything we do.”
At Nubank, culture is more than a statement — it’s a living force that shapes how we hire, build, and grow. And this recognition reflects exactly that.
Merco Talento, the Corporate Reputation Business Monitor, evaluates how attractive companies are as places to work based on the perception of different audiences such as university students, professionals, human resources specialists, unions, headhunters and society in general.
In addition to spontaneous recognition, the ranking combines information about corporate reputation, well being practices, professional development, organizational culture and social purpose.
In other words, it is not just an employer branding award. It reflects trust, credibility and the ability to inspire people.
The methodology used to build the Merco Talento ranking includes:
This combination of multiple perspectives is what makes the ranking solid and respected in countries like Brazil, Mexico and Colombia, where Nubank has been recognized before.
Being among the top 5 most desired companies to work for in Brazil is the result of everyday decisions.
We got here because we work with autonomy and responsibility in teams that genuinely trust people and the decisions they make. We create a safe environment to learn, make mistakes and grow, without rigid hierarchies that limit initiative or creativity.
Diversity and inclusion are true foundations of the way we build products, develop leaders and form teams. And everything connects to a clear purpose: to fight complexity and empower people to have more control over their financial lives.
The post Merco Talento Brazil 2025: Nubank ranks among the top 5 most attractive companies to work for appeared first on Building Nubank.
Major cloud providers are solidifying their positions as the backbone for AI development, with AWS and OpenAI partnership cementing a substantial cloud computing deal, further detailed by OpenAI and Amazon cloud deal, AWS supports OpenAI workloads, and CNBC on the deal. Microsoft continues its global expansion of AI cloud capacity through a multi-billion-dollar Lambda AI infrastructure deal and a Microsoft Australia AI cloud deal, alongside a strategic Microsoft UAE AI investment aimed at advancing US AI diplomacy. The sheer demand for AI compute power highlights significant challenges, as Microsoft's AI GPU electricity needs are growing, while Elon Musk controversially suggests leveraging Tesla cars as AI computers for a massive distributed computing network.
Advancements in AI models continue, with Anthropic Claude research making progress and Apple's Siri-Google Gemini deal reportedly exploring integration to enhance personal AI capabilities. Tools for building and monitoring stateful LLM agents are also emerging, exemplified by Agent-O-Rama for LLM agents for Java and Clojure development. Beyond core development, AI is rapidly finding diverse applications, from Coke's AI holiday ad to Elon Musk's Grokipedia, which is undergoing Grokipedia academic assessment, while content creators like PewDiePie self-hosting AI are delving into building custom models.
The expanding reach of AI is fueling discussions around data rights and ethical considerations, with publishers oppose OpenAI training pushing back against OpenAI's use of their work and LinkedIn AI training privacy plans raising concerns and prompting calls for opt-out mechanisms. Business leaders like Sam Altman are addressing financial transparency amidst speculation about Sam Altman on OpenAI IPO and Sam Altman on OpenAI revenue. Concerns about the potential for AI-driven cyber threats are also rising, alongside the ongoing debate about appropriate design and user interaction with AI hardware, as one notable test proposes assessing if AI hardware user experience devices provoke a desire to physically interact aggressively.
Learn how OTTO optimizes search relevance using Learning to Rank – and the role Deep Learning plays for our Data Science team.
We’ve just open-sourced Agent-o-rama, a library for building scalable and stateful LLM agents on the JVM. Agent-o-rama provides two first-class APIs, one for Java and one for Clojure, with feature parity between them.
AI tooling today is overwhelmingly centered on Python, and while the JVM ecosystem has seen growing support through libraries like LangChain4j, it lacks the kind of integrated tooling that lets developers evaluate, observe, and deploy LLM-based systems rigorously and at scale. Available tools are fragmented or complex to set up, and nothing handles the entire workflow from development to production with proper observability.
Agent-o-rama fills that gap. It brings the same ideas popularized by LangGraph and LangSmith – structured agent graphs, tracing, datasets, experiments, evaluation – but makes them native to Java and Clojure. LLMs are powerful but inherently unpredictable, so building applications with LLMs that are helpful and performant with minimal hallucination requires being rigorous about testing and monitoring.









Agents are defined as simple graphs of Java or Clojure functions that execute in parallel. Agent-o-rama automatically captures detailed traces and includes a web UI for offline experimentation, online evaluation, and time-series telemetry (e.g. model latency, token usage, database latency). It also supports streaming, with a simple client API to stream model calls or other outputs from nodes in real time. Agent-o-rama extends the ideas from LangGraph and LangSmith with far greater scalability, full parallel execution, and built-in high-performance data storage and deployment.
Agent-o-rama is deployed onto your own infrastructure on a Rama cluster. Rama is free to use for clusters up to two nodes and can scale to thousands with a commercial license. Every part of Agent-o-rama is built-in and requires no other dependency besides Rama. Agent-o-rama also integrates seamlessly with any other tool, such as databases, vector stores, external APIs, or anything else. Unlike hosted observability tools, all data and traces stay within your infrastructure.
Let’s take a look at an example agent! This is a research agent from the examples/ directory in the project. In that directory you’ll find equivalent Java and Clojure versions.
You’ll need Java 21 installed and API keys for OpenAI and Tavily (Tavily’s free tier is sufficient). Put the API keys in environment variables like so:
1 2 | export OPENAI_API_KEY=your_openai_key_here export TAVILY_API_KEY=your_tavily_key_here |
To run the agent, clone Agent-o-rama and follow these instructions (for Java or Clojure, whichever you prefer):
1 2 3 4 5 6 7 8 9 | # Java instructions cd examples/java ./run-example com.rpl.agent.research.ResearchAgentExample # Clojure instructions cd examples/clj lein repl (require '[com.rpl.agent.research-agent :as research-agent]) (research-agent/run-agent) |
This runs Rama’s “in-process cluster” (IPC) and launches the research agent on it. You’ll get a prompt at the terminal to enter a research topic. The agent will generate a set of analyst personas to analyze the topic, and you’ll be prompted again whether you want to give feedback on the generated analysts. Once you tell the agent you have no more feedback, it will spend a few minutes generating the report, including using information it finds through web searches and through Wikipedia, and then the final report will be printed.
As the report is being generated or when it’s finished, you can open the Agent-o-rama UI at
http://localhost:1974
.
Here’s an example back and forth:
Enter a topic: What's the influence and legacy of Billy Wilder?
Do you have any feedback on this set of analysts? Answer 'yes' or 'no'.
{"role" "Film Historian", "affiliation" "University of California, Los Angeles", "name" "Dr. Lucy Reynolds", "description" "Specializes in post-war American cinema and the contributions of filmmakers like Wilder. Focuses on Wilder's stylistic innovations and narrative techniques, exploring how they shaped modern filmmaking."}
{"role" "Cultural Critic", "affiliation" "Film Critic Magazine", "name" "Michael Chen", "description" "Analyzes the social and cultural impacts of Wilder's films, particularly in relation to gender and race issues. Concerned with how Wilder's work reflects and influences societal norms."}
{"role" "Cinema Studies Scholar", "affiliation" "New York University", "name" "Professor John Hartman", "description" "Investigates the legacy of classic Hollywood directors, with an emphasis on Wilder. His work focuses on the interplay between commercial success and artistic integrity in Wilder's films."}
{"role" "Screenwriter and Director", "affiliation" "Independent Filmmaker", "name" "Emma Thompson", "description" "Explores the thematic elements in Wilder's storytelling, particularly humor and satire. Engages with Wilder's ability to blend genres and how this influences contemporary narrative structures."}
>> no
# The Enduring Influence of Billy Wilder
## Introduction
Billy Wilder's legacy in Hollywood cinema is marked by his unparalleled ability to blend commercial success with artistic integrity. This report delves into Wilder's impact, highlighting his innovative storytelling techniques and social critiques through iconic films like "Sunset Boulevard," "The Apartment," and "Double Indemnity." We explore how his personal experiences shaped his keen observational skills and narrative style, as well as how his work laid the groundwork for contemporary storytelling and the exploration of gender dynamics. Ultimately, Wilder’s films illustrate the enduring relevance of balancing humor, critique, and emotional depth in cinema.
---
Billy Wilder stands as a towering figure in cinema, adept at fusing commercial viability with artistic integrity. His films often strike a delicate balance between engaging mainstream audiences and provoking critical reflection on serious themes, as exemplified in "Sunset Boulevard" (1950). This film vividly critiques the dark side of fame and highlights Wilder's unique ability to craft narratives that resonate deeply with viewers while navigating complex moral landscapes. His background as an Austrian émigré and early career as a screenwriter supplied him with the observational prowess necessary to convey the multifaceted nature of human experiences, allowing his work to transcend mere entertainment and engage with profound social commentary [1].
Wilder's amalgamation of humor and satire serves as a compelling vehicle for addressing serious social issues, influencing contemporary screenwriters to adopt similar techniques. Films like "Some Like It Hot" and "The Apartment" showcase his signature style, where humor enriches the narrative while prompting reflection on societal norms and human behavior. This approach remains pervasive in the works of modern filmmakers, illustrating Wilder's constructed legacy in storytelling that encourages the interplay of comedic elements and deeper thematic explorations. Notable contemporary films such as "The Big Sick" and "Parasite" echo these traditions, suggesting that humor can coexist with critical commentary and profound moral questions [2].
Central to Wilder's storytelling innovations is his ability to meld humor with dark themes, employing non-linear narratives and flashbacks in movies like "Double Indemnity" and "The Apartment." These techniques reveal complex character motivations and provide a framework for rich, layered narratives. Wilder’s knack for sharp dialogue and intricate comedic timing enhances this social commentary, resonating with audiences across generations. The blend of genres within his films also paved the way for a more diverse cinematic landscape, allowing modern filmmakers to challenge conventions and push creative boundaries [3].
Particularly significant is Wilder's exploration of gender dynamics in "The Apartment," where the protagonist Fran Kubelik's experiences reflect the challenges faced by women within a patriarchal corporate structure. The film critiques the objectification of women through key scenes and deft cinematography, simultaneously highlighting moral ambiguity and emotional depth. This examination of gender roles emphasizes the importance of authentic relationships in a transactional world, underlining the resonance of Wilder's critiques within contemporary discussions surrounding gender and power [4].
In conclusion, Billy Wilder's influence is multifaceted, shaping both the narrative and thematic dimensions of modern cinema. His legacy emerges from an enduring ability to captivate audiences while addressing the intricacies of human behavior, societal constructs, and moral dilemmas. Through a unique blend of artistry and commercial appeal, Wilder set a standard for storytelling that continues to inspire filmmakers and storytellers today.
---
## Conclusion
Billy Wilder's cinematic legacy is a testament to his exceptional ability to balance artistry and commercial appeal. His films, including "Sunset Boulevard," "The Apartment," and "Double Indemnity," not only entertained audiences but also provoked critical thought on profound societal themes and human dynamics. Through innovative storytelling techniques and a distinctive blend of humor and critique, Wilder paved the way for contemporary writers and filmmakers. His enduring influence can be seen in the way modern narratives confront gender dynamics and moral complexities, demonstrating that engaging storytelling can exist alongside rich thematic exploration. Ultimately, Wilder's impact remains a vital reference point in the evolution of cinema.
## Sources
[1] Interview with Professor John Hartman on the legacy of Billy Wilder.
[2] https://glcoverage.com/2025/01/23/billy-wilder-screenwriting-tips/
[3] Culture Vulture | Counter Culture
[4] Breaking Down the Storytelling in Billy Wilder's 'The Apartment' https://nofilmschool.com/apartment-storytelling-breakdown
[5] Analysis of ‘The Apartment’ – Infinite Ocean - Mawr Gorshin https://mawrgorshin.com/2022/08/20/analysis-of-the-apartment/
[6] On its 60th anniversary, Billy Wilder’s The Apartment looks like an indictment of toxic masculinity - AV Club https://www.avclub.com/on-its-60th-anniversary-billy-wilder-s-the-apartment-l-1844004988
If you click on the research agent in the UI, you’ll see this:

The invoke there is what we just ran. Clicking on it brings up the trace for the invoke:

This is displaying the parallel execution of the agent, with orange nodes being aggregations of data computed on multiple branches. On the right is aggregated statistics of everything that happened during the agent’s execution. You can see how many tokens it used, and if it did any database reads/writes you’d see stats about those too. If the agent invokes other agents, you can see a breakdown of stats by agent as well.
Clicking on the “write-report” node brings up a detailed trace of what happened when that node executed:

This node did one LLM call, and you can see the arguments to that LLM, what was returned, and stats on the call in the “Operations” section. The code for this node is just this:
1 2 3 4 5 6 7 8 9 | .node("write-report", "finish-report", (AgentNode agentNode, String sections, String topic) -> { ChatModel openai = agentNode.getAgentObject("openai"); String instructions = String.format(REPORT_WRITER_INSTRUCTIONS, topic, sections); List<ChatMessage> chatMessages = Arrays.asList( new SystemMessage(instructions), new UserMessage("Write a report based upon these memos.")); String report = openai.chat(chatMessages).aiMessage().text(); agentNode.emit("finish-report", "report", report); }) |
1 2 3 4 5 6 7 8 9 10 11 12 | (aor/node "write-report" "finish-report" (fn [agent-node sections topic] (let [openai (aor/get-agent-object agent-node "openai") instr (report-writer-instructions topic sections) text (chat-and-get-text openai [(SystemMessage. instr) (UserMessage. "Write a report based upon these memos.")])] (aor/emit! agent-node "finish-report" "report" text) ))) |
This code says that the node’s name is “write-report”, the node emits to the node “finish-report”, and the node’s implementation is the given function. The
agentNode
/
agent-node
argument is how you interact with the graph to return a result, emit to other nodes, or get agent objects like models, database connections, or anything else. When you emit to other nodes, you simply say what node you want to emit to and what arguments to pass to that node. Agent nodes run on virtual threads, so they can be efficiently written in a blocking style like this.
That’s most of what’s involved in programming agents with Agent-o-rama! There’s a bit more to learn with aggregation and how to declare agent objects, and this is all documented on the programming agents guide. The rest of using Agent-o-rama is creating and managing datasets, running experiments, setting up online evaluation and other actions on production runs, and analyzing agent telemetry.
Also, you can see from this code and the trace that model calls are automatically traced – this node didn’t have to record any tracing info explicitly. Though you can include your own info in traces with a simple API (see this Javadoc and this Clojuredoc).
Let’s take a look at running this on a real cluster! Let’s quickly set up a cluster locally by following these instructions:
./rama devZookeeper &
./rama conductor &
./rama supervisor &
http://localhost:8888
. When the page loads, the cluster is ready.
./aor --rama /path/to/rama-root &
Next, to deploy you need to build a jar first. Here’s how to build either the Java or Clojure version from the Agent-o-rama project:
1 2 3 4 5 6 7 | # For Java version cd examples/java mvn clean package -Dmaven.test.skip=true # For Clojure version cd examples/clj lein uberjar |
The Java version will build
target/java-examples-with-dependencies.jar
, and the Clojure version will build
target/agent-o-rama-examples-1.0.0-SNAPSHOT-standalone.jar
.
Next, to deploy the module just run this command:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # Deploy the module (Java uberjar) ./rama deploy \ --action launch \ --jar /path/to/java-examples-with-dependencies.jar \ --module com.rpl.agent.research.ResearchAgentModule \ --tasks 4 \ --threads 2 \ --workers 1 # Deploy the module (Clojure uberjar) ./rama deploy \ --action launch \ --jar /path/to/agent-o-rama-examples-1.0.0-SNAPSHOT-standalone.jar \ --module com.rpl.agent.research-agent/ResearchAgentModule \ --tasks 4 \ --threads 2 \ --workers 1 |
Now it’s up and running! You can view the agent in the UI at
http://localhost:1974
and play with it. From the agent screen you can invoke the agent with the arguments
["", {"topic": "your topic here"}]
. On the trace, you’ll be able to see any human input prompts the agent makes and respond to them there.
Rama handles all of storage, deployment, and scaling. There are no other dependencies needed to run this. Setting up a production cluster is only slightly more work, and there are one-click deploys for AWS and for Azure.
Check out these resources to learn more or get involved:
Agent-o-rama lets developers gain the benefits of Rama without needing to learn it. Rama’s distributed programming model is powerful but has a learning curve: it introduces a rich dataflow API and uses compound data structures for indexing instead of fixed data models. Agent-o-rama abstracts away those concepts into a familiar API so developers can take advantage of Rama’s strengths for the specific domain of building LLM agents.
For those who want to learn how to program Rama directly, Agent-o-rama also serves as a great example of Rama in practice. The backend is about 15K lines of code and the front-end about 11K, yet together they form a complete, end-to-end distributed system with a diverse feature set. Along with our Twitter-scale Mastodon implementation, it shows the breadth of what can be built with Rama.
We’d love to hear what you build with Agent-o-rama. Join the rama-user mailing list or the #rama channel on the Clojurians Slack to ask questions, share feedback, or discuss ideas with others using Agent-o-rama.
If you’d like to talk directly with us about Agent-o-rama, whether to exchange ideas, get technical guidance, or explore working together on building an LLM agent, you can book a call with us.
At Nu Engineering Meetup #15, functional programming shed its “niche” label and became a tangible practice. Alberto Souza, Software Engineering at Nubank and creator of the Dev + Eficiente ecosystem, opened the session by connecting programming paradigms to everyday design decisions.
Rafael Ferreira, Senior Python Developer, educator at Rocketseat, and founder of the Programador Lhama initiative, shared a perspective on functional architecture that begins in theory and translates into testable, predictable code. Marcelo Arbore, Director of Engineering at Oracle Brazil with over fifteen years of experience in hybrid and distributed cloud environments, presented an experiment combining Clojure, Datomic, and Oracle 23AI for vector search and multimodel data applications.
This post brings together the key ideas and addresses a recurring question among those who look at Nubank and wonder if they’d fit in without knowing functional languages.
This question often comes up in interviews, events, and blog comments: “Do I need to know Clojure to work at Nubank?” The answer is as straightforward as the question itself: no. What we look for is curiosity, solid foundations, and the willingness to learn.
The language is a tool that serves the engineering principles we value. This discussion has appeared in community conversations, such as in Alex Miller’s interview on the Hammock Podcast, where he spoke about learning journeys and framed language choices as means, not ends. You can explore that conversation here.
Functional programming is an approach that favors immutable structures, pure functions, and predictable composition. It gives engineers greater clarity about what changes and where those changes occur.
In practical terms, it means that data transformations return new values instead of updating variables in place, that side effects are concentrated, and that the path of data can be read as a pipeline.
An accessible introduction to this way of thinking can be found in Functional Programming with Clojure, which shows how these principles translate into design decisions that make testing, parallelism, and maintenance easier.
Clojure is our main language in many systems. The choice aligns with our emphasis on immutability and our use of history-oriented databases such as Datomic.
This story has already been told in Clojure’s Journey at Nubank, where we detailed the technical and cultural reasons for adopting the ecosystem. The decision doesn’t create an exclusive club—it builds an environment that encourages focus on business rules, clarity of effects, and responsible experimentation.
For those who want to dive deeper behind the scenes, the special series Clojure Turns 15 captures discussions about the language’s evolution and its impact on our daily work.
Many people who joined Nubank had never written Clojure before. During his talk, Alberto Souza shared how he learned from scratch and how functional principles began to influence his code in other languages.
The main message is liberating: paradigms are not dogmas—they are lenses. You can apply immutability, cohesion, and pure functions in Java, Python, or JavaScript, just as you can bring concepts of cohesion and domain-driven design into Clojure.
On the Hammock Podcast, we explore this technical and personal journey in the episode Journeys in Code: Clojure, Datomic, and Personal Growth. The conclusion is that learning the language comes naturally when you have the right context, peers nearby, and interesting problems to solve.
The three presentations at the meetup show how theory translates into practice.
Alberto highlighted how immutability simplifies debugging and reduces bugs by concentrating mutations in single points of the flow. Rafael presented functional architecture patterns in Python, such as service handle and tagless final, which isolate effects and maintain system predictability.
Marcelo, on the other hand, demonstrated an experiment combining Clojure, Datomic, and Oracle 23AI to build a vector search service with embeddings—proof that the functional paradigm can coexist with modern data and AI technologies.
These approaches reflect an engineering philosophy driven by simplicity and clarity. Every decision—from pure functions to declarative pipelines—is designed to keep systems understandable in the long run.
When we say that you don’t need to know Clojure to participate in our hiring process, we’re also talking about how we support learning from the very first day. The technical onboarding is structured to combine product context, experienced peers, and a safe environment to ask questions, explore, and make mistakes.
During the first few weeks, new joiners are exposed to concepts from the functional ecosystem and learn hands-on through pair programming, code reviews, and mentorship. The learning curve exists—and it’s shared.
Working in a functionally oriented environment is about transforming the way you think.
These principles cut across languages and paradigms, explaining why functional engineering is a mindset that shapes how we build products at Nubank.
Functional programming is an invitation to think more clearly about data, effects, and the evolution of systems. At Nubank, we’ve built an environment that encourages continuous learning, collaboration, and safe experimentation.
If you identify with this way of building, this is a great place to begin your journey.
The post Functional programming, demystified: What we learned at Nu Engineering Meetup appeared first on Building Nubank.

New blog post! mcp-sdk: an Introduction to creating an MCP service with Clojure.
Last month we released mcp-sdk, a pure Clojure SDK for working with MCPs. If you&aposd like to create your own MCP service, check out our blog post to help you get started.
Most of our open source projects carry the Lambda Island name. You find them under lambdaisland on github, and under [com.]lambdaisland on clojars. Lambda Island is the name I chose when in 2017 I decided to get into screencasting, making premium video tutorials about Clojure. These videos are still online, nowadays you can watch them all for free.
The first library I released under the same name was lambdaisland/uri, a small utility I extracted from the code base that runs the lambdaisland website. Many more libraries and tools would follow. Kaocha (2018), Ornament (2021), Launchpad (2021), Plenish (2022), CLI (2024), just to name a few highlights.
Since 2019 the maintainance and stewardship of what are by now literally dozens of projects falls upon Gaiwan colleagues and me. This is a collection of open source that Gaiwan proudly shares for the betterment of the Clojure community, but the old name has stuck. I&aposve never been quite sure what to do with that. People would tell me I should rename Gaiwan to Lambda Island to benefit from the name recognition, or go the other way, and migrate all these projects over to the Gaiwan team and organisation. I will agree this confusion of names has not done us any favors.
For me there&aposs always been a clear distinction though. Lambda Island is not an official entity, but if it was, it would be a non-profit. It&aposs our connection to the community, hence why Lambda Island has an opencollective page, or why we run the ClojureVerse forum. There&aposs no commercial motive here, rather it&aposs in our DNA to give back, to share, and to strengthen the community and ecosystem we benefit from. I guess it&aposs my own anti-corporate tendencies that have always wanted to keep that separate from the business, even though Gaiwan is about as indy as it gets. A handful of people running a bootstrapped business.
Lately however we have at last started releasing newer stuff directly under the Gaiwan name, notably our in-progress IAM implementation called Oak. This is a project that distills years of consulting experience, and so it felt right to put our own name on it. A mark of the maker. Oak is also a starting point for us to explore commercial possibilities in the identity space. If that sounds like something you&aposd like to chat to us about, get in touch!
Reset password screenshot from Oak, our IAM implementationArne will do an online talk about The Gaiwan Stack on November 11, 18:30 London / 19:30 CET. Gaiwan has built a lot of Clojure applications over the years, and we&aposve developed an opinionated stack and tooling. It&aposs overdue that we share more of these learnings.
In this post I&aposll give updates about open source I worked on during September and October 2025.
To see previous OSS updates, go here.
I&aposd like to thank all the sponsors and contributors that make this work possible. Without you, the below projects would not be as mature or wouldn&apost exist or be maintained at all! So a sincere thank you to everyone who contributes to the sustainability of these projects.

Current top tier sponsors:
Open the details section for more info about sponsoring.
If you want to ensure that the projects I work on are sustainably maintained, you can sponsor this work in the following ways. Thank you!
The summer heat has faded, and autumn is upon us. One big focus for me is preparing my talk for Clojure Conj 2025, titled "Making tools developers actually use". I did a test run of the talk at the Dutch Clojure Meetup. It went a bit too long at 45 minutes, so I have to shrink it almost by half for the Conj. The more I work on the talk the more ideas come up, so it&aposs challenging!

Of course I spent a ton of time on OSS the past two months as well. Some special mentions:
sci.impl.Reflector code, based on clojure.lang.Reflector was ported to Clojure with the purpose that it would then be easier to translate to Clojure CLR.str in ClojureScript (worst case 4x, best case 200x)Here are updates about the projects/libraries I&aposve worked on in the last two months in detail.
babashka: native, fast starting Clojure interpreter for scripting.
.addMethod to clojure.lang.MultiFnclojure.lang.ITransientCollection for instance? checksreify + equals + hashCode on Objectjava.nio.charset.CharsetDecoder, java.nio.charset.CodingErrorAction, java.nio.charset.CharacterCodingException in support of the sfv librarynrepl-server completions and lookup op to be compatible with rebel-readlineclojure.lang.Ref for instance? checksNO_SOURCE_PATH instead of <expr> since this can cause issues on Windows when checking for absolute file pathsjava.security and allowing setting deprecated Cipher suites at runtime. See this commit.SCI: Configurable Clojure/Script interpreter suitable for scripting
edamame: configurable EDN and Clojure parser with location metadata and more
clj-kondo: static analyzer and linter for Clojure code that sparks joy.
duplicate-key-in-assoc, defaults to :warning:equals-nil linter to detect (= nil x) or (= x nil) patterns and suggest (nil? x) instead (@conao3)defparkingop macro in core.async alpha:interface flag to :flags set in :java-class-definitions analysis output to distinguish Java interfaces from classes (@hugoduncan)requiring-resolve etc.def body, no results due to laziness bug:not-empty? to only warn on objects that are already seqs:ns-groups (thanks @severeoverfl0w):self-requiring-namespace level from :off to :warningdbg from data_readers.clj since this breaks when using together with CIDERdestruct syntaxget and get-in (especially to catch swapped arguments to get in threading macros) (@borkdude, @Uthar):inline-def with nested deftestsquint: CLJS syntax to JS compiler
mapvidentical? callsnat-int?, neg-int?, pos-int? (@eNotchy)randnull and undefined in #html#html escape fixassoc calls, e.g. produced with ->auto-transient)=, and, and not= even morenot= on undefined and false should return trueassoc, assoc! and get when object argument can be inferred or is type hinted with ^objectstr using macro that compiles into template strings + ?? &apos&apos for null/undefinedtake-last should return nil or empty seq for negative numberskeys and vals should work on js/Mapmap-indexed and keep-indexed lazy= when using it on numbers, strings or keyword literals= to a deep-equals implementation that works on primitives, objects, Arrays, Maps and Setsparse-doubleassoc-in on nil or undefineddissoc on nil or undefined:import-maps support in squint.edn (just literal replacements, prefixes not supported yet)reagami: A minimal zero-deps Reagent-like for Squint and CLJS
clerk: Moldable Live Programming for Clojure
unused-deps: Find unused deps in a clojure project
scittle: Execute Clojure(Script) directly from browser script tags via SCI
(.catch ...) was accidentally munched:start attribute to ordered lists not starting with 1 (@spicyfalafel)cherry: Experimental ClojureScript to ES6 module compiler
=, str, not=:macros option + :refer so you can use unqualified macros using compiler state (see macro-state-test)deps.clj: A faithful port of the clojure CLI bash script to Clojure
pod-babashka-go-sqlite3: A babashka pod for interacting with sqlite3
close-connectionget-connection to cache connectionquickdoc: Quick and minimal API doc generation for Clojure
Contributions to third party projects:
str (4x worst case, 200x best case)munge-str public:ns-groups handling by caching regex creation and usageThese are (some of the) other projects I&aposm involved with but little to no activity happened in the past month.