Vrs: Personal Software Runtime inspired by Emacs, Plan 9, Erlang, Hypermedia

2025-05-2716:1913324github.com

A Personal Software Runtime inspired by Emacs, Plan 9, Erlang, Hypermedia, and Unix - leoshimo/vrs

In the multiverse, you can live up to your ultimate potential. We discovered a way to temporarily link your consciousness to another version of yourself, accessing all of their memories and skills.

It's called verse jumping.

— Alpha Waymond

vrs is a personal software runtime - an opinionated take on my own "endgame" software platform.

It considers every aspect of programming - language, execution, environment, editing, tooling, and more - designing across the end-to-end process of building software.

Its inspirations are Emacs, Erlang, Unix, Plan 9, and Hypermedia systems. It hopes to combine powerful ideas from those projects into one cohesive whole for an empowering, joyful, holistic programming experience.

The key principles are: joy, uniformity, simplicity, practicality, and interactivity.

🚧 Under heavy construction 🐉 Here be dragons

vrs is a sandbox project, focused on play and experimentation in a pure-fun, pure-utility environment. While I live-on vrs everyday, the platform is very volatile in both concepts and implementation.

This software has rough edges. Be warned!

  • lyric: Embedded Lisp Dialect and Virtual Machine
  • vrsd: A runtime implementation as a system daemon
  • libvrs: The vrs library crate shared by runtime and client implementations
  • vrsctl: A thin CLI client over libvrs
  • vrsjmp: A GUI launch bar client

The runtime runs software written in Lyric lang:

# Use `def` to define new bindings
# e.g. "hello lyric!" string to symbol `msg`
(def msg "hello lyric!")

# Update bindings with `set`
(set msg "goodbye lyric!")

# Basic Primitives - integers, lists, keywords, and more
42                                # integers
:my_keyword                       # keywords start with colon (:)
true                              # booleans are `true` or `false`
(list msg var_number var_keyword) # create new lists with `list` function
'("a" "b" "c")                    # quote expression with '

# Function declarationes use `defn`
# Lyric is expression-oriented - last form is returned as value to caller
(defn double (x)
    (+ x x))
    
# Call functions by using bound symbol names within parens, followed by arguments
(double 10) # => 20

# List Operations
(def l '(1 2 3))
(def first (get l 0))       # get 0th item in `l`
(def last (get l -1))       # get last item in `l`
(contains? l 3)             # check if `l` contains `3`

# Association Lists
(def item '(:title "My Title" :subtitle "My Subtitle"))
(get item :title)      # => "My Title"
(get item :subtitle)   # => "My Subtitle"

# Functions (Lambdas) are first class
(defn apply (x fn)
    (fn x))
(apply 41 (lambda (x) (+ x 1)))        # => 41
(map '(1 2 3) (lambda (x) (+ x x))     # => '(2, 4, 6)

# Conditionals with `if` - equality with `eq?`
(if (eq? msg "Hello")
    "msg was hello"
    "msg was not hello")

# and flip conditions with `not?`
(if (not? false)
    "it was not true")

# Catch error with `try`. Introspect result with `err?` or `ok?`
(if (err? (try (not_a_function)))
    "failed to call not_a_function")

# Pattern match with `match`. `_` is a wildcard pattern.
(def result '(:ok "Successful data"))
(match result
    ((:ok msg) msg)
    ((:err err) (:err err))
    (_ '(:err "Unrecognized result")))

# Destructuring bindings can be used to pattern match against forms:
(def result '(:ok "Success"))
(def (:ok status) result)      # matches :ok, binds status to string "Success"

# As a Lisp, Lyric has `eval` and `read`:
(eval (read "(+ 40 2)")) # => 42

# and there are more builtins and symbols in environment, introspectable via `ls_env` and `help`
(ls_env)           # see all symbols defined in environment
(help recv)        # see documentation via `help`

TODO: Examples for fibers, coroutines, yielding, infinite iterators, macros

In VRS, software runs as processes running Lyric lang.

These processes are implemented as green threads, and are lightweight compared to OS processes. Processes are scheduled on multiple cores using nonblocking IO.

Each process has a single logical thread of execution. CPU-bound and IO-bound work is transparent at process level, but the runtime schedules work such that a IO or CPU-bound work do not block cores.

While processes are preemptively scheduled, each process can create fibers, which can be used for cooperative multitasking, coroutines, infinite generators, etc within a single process.

Millions of processes can run on a single machine, without a single process halting the system altogether.

The low cost of processes allows it to serve as a single abstraction to simplify typical event-based, callback-based, or scheduling idioms used in building software.

For example, annual jobs can be represented as a infinite looping program that sleeps for a year:

(loop (sleep (duration :years 1))
      (do_a_thing))

And user flows can be represented sequentially, without blocking the "main thread":

(def query (prompt "Enter search term: ")) # block on user response
(def items (search_items query))           # network-bound query
(def selection (select items))             # block on user selection

Processes run in isolated environments from one another - symbols bound in one process cannot be seen by another process. The only method for communicating between process is via message passing, covered below.

# See list of running processes in runtime
(ps)

# See this process's process_id
(self)

# Spawn a new process
(def echo_proc (spawn (lambda ()
    (def (sender msg) (recv))
    (send sender msg))))

Processes are isolated - and communicate through message-passing.

Each process has a dedicated mailbox that it can poll to receive messages:

# See messages in mailbox, without blocking or consuming a message
(ls_msgs)

# Poll for new message. This blocks execution until a message is received:
(recv)

# `recv` can poll for messages matching specific patterns
(recv '(:only_poll_for_matching msg))

# A common idiom is a "service loop" - an infinite loop that recv messages and runs some function within the process:
(loop (match (recv)
    ((:event_a ev) (handle_a ev))
    ((:event_b ev) (handle_b ev))
    (_ (error "unexpected message"))))

# Sending messages is done via `(send PID MSG)`.
# Use process id from `(self)`, `(pid PID_NO)`, and `(find_srv SRV_NAME)`
(send (pid 10) :hello)
(send (self) :hello_from_self)

# Message from child back to parent
(def parent_pid (self))
(spawn (lambda ()
    (sleep 10)
    (send parent_pid :hello_from_child)))

Services are long-running processes that:

  • are discoverable via name in service registry
  • process messages in mailbox, which may update internal state, and respond to message sender

Processes (including services) can bind to another service, and communicate over message passing. There are convenience macros to help define message passing stubs between processes.

# `register` - register a process under name in service registry
(register :echo)

# `ls_srv` - Can list all services running within runtime
(ls_srv)         # => ((:name :echo :pid <pid XX>))

# `find_srv` - Get PID for registered processes
(find_srv :echo) # => <pid XX>

# Register has options to overwrite and expose interfaces (as function names)
(defn ping (x) x)
(defn pong (y) y)
(register :service_c :interface '(ping pong) :overwrite)

# `srv` is a macro to:
# - Register process under a identifiable name in registry via `register`
# - Start a service loop (covered under "message passing")
(defn echo (msg) msg)
(srv :echo :interface '(echo))

# `srv` is blocking - but often it is more convenient to fork into a new service
# `spawn_srv` is a macro to expand into `srv` inside a `spawn` block:
(spawn_srv :echo :interface '(echo))

# `bind_srv` can be used to define matching message-passing stubs within another process to a service process:
(bind_srv :echo)    # defines `(echo msg)` in current process, which messages `:echo` service

The runtime has built-in global pubsub mechanism.

# Subscribe to :my_topic
(subscribe :my_topic)

# Publish data to :my_topic
(publish :my_topic '(:hello :world))

# Updates are received via mailbox:
(recv) # => (:topic_updated :my_topic (:hello :world))
#!/usr/bin/env vrsctl

# Internal state in process - count
(def count 0)

# Define an interface to increment count and publish over topic
(defn increment (n)
  (set count (+ count n))
  (publish :count count))

# Serve a counter service, with `increment` as exported interface:
(spawn_srv :counter :interface '(increment))
#!/usr/bin/env vrsctl
# macOS System Appearance Integration
#

# Get system appearance state
(defn is_darkmode ()
  (def (:ok result) (exec "osascript"
                          "-e" "tell application \"System Events\""
                          "-e" "tell appearance preferences"
                          "-e" "return dark mode"
                          "-e" "end tell"
                          "-e" "end tell"))
  (eq? result "true"))

# Set system appearance state
(defn set_darkmode (dark)
  (exec "osascript"
        "-e" "on run argv"
        "-e" "tell application \"System Events\""
        "-e" "tell appearance preferences"
        "-e" (if dark "set dark mode to true" "set dark mode to false")
        "-e" "end tell"
        "-e" "end tell"
        "-e" "end run")
  :ok)

# Toggle current state
(defn toggle_darkmode ()
  (set_darkmode (not? (is_darkmode))))

# Fork into service exporting `toggle_darkmode` as service
(spawn_srv :system_appearance :interface '(toggle_darkmode))

vrsctl is a CLI client for vrs. When invoked without arguments, it launches into an interactive REPL useful for live programming and debugging:

$ vrsctl # Experiment with lyric:
vrs> (def url "https://github.com/leoshimo/vrs")
"https://github.com/leoshimo/vrs"
vrs> (open_url url)
(:ok "") # Introspect runtime state:
vrs> (ls_srv)
((:name :launcher :pid <pid 28> :interface ((:get_items) (:add_item title cmd))) (:name :system_appearance :pid <pid 5> :interface ((:toggle_darkmode)))) # Bind and talk to services:
vrs> (bind_srv :launcher)
((:get_items) (:add_item title cmd))
vrs> (add_item "Hello" '(open_url "http://example.com"))
:ok

vrsctl also offers convenient interfaces and tools to support scripting and debugging - see vrsctl --help for an overview of available commands.

There is an major-mode available for Emacs - lyric-mode.

It provides syntax highlighting and bindings useful for bottom-up, interactive, editor-centric software development.

The package is currently not available via package repositories - but is available in my dotfiles repository.


Read the original article

Comments

  • By leoshimo 2025-05-310:554 reply

    Author here - wanted to drop some context on the project from the README:

    ---

    [vrs](https://github.com/leoshimo/vrs) is a personal programming runtime - a sandbox for building my “endgame” software platform.

    The goal is to create a computing environment that brings me joy.

    Each piece - the language, runtime, tools, and more - is designed as part of one holistic experience.

    I live on a collection of personal software running on vrs every day, evolving the runtime as I go.

    Inspired by systems I love - Emacs, Erlang, Unix, Plan 9, and Hypermedia - combining their powerful ideas into something that feels just right for me.

    Built at the [Recurse Center](https://www.recurse.com/).

    • By whartung 2025-05-312:12

      Well, I really like this thing.

      I like that it's a daemon. You can connect to it from various clients, do stuff, disconnect, and your stuff is still there when you come back, your processes are just running in the background. Conceptually, it's no different than SSHing into your favorite remote system.

      In contrast to most other image systems, that are not, routinely, just a long running generic processor.

      It's not "Lisp OS", but it's Lispy-ish OSey-ish.

      They have their own cool little, byte code compiled Lisp, with the mailbox, and multi-processing intrinsic, and those isolated name spaces, which is nice. There's no shared memory, just message passing. That's fun and geeky.

      Mailboxes/queues are not ubiquitous on Unix systems compared to streams and pipes and sockets. The queues, while they exist, are not readily lifted in the shells and such, so they become a specialty item.

      Whereas in Unix, you can craft simple services using, well, anything, and inetd, that's still different from a long running mailbox listener.

      They have different purposes, and the systems that use one as their hammer vs the other, will look different. But the important thing is making those services simple to make and use.

    • By worthless-trash 2025-05-315:281 reply

      I'm having vrsjmp crash on start:

        vrs git:(main) ./target/debug/vrsjmp
      
      thread 'main' panicked at /Users/Username/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tao-0.16.10/src/platform_impl/macos/window.rs:268:21: null pointer dereference occurred note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread caused non-unwinding panic. aborting. [1] 31647 abort ./target/debug/vrsjmp

      Do you accept PR's for fixes ?

      • By leoshimo 2025-05-315:551 reply

        The project isn’t open to contributors, but I appreciate the thought!

        (although I can’t help but wonder about the crash - I can’t seem to repro it. I’ll end up taking a look when I can…)

        • By worthless-trash 2025-05-318:14

          Thank you for your effort, I look forward to it if/when you open up to contributors.

    • By Spivak 2025-06-013:361 reply

      > For example, annual jobs can be represented as a infinite looping program that sleeps for a year

      Does your system actually implement true durable compute? Like I can restart my mac or stop the daemon and it's (apparently) not only smart enough to resume this process where it left off but also account for the time delay and reschedule the wakeup? Astounding if true— if you do what's your strategy for achieving it?

      • By leoshimo 2025-06-021:05

        The project doesn't implement durable programs (yet!).

        I've done some prototyping in the past, and came out of it feeling it wasn't quite the right thing to dive into yet. I mostly lacked clarity around what the concepts / syntax / ergonomics should be, because I didn't have specific use-cases in mind.

        I don't see it as a "global" always-on feature, at the moment.

        I'm intrigued by Membrane's approach (https://docs.membrane.io/concepts/programs/) - but I'm not sure if it's the right fit for this runtime. TBD!

    • By Bjartr 2025-05-314:031 reply

      How would you describe/rate your experience at recurse center?

      • By leoshimo 2025-05-315:05

        I loved my time there - I attended batch in-person in NYC.

        Specifically:

        - I enjoyed how passionate everybody was about something - and how deep they pursued their interests. Everybody was generous with their time and knowledge, and it helped me connect new concepts together in ways I couldn't have alone.

        - I loved having dedicated time and space to focus on this passion project, full time. I joined with the specific goal of building a "livable" MVP during the 3 months I was at Recurse.

        - It was great to be in an environment where people were performing computer stats + shenanigans purely for fun.

        It was the perfect setup for me!

  • By ConanRus 2025-05-3017:322 reply

    Looks good, but Erlang gen_srv, supervisor analogs needs to be created, along with the standard Erlang messaging logic around it, for this project to be useful.

    • By leoshimo 2025-05-311:03

      gen_srv, supervisor analogs needs to be created

      Agreed on `supervisor` - that's high up on my list to tick off when the occasion arrives.

      On `gen_srv` - while there's room to iterate, I consider the `spawn_srv` / `bind_srv` to be a sufficient analog for me:

      https://github.com/leoshimo/vrs?tab=readme-ov-file#services-...

    • By upghost 2025-05-3021:482 reply

      could you dive into the gen_srv a bit more? From what I understand about erlang the value prop is already enormous, what extra does gen_srv add? It seems like people who Erlang understand this intuitively so I haven't really been able to find it explicitly explained anywhere

      • By asabil 2025-05-3022:191 reply

        `gen_server` is an abstraction over the a client sending a `request` that a server services to produce a `response` to be sent to the client.

        The `gen_server` implementation takes care of things like timeouts, servers terminating before a response is produced as well as maintaining the server state.

        You can read more here at [1], [2] and [3].

        [1]: https://learnyousomeerlang.com/clients-and-servers#callback-...

        [2]: https://www.erlang.org/doc/system/gen_server_concepts

        [3]: https://www.erlang.org/doc/apps/stdlib/gen_server.html

        • By vector_spaces 2025-05-3022:442 reply

          I'll add to this that the "abstraction" component of this description is key and the use-case for a gen_server is far more broad than what you might expect in say a networking or web context.

          You would use them for instance when you need to group behavioral units with a defined interface, even if you don't require any explicit networking.

          This is a bit reductive and wrong in some ways but think of gen_server modules and instances as "sort of" like classes/objects. You "send messages/requests" instead of "calling methods," but the spirit is essentially the same -- it's a way of modeling the world with code

      • By upghost 2025-06-0818:03

        Appreciate all sibling comments, they encouraged me to dig further. Read Joe Armstrong's book "Pragmatic Erlang" and the section on gen_server was nothing short of breathtaking. What great and terrible power!!!

  • By cess11 2025-05-3018:112 reply

    Does this project have some relation to LFE?

    • By privong 2025-05-3113:341 reply

      > Does this project have some relation to LFE?

      Can you clarify what "LFE" stands for? Do you mean "Lisp Flavored Erlang" or something else?

      • By cess11 2025-05-3115:52

        Yes, Lisp Flavoured Erlang.

    • By leoshimo 2025-05-310:57

      it does not, although LFE is a cool project

HackerNews