Abstract. Perhaps to build systems that are malleable and end user programmable we need to rethink the relationship between runtime and development artifacts. It is our opinion that the concept of source code as the primary development artifact was the original sin that placed a one-way door between developer and user.
Most existing programming systems are run using a different format to how they are developed. Lopecode is source-last: there is no external source code; the canonical representation is the live executing system. When source code is needed, functions are decompiled on demand starting from Function.prototype.toString(). Serialization becomes a projection to one of many formats: a standalone HTML file, a JavaScript IIFE or an ATProto PDS. Format-independence is a consequence of runtime-primacy. Furthermore, we claim runtime-primacy also leads to simpler implementations of malleability and liveness, by admitting a plurality of non-source based editing surfaces.
We share three field episodes where Lopecode's source-last design had consequences: shipping a tool into a locked-down corporate environment; liberating a running program from a notebook SaaS; and round-tripping an AI investigator into a 3rd party website.
Tom Larkworthy — submission to LIVE 2026.
A business analyst in a regulated industry, in another continent, could not open a large CSV export with their preferred tool, Excel. We knew nothing about their environment other that it was strict i.e. no installing software and no transmission of data. We built a small tool that trims columns from large CSVs, as a Lopecode notebook, and sent the single HTML file over Slack. They double-clicked it. The tool ran offline in their browser, and they were able to trim the CSV so that could be loaded by Excel.
The tool is not that interesting. What is interesting is that it is actually hard to share a program to non-programmers. Native binaries and cloud services are restricted in corporate environments, and you don't know what operating system to target. Often data cannot leave the corporate perimeter. The browser is an ideal runtime because it is sandboxed and present on every operating system. A single HTML file can be double-clicked to start. So a pragmatic option to distribute code is a self-enclosed file that can travel on email and instant messenger, moving the program to the data where it is needed.
CSV column chooser is a copy of the actual tool shared
Most programming systems are developed source-first: a code representation compiled to a runtime representation. A webapp bundles a page for HTML. Lopecode inverts the arrangement in the pursuit of liveness and malleability [Litt et al. 2025]. The canonical representation is the live runtime: a graph of modules containing pure JavaScript functions, executed by a reactive scheduler. When a serialized form is needed, it is computed on demand by reflection against a reified program model [Kiczales et al. 1991]. Because no format is canonical, the same runtime can be projected to different serialized representations.
When the runtime is the source of truth, serialization becomes a projection, so format-independence follows. We call the design source-last: source code still exists, but it is recovered last, on demand, from the running system, rather than maintained as the primary development artifact. New serialization formats can be added after the program has started.
The same arrangement applies to program editors as well. Since the live runtime is the single thing every tool reads and writes, editing surfaces specialize and coexist: editor-5 edits cells as source; editable-md makes rendered prose directly editable; sticky (§Serializing the live system) persists UI state, and svg-lens makes SVG code editable through direct manipulation [Foster et al. 2007]. All are unprivileged userspace modules and thus can be added on demand ad hoc without coordination.
Try editable-md. Click any paragraph of this essay: it opens in a live markdown editor. The prose is cells, and the editor is a userspace module riding in the same file. If you open the code editor as well, you will notice that changing and committing (SHIFT + ENTER) updates the other view. Two editors are targeting the same code cell.
Here is a simple Lopecode program written in Observable JavaScript. Three cells:
constant defined as a number: constant = 31
fun defined as a pure function: fun = ƒ(x)
result applies one to the other, implying both are dependencies: result = fun(constant)
Edit constant or fun and result recomputes reactively, like a spreadsheet does. Below are the three cells, in groups of three. In each group you first see the value of the cell, then the high level source code, then the low level representation.
constant:Low level runtime representation of constant
function _constant() {return (31);}
fun:Low level representation of fun
> function _fun() {return (x => x * 2);}
result:Low level representation of result
> function _result(fun,constant) {return (fun(constant));}
The high level cell sources are compiled to the low level representation for scheduling by the Observable Runtime, and then they compute to produce a value. The low level representation is vanilla Javascript and is executed by the browser. For simple cells, a cell is mapped to a single reactive variable (for other types of cells, like viewof, check the language reference).
A reactive variable is stored as a pure JavaScript function from its inputs to its value, called its definition, and critically for this essay, those definitions carry their own source [TC39 2018]. We can access those internal functions through the Runtime and request their source.
That one call, Function.prototype.toString(), is the reflective interface Lopecode builds upon. The runtime does not keep the high-level source you typed anywhere; when source is needed to edit or to export, it is recovered from the executing function with decompilation.
Note that recovered functions cannot contain closure variables. toString() cannot see captured variables, but due to the reactive runtime design everything arrives as an input mapped to a function argument (pure functions only), so the recovered source is the complete definition.
Try it. Ensure Edit mode in the burger menu is on, and click edit under fun to open editor-5's source editor and change the code to (x) => x * 3. Note result recomputes, and the decompiled listing refreshes.
toString() only recovers the low-level compiled JavaScript, not the original high level notebook-syntax text the author originally wrote. The observablejs-toolchain is a paired compiler/decompiler that can reverse the compilation by construction [Foster et al. 2007]. By design, the generally ill-posed problem of decompilation [Kell & Stinnett 2024] is avoided.
The compiler/decompile pair for Observable Javascript 1.0 syntax is not intrinsic functionality to Lopecode. It has been added by a userspace module that can co-exist with others. The intrinsic functionality of Lopecode is just the low-level Observable Runtime representation which is Javascript of a certain shape (e.g. no closures). We expect in the future to support Observable Notebook 2.0 syntax, thus accommodating multiple high level source code representation on the same runtime.
The coarse unit of composition in the reactive runtime is the module, a namespace of cells (its "scope"). In a typical Lopecode setup, a module is visualized as a notebook, and a single runtime can contain many modules/notebooks. Modules contain reactive variables, which are the finer grained unit of code. Modules are expressed as JavaScript modules [Ecma 2015]. Within a module definition are JavaScript functions, which become the definitions of live reactive variables.
The process of adding a module then, its importing is the ESM module that represents it. JavaScript's normal module import machinery evaluates JavaScript source to create the live runtime functions. Redefining a low-level function is an eval of its source.
The problem of serializing a live runtime is then one of expressing the JavaScript module structure, including the reactive variable definitions and metadata, so that the reactive dataflow structure can be deserialized elsewhere as JavaScript. Thus serialized representation deal only with pure JavaScript, and the high level source code the author experiences is never written anywhere.
The burger menu at the top left of the page contains Save in place, Download, Edit mode and Fork. These will serialize a snapshot of the current runtime code as it is now. Those same features can be inlined into prose too: download this essay, or fork into a fresh browser tab. Copies carry all code and asset changes, the prose, the tooling, and require no external build steps or internet connectivity. Note though, runtime values are not typically serialized.
[Horowitz & Heer 2023] called out that Observable Notebook's lack of runtime state persistence is the primary reason why they are not a "Live, Rich, and Composable Programming System Beyond Static Text". Lopecode is able to offer opt-in value persistence because there is no external source code.
The slider above is wrapped in sticky, an imported userspace function. On user manipulation, sticky parses its containing cell's low-level definition with acorn and rewrites the persistence argument to the current value. In essence, storing state in its function definition. Reflective exports then propagate that state onwards.
Try it. Drag the slider above, then toggle the cell editor. The second argument of sticky(...) holds the value you just set. Close the editor, drag again, reopen, the source rewrites itself on every slider change.
sticky demonstrates that a single cell definition can be interpreted in multiple languages. When viewing the source of the cell through editor-5 we manipulate using a bespoke higher level reactive language, Observable JavaScript. When sticky mutates its own container, it uses the lower level JavaScript language that can be parsed with an off-the-shelf JavaScript parser, Acorn.
Sticky wrapped cells in Observable JavaScript representation look like
viewof <identifier> = sticky(<ui widget>, <persistence_value>)
The sticky function discovers its hosting reactive variable, and on UI change, redefines its reactive definition with a new persistence value. This mechanism is similar to ColorForth's magenta variables [Moore 2001], which persist their state across program runs by recording their value in source code. One difference is that sticky is written in userspace and is not a special system or language feature.
sticky operates entirely at the low level: on change it recovers its own cell's source with toString(), patches one literal with acorn, and pushes the new code back. When an author opens the cell editor, the editor also uses toString() to obtain the latest low-level source and decompiles it to Observable JavaScript syntax, with the updated literal included. Sticky does not understand Observable JavaScript syntax at all and has an entirely different domain of operation.
Sticky illustrates the core argument of this essay. By not having a primary source code representation, we were able to remove the biggest criticism [Horowitz & Heer 2023] had of the Observable Notebook system, that UI widgets could not extend the programmable surface. It highlights that Observable JavaScript is no longer the exclusive language of the runtime, and the sticky approach was generalized to svg-lens, a much more impressive example of creating new live malleable UI interfaces in userspace on Lopecode.
sticky uses parsing, the observablejs-toolchain uses decompilation, and svg-lens uses functional lenses [Foster et al. 2007] [Litt et al. 2020] to implement their features. All of them, at some level, retrieve source code as late as possible, which is why these features do not need to coordinate over some cached canonical format.
JavaScript makes this quite simple to implement because functions carry their source, but a native equivalent would be on-demand decompilation (to a high level language) or patching (for materializing changes), so Lopecode's ideas carry to other domains. In fact, the technique is already prevalent in the games modding community, who work directly against binary representations [FabricMC 2016] [n64decomp 2019]. Why not elevate the technique to be the primary method of program editing? Lopecode is about making decompilation and patching the primary method for live editing and saving.
Lopecode currently maintains three serialization mappings. Each is a userspace module, and they share nothing with each other beyond the reflection described in §A cell is a function that carries its source.
The HTML mapping stores each unit of the module graph as a <script type="text/plain"> block: one block per module holding its decompiled source, one MIME-typed block per file attachment, and a bootconf.json naming the main modules to load first (the rest lazy load via import statements). A short bootloader at the top starts the Observable runtime and resolves imports from the embedded blocks instead of the network. The file is plain text, diffs cleanly under git, and opens from file:// with no server.
This essay's file blocks
The stored form of this very module, as it sits in the HTML mapping (first 350 characters). It goes stale as you edit and is refreshed on the next save, because the block is a projection of the runtime:
const p0 = function _anonymous(md) {return (md`# Source-last programming`);};
const _abstract = function _anonymous(md) {return (md`**Abstract.** Perhaps to build systems that are malleable and end user programmable we need to rethink the relationship between runtime and development artifacts. It is our opinion that the concept of source code as th…
Try it. Edit the title cell, the stored block above does not change: the runtime has moved and the projection is stale. Now Fork from the burger menu (or the inline link in §Serializing the live system): the fork is written from the runtime, so it opens carrying your edit, reflected in the serialization here.
The runtime graph maps cleanly onto ATProto. A record of type com.lopecode.bundle carries an array of file entries, pointers to MIME-typed blobs, and metadata analogous to meta tags in HTML.
The userspace module atproto allows you to serialize and publish the live notebook into the decentralized network directly. A web proxy exists for convenient access without a special reader at lopecode.com, but it is also possible to fetch modules directly from the network.
The exporter's Copy To JS renders the runtime into a single immediately-invoked function expression (IIFE) on the clipboard. If you paste this into the developer console of any page that will execute a script, it reconstructs the whole environment inside that page, bringing a live editable runtime into an origin we do not control. Field Episode III utilized this representation to carry an AI investigator into a 3rd party domain.
Try it. Click 'Copy as JS', open maps.google.com, open the developer console and paste into the console, you should see this essay appear inside google maps!
The reflective method of obtaining source code was necessary to bootstrap Lopecode from the original environment of the Observable Runtime, the ObservableHQ.com hosted notebook service. Notebooks on ObservableHQ.com are compiled on Observable's servers; the notebook source is not machine accessible within the Terms of Service. Observable's first-party export feature generates a multi-file bundle of low-level code that contains no high level source code. Exports are also missing any editing functionality. ObservableHQ.com was for some time the only place Observable JavaScript could be written. What we wanted to achieve with Lopecode is the ability to continue to use Observable Notebooks, but without a dependency on the hosted service, which will one day be turned off [Bostock 2026].
Exporter-3 is written as a notebook, in userspace, and is therefore transitive: it can re-export, and can even export exporter-3 itself. The exported copy becomes independent of the hosted platform. However, an export done this way contains no editing functionality because exporter-3 is just a serializer.
Try exporter-3 on Observablehq.com. Click fork there and see the transition between Observable Notebooks and Lopecode happen in front of you
Exporter-3 operates entirely within Observable's userspace, using features of the MIT licensed Observable Runtime, thus within the bounds of Observable Terms of Service for user-generated content. We can consider exporter-3 an example of Adversarial Extension [Shank & Reed 2025]. The source-last architecture was a forced choice originally needed because there was no other legal route to obtaining source code, however, that choice turned out to have other long term benefits for decoupling, such as format and language agnosticism.
Because Lopecode is self-serializing and relatively self-contained, it is possible to unpack it inside another application. In this episode we used Lopecode to smuggle in an AI agent to perform in-situ exploration of a running application.
AWS provides a convenient "share this dashboard" feature to export a CloudWatch metrics dashboard via a sharable link. This lets you easily host live data on a website without authenticating through AWS. However, you are stuck with a rather limiting palette of chart visualization options. What we wanted was the data, and the simple link based authentication, but write our own visualization for our data. Lopecode's ability to deserialize inside 3rd party domains helped us figure out how AWS link sharing works.
Using §IIFE: an unloader for the HTML we unloaded a Lopecode notebook, carrying a Claude Code agent interface, inside the console.aws.amazon.com origin, sharing the page's session. We connected Claude Code to the running Lopecode runtime. Claude could now access the notebook environment within a privileged origin page, which it could monkey patch to learn how the page works.
We asked it to figure out the dashboard's data series fetch API. It wrapped the page's fetch and XMLHttpRequest, and asked us to press the dashboard's refresh button. It watched the console make its own calls, and rebuilt the read path. It discovered the data comes from an internal console target, CloudWatchVersion20130116.GetMetricData on monitoring.<region>.amazonaws.com. This endpoint requires three undocumented headers:
a specific x-amz-user-agent value
an X-Amz-Sharing-MetaSum/-MetaTime pair that hashes the request
a per-widget token, X-CloudWatch-SharedDashboardToken, that GetDashboard returns on each widget.
Google's index doesn't contain references to X-CloudWatch-SharedDashboardToken! This is novel information. The nearest published reverse engineering work on CloudWatch dashboards doesn't mention any of these mechanisms [Tsaousis 2025].
Once the auth mechanism was understood and reproduced inside Amazon's own webpage we could reserialize the runtime back out to disk, and massage it into a general utility. The chart below runs that here; its infrastructure is built in the AWS dashboard notebook. Below S3 bucket data loaded from an AWS CloudWatch dashboard sharing link.
Try it. Copy as JS, then open the public CloudWatch dashboard and paste into its developer console (CMD + OPTION + I): this essay re-materialises inside Amazon's page. Then press CMD + K, open robocoop-5, and tell it "You are now embedded on AWS CloudWatch dashboard, can you read the values of the dashboard graph?".
Several elements of this episode were aided by source-last. First, the plurality of serialization formats allowed us to transport a working notebook inside a foreign program easily. Second, we were able to start our running program, with additional tools, inside a foreign runtime, which was aided by the local-first, low dependency runtime of having everything defined in userspace. Arguably there are more ways of achieving this, but Lopecode was never designed for this use case. It was originally designed to help clone personal work from the hosted Observable environment, but the same reasons source-last was adopted for that use case have made it suitable for this niche one.
Claude Code Pairing interfaces with Lopecode at many different levels of abstraction, and Claude Code switches between them to solve tasks. Claude Code Pairing offers tools to read and modify individual cells using the high level language or the low level representation, execute arbitrary JavaScript snippets, read runtime values, replace whole modules as ES modules via a file abstraction, manipulate the serialized Lopecode HTML file with OS file tools, pick up custom MCP [Anthropic 2024] tools defined in userspace, and call cell defined functions directly. The avoidance of a true canonical representation that source-last programming embraces gives agents much flexibility in approaching a task, which broadens the domain of applicability.
Lopecode's core runtime construct is the Observable Runtime (6.5 KB). es-module-shims redirect vanilla Javascript import statements to be loaded from its own HTML, a bootloader sets up the standard library; altogether a minimal Lopecode file is about 48 KB. Everything else demonstrated in this essay (4Mb) is userspace.
The Observable Runtime library offers paved paths for putting variable definitions into the runtime using the low-level representation, but our essay has focused on our source-last technique for getting them back out through Function.prototype.toString()[TC39 2018]. Once the outbound path was defined, a cycle is formed, allowing just the runtime to be the bus with which to build editors, visualizers and everything else in userspace. That cycle, is the low-level morphism of the compile/decompile cycle that enabled end-user programming.
The form of this submission follows [Edwards et al. 2019]. That paper criticizes LIVE-style venues for work "presented informally, through screencasts", lacking related work, and proposes the interactive essay, evaluated by inquiry, as the remedy — while naming the archival fragility of web essays as an open difficulty, "easier to address if they are self-contained". This essay is submitted as evidence on that point: an interactive essay that is self-contained by construction, carries its own tooling, and re-exports itself. We adopt their author guidelines, including the requirement for problematic examples (§Problematic examples).
[Jakubovic et al. 2023] define a programming system as "an integrated and complete set of tools sufficient for creating, modifying, and executing programs". Their design-space maps observe "a conspicuous blank space at the top-right" where high self-sustainability meets high notational diversity; I believe Lopecode is in that corner, self-serializing while its notation spans prose, code, widgets and whole userspace UIs. [Jakubovic & Petricek 2022] define self-sustainability as dissolving the product/source/producer distinction, and reach it by ascending from a minimal substrate. Their persistence, notably, was a manual walk of the state graph to a JSON file — "reminiscent of the image-based persistence in Smalltalk, though it is frustratingly manual". The exporter is Lopecode's answer to exactly that problem, and as it serialized to a single runtime-free file, its persistence doubles as dissemination.
The exporter is a mirror in the sense of [Bracha & Ungar 2004]: a meta-level facility separated from the base program (their stratification) that reifies the runtime's own categories — modules, cells and attachments map one-to-one onto script blocks (their ontological correspondence). Bracha and Ungar motivate mirrors by "significant advantages with respect to distribution, deployment and general purpose metaprogramming"; a mirror whose output is a deployable artifact takes that motivation literally. Lopecode doesn't stop at one mirror, the ATProto maps the notebooks modular structure also onto an independently developed distributed storage format.
Several prior works have carried persisted computational worlds over long time horizons: the Smalltalk image [Ingalls 1981], ColorForth's magenta variables [Moore 2001], and Interlisp's residential environments [Sandewall 1978] [Venue 1993]. Superficially, Lopecode has a different programming model (dataflow). More relevant to our essay, Lopecode's runtime code persistence is implemented in userspace and designed to be decoupled because it eschews naming a mutually exclusive canonical representation. We believe this endows it with flexibility; indeed, it has been continually evolving and bridging new formats. The original exporter-1 was coupled to the Notebook 1.0 high level syntax, and Lopecode has evolved towards source-last in order to accommodate looser modularity, to make it easier to evolve (as of 2026-09-15, Lopecode supports Notebook 2.0 syntax [Observable 2025]), and to admit alternative editing methodologies beyond text. We cite Smalltalk for image persistence and total moldability, which is functionality similar to Lopecode. Lopecode does not want to serialize values, as there is a risk those values could contain secrets, but it does offer limited opt-in with sticky.
[Miranda 2025] is a recent system that also serializes to a single HTML file, and is able to self-modify and resave. Both Lopecode and [Miranda 2025] share an opaque, complicated loader script that sets up the environment to use the HTML as a code source. However, a big difference is that Lopecode's own userspace module exporter-3 defines that loader script, and Lopecode is capable of any number of userspace modules defining serialization forms, and actively does. [Miranda 2025]'s format could not be injected into an AWS domain, for example, as the user's entrance and exit points are assumed to be an HTML file, whereas Lopecode offers full programmatic access to serialization that is extensible within userspace.
[Klokmose et al. 2015]'s Webstrates replicates the DOM itself and is realtime multiplayer. Lopecode inherits asynchronous, git-style multiplayer in some sense by having a stable plain text representation, but does not have realtime multiplayer and it is not a priority. Both are similar in that they copy browser runtime state to a remote. Lopecode focuses on replicating functions in a dataflow graph, whereas Webstrates replicates DOM representations. Both are capable of self-serialization and self-modification in userspace [Rädle et al. 2017] [Borowski et al. 2022]. The main difference is the level of abstraction each works at. Webstrates serializes the DOM model of the browser, and transient user-specific DOM nodes have to be opted out [Webstrates docs]. Lopecode serializes the DOM builders, not the nodes themselves, and the UI elements Lopecode produces are values that are not serialized by default. Sticky adds a limited way of opting in to parametrized value serialization. Another important difference to Webstrates is that Lopecode's computation and state are entirely self-contained on a single machine, whereas Webstrates and Codestrates [Rädle et al. 2017] need an external server to store state.
Decompilation is an existing technique for understanding or modifying code whose source you do not have. Minecraft's modding ecosystem stands on decompiling and re-mapping an obfuscated binary [FabricMC 2016]. The Super Mario 64 decompilation project reconstructs buildable source from a ROM [n64decomp 2019]. Gamer communities spend significant effort to recover source code. A source-last architecture makes decompilation the primary development path, essentially ensuring continuous moddability.
Following the guideline of [Edwards et al. 2019], as many problems as benefits.
Not everything can round-trip. Serialization captures code definitions, module structure and binary file attachments. Program state accumulated in memory is lost. The sticky idiom of §Serializing the live system narrows the gap, but only for JSON-serializable values.
Furthermore, decompilation of code definitions is not a general strategy. toString() recovers compiled JavaScript, but the inverse mapping back to high level syntax has to be designed for reversibility, which impedes adding new languages. AI Agents without guardrails will often write arbitrary low-level definitions that are not decompilable.
No closures means not general JavaScript. The recovered source is complete only because cells close over nothing (§A cell is a function that carries its source). toString() cannot capture a closure environment, so the technique does not cover the host language. Source-last recovery is a property of the closure-free cell shape the Observable Runtime has, not a general mechanism that could be used anywhere.
Offline-first add complicated work. Every dependency must be vendored into the file, otherwise the environment falls back to network fetching and the HTML stops being self-contained. Furthermore, vendoring is actually quite complicated in the Javascript ecosystem, UMD/ESM and package local references need to be handled differently, sometimes even having to use a bundler like esbuild. Vendoring is a topic unto itself.
The moldability gradient is steep. Changing a value or a paragraph is simple. Writing a code cell is a bit more complex. Replacing the exporter or the editor requires deep understanding of the runtime SDK. We have no evidence yet that anybody can cross that gradient unaided. Agents have a habit of glitching themselves when attempting it as well from within, though they have more success manipulating the file as an HTML document loaded into Chrome for Testing and pairing into it and using the Chrome DevTools Protocol. The reason this works is mistakes don't break the userspace agent. Editing the runtime from within is technically possible, but fraught with risk that the editing surface breaks as you edit it. The system's modularity and literate programming help to at least build a second system with the first in place which permits some incremental development though.
Is source-last even doing anything? The reflection toString tactic used here could be trivially replaced with a last minute cache of the source code, that is read back instead of using toString. In fact, a late source cache would be the nearest workable solution for conversions that are not bidirectional (e.g. optimized compilation to WebAssembly). It is my opinion that late-caching is still source-last, but deploying that tactic still presumes you own the compile step to cache it. In Field Episode II that was not the case, we did not have access to the source! But what if the system did own the compile step, would it still make sense to be source-last? The point of this essay is to say yes, if you have a cellular program architecture, then the lower level your primitive representation is, the easier it becomes to support a polyglot interface, and the easier it is to transpile to alternative representations and evolve over time. Writing paired compiler/decompiler pairs is odd, but it preserves the symmetry of bidirectional transformation that allows the end user to also be a developer.
Source-last is only one part of the malleability puzzle though. Lopecode is also supported by other equally important features (reactivity, isolated cells, local-first and meta-programming). I draw attention to this particular aspect because it is naturally occurring in other malleability contexts: games modding [FabricMC 2016] [n64decomp 2019] and reverse engineering, and so, it is not a leap to consider it as a primary programming methodology for live malleability systems. Users can never be deprived of the source code if the system is decompilable by construction.