Previously: Old databases are new again
Why implement a REPL? And what the heck is a REPL anyway? Why should I care?
As some remember, classic Frontier had the QuickScript Window: Type some UserTalk, click Run, get the result in a little output area. It was useful, and we used it. A lot!
While working on the new headless version, I found myself wanting that quick loop instead of a series of stateless one-off terminal commands that fired up the runtime and returned a result in Terminal.app. It should be something closer to a browser JavaScript console: interactive, inspectable, aware of context, and fast enough to support real exploration. More importantly Frontier is a stateful environment. I needed the runtime to come up and stay up while I was working with it. That was part of the original DNA and I didn’t have it. (And I didn’t want to wait for a GUI to exist to get it.)
So I built a REPL.
What’s a REPL and why build one?
REPL, if the acronym isn’t familiar, is an old idea: read, eval, print, loop. Terminals are REPLs. The Apple ][ prompt is a REPL. DOS and PowerShell are REPLs. SQL shells are REPLs, and even Swift has one. In some ways AI coding agent harnesses and AI chat interfaces are REPLs too.
This turned out to be more than a convenience. Implementing a REPL for Frontier was the moment I stopped thinking only about porting the system forward and maintaining backward-compatibility, and started thinking deeply about what would make it useful in the modern era beyond (eventually) having a GUI that could connect to a remote headless instance.
Before this, interaction was awkward: I could run Frontier in the terminal as a one-shot process. I could pass it a UserTalk expression or verb call and get a result. I could move legacy .root files over from Windows and migrate them to the new 64-bit format. I could interact with the object database indirectly through file-based mechanics. But none of that was really interactive – instead it was iterative, painful, slow, and error-prone.
This was a real problem when I was doing database migration stabilization. I needed to inspect tables and objects, verify that migrated data was sane, and do it fast enough to have a flow. I already had hundreds of unit tests and was building a suite of integration tests to validate UserTalk functionality, but automated tests only get you so far. They tell you something failed, but they don’t give you a leg to stand on while you figure out what the hell went wrong when something breaks.
What did I actually build?
The first version of was basically QuickScript in the terminal with scrollback. That was very useful but didn’t bring me much joy if I’m being honest here. First I wanted more interactivity, so next I added a /list dot.path.to.table command to show the contents of a table, and then a /jump dot.path.to.table command to move to a different table (roughly analogous to Frontier’s original Jump/Cmd-J command), with the current table’s dot-path shown in the REPL prompt. That mattered more than I expected: When the prompt shows where you are, it stops feeling like a generic shell and starts feeling like Frontier, albeit just a little bit.
That immediately changed the way I worked on and tested the headless app. I could inspect objects directly. I could do string (path.to.object) and see the full contents of a script or text object instead of squeezing output through a tiny one-line field like we did in days of yore. I could run verbs, see which ones failed, get a line number, and then go inspect an exported script as text on disk to get an idea of what had gone wrong. (I’d already exported all the script code in Frontier.root to text files.) Even now I’m working today on tightening that loop by adding surrounding-line context directly in the REPL so you can see the context of an error, not just the error message and line number.
How AI-assisted coding plays in
The basic REPL “bring-up” was actually pretty easy to do with Claude Code. I explained what I wanted and the first rev was done in 30 minutes or so. The harder parts came later…
Context tracking for implementing the /jump command was a relatively small lift. Log spew was a bigger issue: The REPL is only useful if you can read it without a bunch of noise, and there was way too much noise. Cleaning that up actually became a broader logging upgrade effort spanning a few days: I built a logging framework with real stdout/stderr discipline, documented how it worked, and added agent policies so they wouldn’t casually pollute the interactive surface going forward. After that came persistent REPL-local variables, essentially a REPL runtime stack that survives across commands. That was a little tricky, but I benefited from knowing the UserTalk runtime and object database really well and was able to do it quickly once I understood how it should work.
The hardest part, and still unfinished, is dynamic menus…
Frontier always had (at least while I worked with it) a menu bar that would dynamically change depending on what you were doing: Which object am I editing? What type is it? What tools do I have installed? Is the webserver running? What custom commands does this user have?…
I don’t want headless Frontier to lose that part of its original character. So I started building a text-based menu system that relies on Frontier’s native menubarType and dynamic menu system, which is mostly implemented in UserTalk code triggered from kernel-level callbacks. It’s invoked with the ‘/’ key the way VisiCalc did it (prior art is super-important!): The slash key brings up a horizontal textual menu and arrow keys or letter keys navigate/activate items and sub-menus. (I wrote about VisiCalc’s menus being carried into Excel in 2013: The Longest-lived Shortcut Key In Personal Computing)
The Role of AI
AI helped a lot here, with the biggest lifts being code archaeology, test scaffolding, refactoring and removing dead code, and raw leverage. At that stage my workflow was still pretty interactive and serial: one job at a time, lots of back-and-forth, occasional mistakes and back-pedaling. Most of all, learning what works and what doesn’t.
The way I work with these things is way more structured now: Typically I do short planning sessions (not always in plan mode), keep a well-maintained backlog, extensive docs and policies, and hooks that prevent breakage, and lots of custom skills at both a global- and project-level that encapsulate how I like to work, and how my way of working works best with AI agents.
The shift in thinking has enabled larger mostly autonomous workflows with the AI notifying me when it needs help or gets stuck. And I’m still tuning the process continuously. The tools don’t arrive pre-shaped to your standards. You have to shape them or you’ll find yourself frustrated with inconsistent results and a spaghetti-mess of code that you can’t easily reason with, much less fix. (20 years as a TPM is showing its colors here!)
The part that matters most: The real story here isn’t “AI helped write some code.” It’s all about codifying workflows, and for me this ended up translating to:
- Plan first at a high level, review, and iterate on the plan
- Make an execution plan if complexity warrants it
- Create a worktree and branch to isolate parallel work and protect the main branch
- Write tests first (test-driven development / TDD)
- Implement and iterate until tests pass
- Run the full suite to catch side-effects and regressions
- Push a PR as a checkpoint on an atomic work unit
- Run a review loop, fix all of the serious issues, and track the rest
- Merge and clean up the worktree
- Sync the main branch locally and do a human smoke test
That’s the system that lets me move quickly without turning the codebase into mush. And now I have skills that implement each step, and skills that wrap them into logically related loops, and even more skills that wrap the whole thing in autonomous chains that only interrupt me when the agent needs help, keep running notes as they go in a place I can see them, and enable me to work many times faster than I was when I was implementing the REPL.
This was an early major milestone for me with agentic coding. Not because it was a huge amount of code, and not because it did something I couldn’t have done myself. But because it changed the frame for me about what was possible with these tools, and what a headless Frontier could become.
Up to that point I was mostly asking: Can I get Frontier to compile, run, and survive on modern systems with real backward compatibility? An important question to be sure. But once the REPL existed, a bigger question appeared: How do I make command-line Frontier genuinely useful on its own in a modern context?
And once I started asking that, the next question showed up immediately: What role could Frontier play in the agentic world we’re entering now, and how do the ideas from the prior Frontier era apply in this world?
That led to other things later, like a protocol layer that would be useful both for future UI work and for AI agents to interact with Frontier directly. But that’s another post. 😉
This was the moment the project stopped feeling like a port and started feeling like it could grow up to be a truly modern tool.
More soon…
Ps. If you work with Claude Code or Codex and need help let me know. I want to figure out how to make this way of working easier for others, but please also be mindful about the fact that I only have so much time in a day and Frontier is not my only high-bandwidth project.

