The VCS Landscape
Version control has used the same mental model since the beginning of computing. Code is a stream of changes that will eventually merge into a release package of some kind. Branches are really tributary rivers of data feeding into a main stream, only to be obliterated and lost forever in the literal sea of code in the merged branch.
Most small- to medium-sized distributed teams don’t need a lot of the complexity that tools like git introduce and are hardest hit when the tool enables footguns. Not all flaws of the various vcs tools come down to their design patterns, but some of the thorniest ones do.
The core problem is that merges are destructive. When BranchB is merged into BranchA, BranchA is irrevocably changed. Sure, you can revert those changes, but that doesn’t mean BranchA is untouched. It’s just been reset to a prior point. With git, it’s not even really reset. The repo pointer is just set to a commit hash and any new commits to that branch will continue from that commit. All the other commits still live in the git history. Rewinding or unwinding a stream-based vcs becomes tedious quickly especially if you only want certain parts of other commits. How do you separate out the rivers that feed the Mississippi? How do you easily separate merged code? Answer: you can’t.
The Proposed Solution
Cake is a design pattern for version control that thinks of code changes as a layer of only changed files. In other words, if code were a map of a physical place, there would be peaks and valleys of code changes. The peaks themselves are information that “erupt” out of structure. With each topographical layer laid on top of each other, the shape of a mountain forms. This is the mental model for cake. Using this metaphor in version control terms, cake sees layers as composable units of code changes to build up on top of each other to produce a deterministic output or release package.
Example
Let me give you an example of how this makes version control easier. Let’s say you have a recipe for a vanilla cake that you love. Every time you bake that cake, you go to the recipe and start following the directions. Add this, stir that, cool those and, voilá!, a new vanilla cake is born. You keep reusing the recipe with the same ingredients and, as long as you follow it the same way each time, you get the same vanilla cake you love.
Now, someone gives you a recipe for a Neapolitan cake where one layer is chocolate, one layer is strawberry and one layer is vanilla. The recipe has recipes for each layer. But you love your vanilla cake. So you swap out their vanilla cake recipe for yours. When you bake the Neapolitan recipe, everything is exactly the same except for your vanilla layer. That’s how cake vcs manages code. Each layer is a possible insertion point in a bake. The bake is controlled by the recipe.
Properly bounded and designed, layers are coherent units of work that can be composed into different contexts.
Walkthrough
[All this is planned functionality but the syntax and design will change before there is a release candidate. This is meant to be theoretical]
Using our metaphor above, we might have an application like this.
All the ingredients for a cake of various combinations. When a user issues chef kitchen:init on the cli, a .kitchen directory is created with empty directories like this. (chef is the name of cake’s CLI tool)
The three directories are empty. One of cake’s core philosophies is intentfulness. Think “Less magic, more sanity”. So, rather than just making a copy of the working directory right away, cake expects the dev to issue the specific command chef layer:create ingredients. Creating a layer is like committing. When a layer is created, it becomes immutable and is added to the kitchen’s layers in the cake-server. When the layer is created, the kitchen looks like this.
There’s a new directory in layers/ with a timestamp and the passed layer name. The files are copied and their contents hashed. The content hash is used to quickly ignore files. cake can quickly determine whether any changes exist in a file between two layers by hashing the bottom layer file and comparing that hash to a hash of the top layer file of the same name.
In this layer, my butter.json contains
{
"texture": "creamy"
}
But I forgot I’m supposed to also track fat-content so I add
{
"texture": "creamy",
"fat-content": "2%"
}
Because layers are immutable, I can’t update a layer with my change. I have to issue chef layer:create added-fat-content —to-recipe=ingredients. cake then makes a new layer with only the file changes between the existing ingredients layer and the changes in the working directory. It would be tedious for a dev to have to type the recipe name to update. Devs will be able to issue chef recipe:track-layers [recipe_name] and have any new layers created added to the named recipe. Changed files are added to the new layer with the timestamp-layer-name format. The .kitchen now looks like this.
And the recipe looks like this.
So far, so good. Nothing to write home about.
Odd Choice for Storage?
But why do it this way instead of say, storing file blob hashes in a flat object store? This makes each layer highly observable. Anyone can browse the filesystem and see what the actual code changes are without special tooling. For example, grep becomes far more powerful for finding code snippet references throughout the history of all layers that have been locally downloaded. History is observable and therefore auditable. If someone tries to make a change to a layer, they won’t be able to send it to the cake-server because it will be rejected for trying to change an immutable layer. There is no layer:update command, anyway. Immutability, while it will have its drawbacks, already solves a host of issues that arise when maintaining a source of truth between server and client.
Ok. We have layers. We have a recipe telling us how to bake the layers. It would make sense to look at conflicts next. However, we’ll skip conflicts and come back to them after the bake process.
Bake the Recipe
[Reminder that this is all theoretical at this point and the details are still in design.]
Once the dev has created all layers for their task, they issue chef recipe:bake ingredients and cake then systematically goes through the recipe and continuously merges the layers into a new artifact. The actual and complete process is only diagrammed at this point based on the POC I made in 2026-01 to 2026-02. The essential steps are
- create a temporary output directory
- remote call each layer in the recipe, if not locally available
- add or merge each layer to all previously resolved layers, resolving any new conflicts
cake has "baked" a separate artifact from the layers. The layers are unchanged and reusable.
The dev can then simply cd to the temporary output directory and issue a layer:create command. All the previous layers are effectively squashed into the new layer. Recipes can be compacted in this manner.
Managing Conflicts
The first option for conflict resolution is to accept all [top|bottom] layer that will make a blanket conflict update by simply copying the appropriate file to the directed layer. For any simple insertions and/or deletions, cake will automatically perform those actions and tally them in a log output, if desired. For any other conflicts, they will be surfaced on the cli and the dev will make a conscious choice how to resolve the conflict. Alternatively, the conflict can be inserted into the bake with tags and block markers much like any other vcs.
One of the key, and arguably best, features of cake is the conflict decision log that will be stored in the recipe itself. Each edit made through the cli is automatically tracked and added to the conflict decision log so that each conflict can be reviewed and properly managed. This allows devs to subsequently bake the recipe and just accept the same conflict decisions over and over again without interaction. If needed, discrete steps in a recipe can be extracted along with the related conflict decisions and added to another recipe as a block update. The composability design rule extends to the recipes themselves.
What's the Big Deal?
The value of layers outputting an artifact without being changed is hard to overstate. The system is self-documenting in the sense that all actions are observable and searchable. Got the recipe wrong, had the wrong layer name, or whatever? Remove or update the layer entry in the recipe and re-bake. You can’t unbake a cake, but you can bake a million cakes for free.
Because of the conflict log and the filesystem store for state, the bake up to a fatal error codepoint is preserved and can be resumed. Yeah, you read that right, you can fix something and then just resume a merge.
Any sets of layers can be squashed for efficiency, storage, etc. into a new layer that can be built upon. Composability arises from immutability and using secondary output, i.e., the bake.
Summary
We looked at cake’s proposed functionality for creating and managing layers, using them in recipes and baking a release package. We saw how layer composability had several benefits for code organization, code reuse and conflict resolution.
The ability to manage your code as a composable library of work makes code reuse cheaper to support, debug and extend.
One of the major benefits that is not obvious is that it is hard to have a corrupted state in cake. If there is an error in a bake, the dev can update or make changes to the recipe to address the error (by perhaps inserting a fix layer) and then resume the bake from that point forward.
The conflict resolution log that is added to a recipe combined with immutable layers allows devs to deterministically bake a recipe and inspect every change. Work is never lost once sent to cake-server.
While there are many implementation details to be worked out with missteps along the way, cake is already showing real promise as a viable alternative to other vcs for particular use cases like small team, large codebase type scenarios. The layer model is flexible enough to represent any sophisticated or voluminous codebases. Combined with immutability, the layer also does double duty as a snapshot of code units and documentation of changes.
Baking is a nearly free operation especially after the first conflict decisions have been made between two layers. Non-interactive, deterministic output from immutable code layers of only changes could support an integrated CI/CD pipeline. Layers could function as patches as well and be included as part of a larger CI/CD sequence in a multi-tech environment. A recipe for a “QA bake” could insert a layer for inspection tools to help QA without making changes to the application itself.
The possibilities and implications are really exciting. Over the next months, I'll be looking at recipes as pipelines, cake-server internals and much more on the conflict decisions process.
Thanks for reading!
I encourage you to read the (loose) TDD here if you want a few more details and clarification.
You can follow me here for more updates or on https://cakevcs.substack.com
Follow me on Bluesky!





Top comments (0)