Jake Savin

“Have no fear of perfection: you'll never reach it.” – Salvador Dalí


A new Frontier: An Inscrutable Tangle of State Management

Previously: Frontier in the Terminal With a REPL

By this point, the easy parts were mostly already over. Frontier was no longer a pile of old source to coax into compiling. It was running headless. It could load old databases. It had a new REPL so I could do basic command-line interactions, and more and more was becoming available at the terminal. From the outside, it might have looked like the project was heading quickly towards production-quality: get the runtime working, make old data survive, bring more features online, make the environment more livable.

That was all true. But it was also when a different kind of problem started looming, one I’d anticipated but didn’t whose scale I hadn’t understood: Global state.

I started repeatedly running into crashes and bugs that didn’t really belong to any one feature: A threading change would expose a problem in database access. A database migration fix would uncover assumptions in the runtime about where data was being written. Over and over again, cleaning up code in one area would reveal yet another place where the code was quietly depending on some hidden notion of the “current” context. Fundamentally this was a relic of the era in which Frontier was created…

Imagine a time not too long ago where those assumptions were perfectly reasonable. Frontier had been built as a fundamentally single-threaded application, with cooperative multitasking (meaning only one thread actually executes in a given time-slice) and the app maintained a lot of shared runtime context which would be swapped in and out depending on which thread or script had control at a given moment. In this kind of system, it’s natural to have implicit “global” ideas about what the current database is, which table is in the frontmost window, what outline is currently being edited (or targeted) – essentially the entire execution context. Frontier more or less had one center of gravity, and a lot of the low-level code was written with the assumption that it could always work this way.

But once I had the runtime doing real work, these assumptions started showing up everywhere.

Some of the problems were concrete and easy to describe. Thread-related work would reveal that execution state wasn’t as isolated as it needed to be. Database migration operations would turn out to depend on context from a prior operation instead of explicit context for the current one. Code that looked innocent on its own became hard to reason about when the stack got deep or components were nested. When one piece of work interrupted another, or when a feature that had once lived safely inside the desktop app was now being asked to behave like part of a long-running headless runtime, many of the old assumptions simply broke.

I started spending a lot of time not adding flashy new capabilities, but unwinding old assumptions. After recognizing that a pervasive “push-pop” pattern that was used for managing in-memory and on-disk database context was frequently breaking things, I finally established a pattern for eliminating it, first for tables, then for outlines, then for scripts and all other “external” (non-scalar) types: Instead of relying on global state, deterministically pass the state as a struct all the way down the call chain. The function signatures got longer, but the code became predictable.

At one point not long later, I said to Claude Code, “We need a plan for how to do a broad refactor to get rid of this anti-pattern everywhere. Please review all the components used in the headless Frontier runtime, and write a comprehensive plan for how to completely eliminate the push-pop pattern. Burn the globals! With fire! Go!” (Guess what the planning doc was called. 😜)

What this entailed beyond ridding the code of the push-pop anti-pattern included tightening thread behavior so different bits of work couldn’t casually step on each other, by introducing a “thread-globals” concept in the kernel. It meant making database handling un-magical and explicit, so code that operated on the database it was actually meant to operate on, instead of whatever one happened to be in the ambient global state at a given moment. It meant tracing save-and-restore patterns that made sense years ago, but had become fragile now that the state that used to be managed by the OS’ window manager was out of the picture.

None of this was glamorous. It’s definitely a harder sell than “look, it has a REPL now,” and it’s the kind of technical debt that would most likely be overlooked in an Enterprise or rapidly-growing startup context. But this is some of the most important work of the whole project. It changes the kind of confidence we can have in the runtime itself. And it’s been gratifying to see some of the tech-debt that we were struggling with even all the way back in 2004 is now being addressed.

Before this it was all about proving that the kernel could compile and run, and that the script parser and compiler did their thing. The goal shifted from this being a fun and adventuresome side project, to finding a real path for running Frontier-based production workflows again in the modern era.

That distinction matters.

“Works” is not a high bar. The real question is whether one can keep building on top of a stable foundation. Whether fixes stay fixed. Whether context is legible. Whether the state is in places you can actually understand, instead of smuggling meaning through old shortcuts that only made sense in an earlier era.

The deeper battle during this period was this: On paper, the project was still moving forward feature by feature: threads, database behavior, runtime cleanup, terminal usability. But underneath, a lot of that work was really converging on the same thing: The runtime had to move from implicitly managed to explicitly managed state. Once that was clear I couldn’t unsee it.

In retrospect this all seems obvious, but in reality it took weeks of running into the same class of problem from different angles, fixing one manifestation only to find another, over and over again. In the end, what looked like a scattered series of bugs was really one long encounter with inscrutable hidden state.

All of the more recent progress has depended on winning that fight first.

Burn the globals! With fire! 🔥



One response to “A new Frontier: An Inscrutable Tangle of State Management”

  1. […] Next: An Inscrutable Tangle of State Management […]

Leave a Reply

Discover more from Jake Savin

Subscribe now to keep reading and get access to the full archive.

Continue reading