i develop mesh wifi at amazon. part of what we do (and i won’t go into the proprietary bits) involves reacting to radar strikes. dynamic frequency selection says that certain 5GHz channels are shared with radar, and if your access point detects one, it has to vacate the channel inside a specific time budget, sit out a non-occupancy period, run a channel availability check on the new one, and only then start transmitting again. that whole thing is an FSM (because an arrow diagram quite literally exists). but the formalism isn’t a metaphor i am picking up to explain the construct. it’s the spec. we read it as an FSM because it is an FSM, and once you internalize that, you start seeing the same shape everywhere and it will start getting to you.
take a look at TCP; CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT → TIME_WAIT → CLOSED, so basically an FSM implemented identically across every OS stack, because the protocol is the FSM. the handshake isn’t bare policy on top of math. it is the math. even regular expression engines compile to NFAs and then DFAs. lexers are FSMs scanning tokens. raft (used in kubernetes’ etcd) is three states (follower, candidate, leader) and the correctness proof is about which transition sequences are reachable from the start state. you can implement raft in any language; the FSM is the spec. and FSMs are the bottom rung.
this matters when you’re engineering anything that decides what to do next. in theory, an FSM-shaped system is analyzable, verifiable, cheap. you get guaranteed termination, decidable emptiness, minimization. in short, a system that’s turing-complete when it didn’t need to be is a system that can hang forever. but the question that remains is how much power we can get lax with. 9 out of 10 times, i’ve caught people over-engineering. (or simply put, 90% of present-day software is over-engineered; probably more)
i’ve started to have better days when i focus on determinism.
take AI agents for example. as i type, almost every one of them is ruminating in a loop: model receives input, model picks a tool, tool runs, output goes back to model, repeat until your model consummates. this loop is turing-complete by construction i.e. the model can call anything, in any order, as many times as it wants. sadly, also including ways that never terminate. so people build these rube goldberg–style onboarding flows and customer support bots and lead qualification agents on top of this machinery and act surprised when their infra hallucinates a tool call, loops forever, or takes a trajectory nobody anticipated.
most of agentic use cases are not actually open-ended; they are almost always bounded. honestly, an onboarding flow couldn’t be more than “collect email, verify, collect name, collect preferences, hand off.” six states, ten transitions. it’s an FSM. the moment you draw it as one, it gives you the ability to enumerate every trajectory, write deterministic tests, bound the number of LLM calls, and prove it can terminate. but the symmetric mistake is also high stakes because a coding agent cannot FSM-shape “build me a feature.” so it’s turing-complete by necessity (not by chance), and the cost is exactly what the theory predicts, basically undecidable halting, unbounded cost, behavior that can only be tested probabilistically which makes me probe whether maybe that’s the right tool for that job.
so, do i adopt determinism for soundness or non-determinism for results that will barely survive the keynote. unlimited token window is the fastest selling lie and only inflating non-determinism, which is a territory better unexplored. people forget that determinism is a feature, not a limitation. i think the version of this i want more distinguished vibe coders to internalize is that “i drew this as a graph” is affirmative to “i hope the model does the right thing this time,” because the proof is pen and paper. it’s about the guarantees you earn by understanding your problem well enough to bound it. most problems are bounded. if there is no DFA, you gave up too soon.
the place this bites hardest is evals. and yes, evals on a non-deterministic system are themselves non-deterministic, which is the obvious objection. the phrase “inherently non-deterministic” (with depressing regularity) is doing a lot of work in conversations where the speaker hasn’t actually tried to pin the model version, fix the seed, lock the system fingerprint, freeze the precision (because bf16 and fp16 will not produce bitwise-equal outputs hence an A100 and an H100 will disagree at the bit level on the same kernel), and force static batch sizes instead of continuous batching. that gives you reproducible inference. then you do trajectory snapshotting, which is the part i think more teams should steal from systems engineering. capture the entire trace of a run as an artifact, every input, tool call, intermediate output, retrieval chunk, the whole world state, and treat it like a rootfs you can mount and replay. when you change a prompt or swap a model, you only re-run against the snapshot, and the diff is the eval (and not the output). the non-determinism that’s left is confined to the actual transitions you’re testing, which is where statistics belongs anyway. and if the agent is FSM-shaped to begin with, you can enumerate trajectories exhaustively instead of sampling (in short, we detest pruning).
so the through-line of all of this is the same idea applied at every layer. you don’t have to get rid of non-determinism. but maybe attempt to push it into the smallest box it’ll fit in, and you instrument everything outside that box like you mean it. determinism is what’s left when you’ve done that honestly.