I put Temporal's Rust SDK 1.0 on a conference badge

AUTHORS
Shy Ruparel
PUBLISHED
Oct 06, 2026
DURATION
11 MIN
  • Rust
  • Durable Execution

Our Rust SDK team asked about sponsoring RustConf. I wanted to say yes with something better than a table and a banner, partly because that team had put a lot of their own time into helping me get the original Replay 2026 badge out the door, and I owed them one. Building strange IoT demos is a fairly regular part of my job, so the shape of my answer was predictable.

So I submitted a talk: “One Button, 64 LEDs, One Durable Workflow: Running Temporal on an ESP32-S3 Badge.”

None of that existed yet. There was no Rust Worker on the badge, no trivia Workflow, and no button wired to an Activity. The 8×8 LED matrix was in the title because it sounded good, and I then forgot about it until the game was already running. It got added late. A correct answer flashes a checkmark twice, a wrong one flashes an X twice. Accurate conference title achieved, barely.

What I did have was a Replay 2026 badge, early access to preview builds of the Temporal Rust SDK, and a question worth answering before anyone starts thinking about a Replay 2027 badge: can the badge itself be a Temporal Worker, or would I have to hide the interesting parts behind an HTTP proxy?

We’d set the Rust SDK 1.0 release for the start of RustConf so we could announce it at the conference, which gave my experiment a deadline I couldn’t move.

It didn’t have to do much#

The ask I made was small: get me a Rust firmware build with the Temporal SDK compiled into it. It didn’t have to play a game or look like anything. I wanted to watch a Worker poll a Task Queue from a device in my hand.

Then I decided to bring ten badges to the Temporal booth and let attendees play on them for two days, and the requirements grew to match.

The hardware was the mass-produced Replay 2026 badge: an ESP32-S3 with 16 MB of flash, 8 MB of PSRAM, four buttons, a 128×64 OLED, and the LED matrix I’d temporarily forgotten. None of that was chosen for this. It was chosen to be a conference badge.

Writing an HTTP client would have been easier, but I wouldn’t have learned anything new from it. I wanted the badge running the real SDK: authenticating to Temporal Cloud with an API key over TLS, polling for work, heartbeating while a person thinks, and completing an Activity when a thumb hits a button.

Compiling was the easy half. The other half was ten badges surviving two days of strangers without turning the booth into a firmware repair station.

Where the durable state actually lives#

The finished project is Durable Trivia, a two-minute game where a fleet of badges races to answer as many questions as it can.

“Running Temporal on a badge” is easy to misread, so here is what actually runs where.

A MacBook plugged into the conference display runs the Workflow Worker and the game controller. Temporal Cloud holds the Workflow state and coordinates everything. Each badge is a real Activity Worker built with the Rust SDK, and questions are Activities.

The Workflow owns the timer, the shuffled question deck, the scores, the power-ups, and the final result. A badge polls the shared Task Queue, draws its assigned question on the OLED, and heartbeats while it waits for someone to press one of the four buttons. The press completes the Activity, and the Workflow applies the score and schedules more work.

That split gave me a very literal failure demo. Hold LEFT and RIGHT for half a second, and the badge simulates a Worker crash by stopping its heartbeats. Temporal’s 15-second heartbeat timeout expires, and the unanswered question becomes eligible for retry on another badge. The Workflow still knows the question, the deadline, and everyone’s score, because none of that lived on the Worker that died.

Crashing your Worker became a game mechanic. If a question looked too hard, you could panic and hope somebody else dealt with it.

Tokio was the real constraint#

This is the part I expected to kill the project.

The most stubborn server-side assumption lived in the runtime underneath Temporal’s programming model. The Rust SDK expects Tokio, which on a server is an unremarkable choice. On this badge, a single current-thread Tokio runtime was responsible for the Temporal Worker, TLS, Wi-Fi, buttons, heartbeats, the OLED, and the LED matrix. One blocking call could stall the whole thing.

So I kept the Tokio surface deliberately small:

tokio = { version = "1", default-features = false, features = ["macros", "rt", "sync", "time"] }

The first unmodified SDK build tripped over Unix signal APIs that ESP-IDF’s libc doesn’t provide. Once the toolchain and build-std setup were right, that turned out to be a genuine portability boundary rather than a misconfigured environment. That distinction took a while to establish.

From there the changes stayed narrow. The badge uses fresh vendored copies of the official 1.0 crates with four ESP-IDF compatibility gates:

  1. Tokio’s desktop-only process support is enabled only for the ephemeral server feature that needs it.
  2. portable-atomic supplies the 64-bit atomics Xtensa is missing.
  3. Desktop hostname and platform discovery get embedded fallbacks.
  4. The unused Workflow-history JSON parser is excluded on ESP-IDF, because its float deserializer still triggers an Xtensa LLVM instruction-selection crash.

That last one is my favorite kind of embedded error. Not “this API is unsupported,” but an LLVM backend giving up while compiling code the badge would never execute.

Four problems. I had assumed a hardware surface this small would fight me on ten separate things and that I would end up writing the HTTP client after all. It fought me on one library and a compiler backend.

This isn’t an announcement that ESP32-S3 is a supported Temporal deployment target. These are local compatibility patches, not an embedded fork of the SDK. Most of the SDK I never touched at all.

The production 1.0 firmware image came out at 8,444,688 bytes, or 57.52% of the badge’s application partition, and only 16,448 bytes larger than the 0.7-based image it replaced. That image holds a server-oriented Durable Execution SDK, a TLS stack, the game client, display code, and input handling, with room to spare.

Getting to that boundary took several rounds of fixing the ESP Rust environment and chasing compiler errors. Once I understood where it was and settled on the narrow patches, a Worker on the badge was polling Temporal Cloud within a couple of iterations.

The prototype was easy, humans were harder#

I have still never written Rust from scratch. I used Codex for this whole project, and it covered for the fact that I don’t know the language. I picked the experiment, the architecture, the game, and what counted as working. Codex read the build failures, wrote the compatibility patches, and built firmware. I held the badge, pressed the buttons, described what I was seeing, and said whether it worked.

During an early test, I pressed the wrong answer. Not because the input mapping was broken. I just didn’t know the answer to the trivia question. That made it obvious my test loop couldn’t depend on a human knowing arbitrary trivia on demand. I added a USB HIL feature that injects answers and inspects badge state, and kept it behind a compile-time feature. The production build explicitly asserts the test protocol isn’t present. Humans use the physical buttons; the automation exists to prove the path works before a badge goes to an attendee.

Another test looked like a connectivity failure. Two badges were polling and only one ever got a question. Wi-Fi was fine, and so was Temporal Cloud. The Workflow was intentionally holding one Worker in reserve for retry recovery, a policy that made the failure demo work and made the normal game look busted.

I changed new rounds to keep work available for every connected badge. Because Workflow code is replayed from durable history, changing the command sequence meant adding a Temporal patch marker so older games kept their recorded behavior while new games used the new scheduler. A trivia game for a conference booth had produced a Workflow-versioning problem, and I enjoyed that more than was strictly reasonable.

Tim Bruce, who wrote the Rust SDK, later sent a PR that fixed a bunch of edge cases I hadn’t hit yet: the input state machine, shared configuration, identity handling, and tests that are sensitive to replay. The rest of the Rust SDK team was getting 1.0 over the line while I was off finding strange places to run it.

Then I optimized the battery#

The Wi-Fi credentials are compile-time constants, baked into the firmware by build.rs next to the Temporal address, namespace, and API key. Switching networks means rebuilding and reflashing all ten badges, which is not a thing I wanted to be doing on a conference floor in Montreal. So I flew up with a Ubiquiti travel router in my bag and brought my own SSID with me. As far as the badges knew, they never left my desk. That router has quietly become the most useful piece of hardware I own. It also ran the robot arm I took to an AWS conference a few weeks ago. The venue network is the part of a hardware demo you control the least and can lose the most to.

By the time RustConf arrived I had ten badges flashed and verified for the booth. They joined Wi-Fi, synced time, showed up in Temporal’s Worker roster, polled the Task Queue, answered questions, and flashed their matrices right side up. I had host tests, hardware tests, and a production-image gate.

Then I made a completely reasonable optimization: automatic idle sleep, to save battery. Five minutes without a question or a button press and the badge would go to sleep. The behavior had tests, the firmware built, and I flashed four badges and watched them rejoin Temporal.

What I did not do was sit and watch one badge sleep and wake up, start to finish, before moving on to the rest.

My coworkers found that missing acceptance test for me:

Marcus Merrell 2:01 PM hey there–all the badges have gone to sleep and I don't know how to wake them up... restarting the process didn't seem to help

Shy Ruparel 2:01 PM Just touching any button on them should turn them back on

Marcus Merrell 2:01 PM hmm... it's not

Shy Ruparel 2:01 PM They have a 5 minute sleep idle on them Ah crud

Marcus Merrell 2:02 PM hang on–they. might be ok, I was being impatient took like 10 seconds--I'm a spoiled, spoiled man

Ten seconds of wake time is fine in isolation. It’s not fine when someone is standing at a booth wondering whether the demo is broken. An hour later I got the same report from someone else:

Greg Travis 2:59 PM The badges seem to not want to wake up

Shy Ruparel 3:00 PM Marcus was saying you have to hold them down for a few seconds? I can flash them again to take off the sleep setting when I'm done with calls? You can also give them power and they should turn on too

I was on calls. The badges were asleep. I pulled the automatic sleep out, kept the deliberate three-second button gesture for manual sleep, rebuilt the production image, and reflashed the badges during the conference.

Getting a Rust Worker onto an ESP32-S3 turned out to be far more approachable than I expected. Making ten of them boring enough to survive two days of booth play is where the rest of the time went.

Go build something unreasonable#

The Temporal Rust SDK is 1.0. You probably shouldn’t start by putting it on a conference badge.

Start with the Rust SDK documentation and build something that runs on hardware with a wall outlet, a normal operating system, and no coworkers asking you how to wake it up.

Then try something unreasonable. Durable Execution gets interesting when Workers disappear. Usually that’s a container restarting. Sometimes it’s somebody holding LEFT and RIGHT because they don’t know the answer to a Rust trivia question.

Temporal Cloud

Ready to see for yourself?

Sign up for Temporal Cloud today and get $150 in free credits.

Build invincible applications

It sounds like magic, we promise it's not.