This post is a response to the first Malleable Systems Challenge Problem, which explores the theme of fearless extensibility. The challenge asks participants to think deeply about how we can achieve powerful extensibility in systems without introducing significant security and maintenance risks. In this post, I’ll be exploring several dimensions of the problem and proposing ideas for balancing the benefits of extensibility with the inherent challenges.
Inspired by Technical dimensions of programming systems and Technical Dimensions of Feedback in Live Programming Systems, I aim to outline different dimensions of extensibility to facilitate comparison between designs and foster discussions around malleable systems. This is not intended to be a comprehensive list but will be a live document that will evolve over time. If you have any thoughts or suggestions, feel free to reach out and connect with me.
A key distinction in the design of extensible systems is between open and closed extensibility, drawing inspiration from the open/closed principle of object-oriented design.
How can we migrate from open to closed extensibility? Is there a way to allow users to extend systems locally and then easily upstream their changes into a more general case?
One possible approach is to create a clear migration path for user extensions, where user-created local modifications can be incorporated into official APIs or extension points over time.
Another key issue in extensibility is trust. When allowing third-party extensions, how can we trust that the code won't break the system or introduce security vulnerabilities?
One potential solution is to build shared software sources, such as package managers or app stores, where extensions are vetted and reviewed by the community. Additionally, cryptographic authorities can help verify that code has been reviewed and approved by trusted parties.
Beyond that, we should use static analysis and programming language techniques to restrict what extensible components can do. For example, can we ensure that an extension cannot read from the file system or access the network unless explicitly allowed?
Another concept to explore here is capability-based security (OCAP), which emphasizes controlling what resources (or capabilities) an extension is allowed to access.
A crucial aspect of designing extensible systems is ensuring that orthogonal extensions do not interfere with each other. Non-interference means that multiple extensions can coexist without breaking or altering each other’s behavior in normal operation, preserving the stability and predictability of the system. Orthogonality is the property of extensions that obviously should not affect each other.
A styling extension for a web browser and a tab manager would be examples of orthogonal extensions since you would not expect any confluence of behavior when having both.
While orthogonality focuses on ensuring that independent extensions do not interfere, compositionality goes a step further by enabling multiple extensions to work together in a predictable and structured way. Compositional systems allow extensions to combine and build on each other’s functionality, often producing more powerful results than any single extension alone.
The challenge of compositionality lies in ensuring that extensions can interoperate smoothly, without unexpected interactions or conflicts. To achieve this, systems need well-defined interfaces and extension points that provide predictable behavior when used in combination. In addition they may also need ways for end-users to handle intrinsic conflicts themselves.
We can look to functional programming and mathematical rigor for inspiration. For example, static analysis can help us ensure that extensions adhere to certain properties, while debuggability and observability are essential for maintaining a high level of trust in the system.
Jonathan Edwards introduced substrates to me during his LIVE Keynote at SPLASH this month. He gives some criteria as to what define a software substrate. My interpretation is that substrates are the computational model and framing in which systems are defined. It is paramount that the user interacts in terms of the model so that programming the system is the same as using it. So in a sense they define the data model as well as how computation itself can be represented in a system. Historical substrates include POSIX, spreadsheets, objects, actors, and functional paradigms. With regards to extensibility it's important to design these substrates in such a way that it allows for the extensible properties we'd like to have.
When building extensible systems, it’s critical to ground them in clear, well-defined primitives. Designing systems with composable, flexible interactions from the outset ensures that extensibility can be smoothly incorporated later, rather than having complexity bolted on after the fact. Effective extensibility deeply embeds into the structure of the substrates rather than existing as an isolated layer on top. This allows extensions to integrate seamlessly into the system, enabling them to express their own data models and computations in a natural and coherent way. In short, for true composition to thrive, the substrate must be expressive enough to accommodate extensions without breaking its core structure or creating unnecessary friction.
Designing extensible systems is a complex challenge. The ideas above are clearly incomplete but I hope can be a jumping off point for a discussion about what we value in the future.