Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

LayerFS

Filesystem Storage for Parallel Agents

01 · LayerStack storage model

Core storage mechanisms

CAS, CDC, and COW solve three different forms of reuse: object identity, byte locality, and structural locality.

01 · Identity

Content-addressed storage

Names immutable objects from their canonical bytes, verifies reads, and reuses exact duplicates across files, layer stacks, and agents.

02 · Byte locality

Content-defined chunking

Keeps chunk boundaries stable around localized edits, so changing a small region does not require storing an entirely new large file.

03 · Structural locality

Copy-on-write

Publishes a change in a new layer by rebuilding only the changed file and directory path while preserving every unchanged subtree from its parent.

Check out an ephemeral filesystem from any layer

A LayerStack records a sequence of filesystem states. Agent A can check out an ephemeral filesystem from L1 while Agent B independently checks out another from L3, giving each agent a separate place to work from the state it selected.

A LayerStack containing layers L0 through L4. Agent A checks out an ephemeral filesystem from L1, while Agent B checks out a separate ephemeral filesystem from L3.
One LayerStack, two checkout points, and two ephemeral filesystems in which agents can work independently.

02 · System boundaries

LayerFS components

The storage engine is implemented today. The SDK and filesystem projection are the planned interfaces around it.

Implemented core

Storage

Owns identities, canonical objects, CDC, file manifests, structural COW, packs, immutable CAS admission, lifecycle coordination, and verified reads.

Planned boundary

SDK

Will expose stable filesystem, workspace, layer-stack, and publication operations without leaking private CAS handles or storage formats.

Planned boundary

Filesystem projection

Will expose a workspace to an agent and capture bounded filesystem effects. It delegates identity, CDC, COW, and admission to storage.

03 · Reading path

Build it from first principles

The book follows the same dependency order as the storage model: establish immutable content identity, localize file edits, optimize the combined representation, and only then add filesystem-level copy-on-write.

Chapter 1 CAS + CDC Vanilla CAS → CDC → chunks and file manifests
Chapter 2 · planned Optimizing CAS and CDC Typed objects, verified admission, identities, packs, and efficient reads
Chapter 3 · planned Adding Copy-on-Write Immutable trees, changed-path rebuilding, workspaces, checkpoints, forks, and rollback

04 · Ecosystem

Collaborating projects

LayerFS supplies storage mechanics. The surrounding projects own execution environments and version-control workflows.

Powered by Ephemeral AI Lab

Chapter 1 · Foundations

CAS + CDC

Overview

Content-addressed storage (CAS) derives an object’s identity from its bytes. Equal bytes produce the same identity, so immutable objects can be verified and exact duplicates can share one stored copy. A whole-file CAS, however, loses that reuse when even a small edit changes the identity of the entire file.

Content-defined chunking (CDC) divides a file using boundaries derived from the content itself. A localized insertion, deletion, or replacement usually changes only nearby chunks; unchanged regions retain the same bytes and therefore the same CAS identities. Together, CAS and CDC represent a file as an ordered manifest of reusable, independently addressed chunks.

Combined path: file bytes → CDC boundaries → immutable chunks → CAS identities → ordered file manifest.

1.1 · Available now Content-Addressed Storage Build and analyze a vanilla whole-object CAS.
1.2 · Planned Content-Defined Chunking Introduce edit-stable boundaries that preserve unchanged regions.
1.3 · Planned CAS and CDC Together Build a reusable file representation from ordered chunk identities.

Content-Addressed Storage

Most storage systems name data by where it is stored: a pathname, an object key, a URL, or a database row. Content-addressed storage (CAS) uses a different rule. It derives an object’s name from the object’s bytes, then uses that name to store, retrieve, reuse, and verify the object.

That rule is small, but it changes what a storage reference means. A path such as /docs/report.txt can point to different bytes tomorrow. A content address continues to identify the same bytes; changing the bytes produces a new address.

This section develops a vanilla CAS from first principles. It covers the architecture, core algorithm, exact-content deduplication, real-world applications, and why file-scale objects need a chunked representation. It does not yet cover the content-defined boundary algorithm, LayerFS object formats, typed identities, object graphs, admission, packs, or production publication. Those mechanisms make sense only after the basic CAS contract is clear.

1. The Address Is the Content

Consider a conventional filesystem path:

/docs/report.txt

The path tells the filesystem where to look. It does not permanently identify the bytes found there. A process can overwrite the file while keeping the same path, or move the same file to a different path. The relationship between name and content is mutable.

A content-addressed store starts from the opposite direction:

object address = hash(object bytes)

The address is derived from the content rather than chosen independently. If the content remains unchanged, so does the address. If the content changes, its address changes too.

Location-addressed storageContent-addressed storage/docs/report.txtVersion 1 bytesVersion 2 bytesH(version 1)Version 1 bytesH(version 2)Version 2 bytes  resolves now  may resolve lateralways identifiesalways identifies






The distinction is not that one system has locations and the other does not. Every physical store eventually places bytes somewhere. The distinction is what the public identity means:

Naming modelA name identifiesIf the content changesIf the content moves
Location-addressedA mutable placeThe name may stay the sameThe name usually changes
Content-addressedOne exact byte sequenceThe address changesThe address can stay the same

This gives CAS a useful vocabulary for immutable data. Two callers referring to the same content address mean the same expected bytes, even if the storage system later changes the object’s physical placement.

It also changes lookup. CAS does not search the store for bytes that resemble a request. The caller already has an object identifier. The store uses that identifier to locate the object directly, just as a key-value store uses a key. The difference is that the CAS key is derived from the value.

2. Identity from Bytes

Let x be an object’s byte sequence and H a cryptographic hash function. A minimal content identity is:

id(x) = H(x)

The hash function processes an input of arbitrary length and produces a fixed-length digest. A CAS depends on several properties:

  • Determinism: hashing the same bytes with the same algorithm produces the same digest.
  • Sensitivity to change: changing the input should produce an unrelated digest.
  • Collision resistance: finding two different inputs with the same digest should be computationally infeasible for the chosen algorithm.
  • Streaming evaluation: the digest can be computed incrementally without loading the whole object into memory.

A digest is not a mathematical proof that collisions are impossible. It is a security assumption: the digest is large enough and the algorithm strong enough that accidental or deliberate collisions are infeasible for the system’s threat model. Long-lived systems also need a way to evolve when a hash algorithm ages; Git’s hash-function transition design is one example of why algorithm choice cannot be assumed permanent.

Identity enables verification

Suppose a caller requests an object by expected_id, and the store returns content. The caller or store recomputes the digest and compares it with the requested ID:

actual_id = H(content)

if actual_id != expected_id:
    return integrity_error

If the values differ, the returned bytes are not the requested object. This can detect corruption, truncation, a faulty locator, or storage returning the wrong object.

This is an integrity check, not a complete security system. It does not prove who created the bytes, whether the bytes are trustworthy, whether they are encrypted, or whether another copy is available for repair.

Identity enables exact deduplication

If an object with address d is already stored, another insertion of the same object can reuse it instead of writing another payload copy. This is exact-content deduplication at the CAS object’s boundary.

Object boundary matters. A CAS storing whole files recognizes two identical files, but a one-byte change makes the edited file a different whole object. Reusing unchanged regions inside the file requires the system to divide the file into smaller objects. Chunking is an additional design choice, not part of the basic CAS definition.

The distinction among CAS, deduplication, and incremental backup is worth making explicit:

ConceptPrimary questionBasic mechanism
Content addressingHow is an object named?Derive identity from content
DeduplicationWhich repeated data can share storage?Store one physical instance and reuse it
Incremental backupWhat changed since a prior backup?Record data or files changed relative to an earlier backup

These techniques often appear together, especially in backup products, but they are not synonyms. The BlinkDisk CAS overview describes a chunked backup system; the chunking is what allows unchanged regions inside an edited file to be reused. Its separate introductions to deduplication and incremental backup show the related storage and backup concepts. A vanilla CAS can deduplicate exact objects without implementing either chunking or an incremental-backup chain.

3. Architecture: A Minimal Content-Addressed Store

A minimal CAS needs a narrow set of responsibilities:

  1. accept a sequence of bytes;
  2. derive its object identifier;
  3. retain one immutable object for that identifier;
  4. locate an object from its identifier; and
  5. optionally rehash returned bytes to verify them.
ClientHashLocate by IDImmutable storeVerify  putIDpublish  get(object ID)getchecked bytes








ComponentResponsibilityNot responsible for
HasherDerive an identity from all object bytesChoosing human-readable names
LocatorResolve an object ID to physical storageDefining the object’s meaning
Object storageRetain immutable payload bytesCheckpoints, branches, or directories
VerifierConfirm returned bytes match the requested IDRepairing a damaged object
Client or higher layerKeep useful names and relationships between objectsChanging an installed object’s bytes

The locator does not have to be a database. A simple store can derive a pathname directly from the digest. A larger store may use an index because objects are packed, remote, replicated, or moved between storage tiers. Both designs preserve the same separation:

logical identity:  which exact bytes?
physical locator:  where are those bytes currently stored?

Above the CAS, an application usually maintains names and relationships that people care about. A backup system maps snapshots and pathnames to stored objects. Git maps trees and commits to objects, then uses mutable branch names to select commits. A container registry maps tags to manifests whose descriptors identify blobs by digest. These upper layers supply meaning; the vanilla CAS stores opaque bytes.

That boundary is deliberate. CAS is easier to reason about when its core contract does not also try to be a filesystem, version-control system, backup catalog, or distributed availability service.

4. Core Algorithm: Put, Get, and Verify

At the conceptual level, a vanilla CAS has two operations:

put(bytes) -> object_id
get(object_id) -> bytes

The core algorithm can be written as:

put(bytes):
    id = hash(bytes)

    if id is not already stored:
        publish bytes as the immutable object named by id

    return id

get(id):
    bytes = locate and read the object named by id

    if hash(bytes) != id:
        return integrity error

    return bytes

The phrase publish ... as immutable hides real engineering work. A production implementation must handle crashes, concurrent writers, a destination that already exists, partial writes, and storage errors without replacing trusted data. Chapter 2 returns to those mechanics. For now, the logical rule is enough: after successful publication, the bytes associated with an object ID never change.

A put operation

Input bytesHashObject already present?PublishReuse  noyes




Both paths return the same object ID; only the publication work differs.

The same input produces the same candidate ID, so exact duplicates converge on one name. A trustworthy implementation must not blindly trust an existing occupant merely because it has the expected pathname; Chapter 2 will strengthen this reuse path with authenticated admission.

A get operation

A caller supplies the ID of the bytes it expects. The store resolves the ID, reads the bytes, and recomputes the digest. A matching digest authenticates the byte sequence against the requested content identity. A mismatch must be reported rather than returning bytes as though the read succeeded.

Verification can occur at different boundaries. A local store may verify every read, verify during admission and rely on trusted immutable media, or let a client verify a remote response. The placement changes cost and trust assumptions, but not the equation H(bytes) = requested ID.

A worked example

Assume strings are stored as their UTF-8 bytes:

WriteInputObject IDAdditional payload stored
1helloH(hello)5 bytes
2helloH(hello)0 bytes
3hello!H(hello!)6 bytes

The application submitted 16 logical bytes, but the CAS retained 11 unique payload bytes. The second hello still had to be identified—normally by reading and hashing its five bytes—but it did not require another payload copy.

This small example captures both the strength and the boundary of vanilla CAS:

  • exact repeats are cheap to retain because they share an ID;
  • any changed byte creates a new whole-object identity; and
  • higher-level references are needed to explain why an application cares about either object.

5. Content Addressing in Real Systems

CAS is rarely the whole product. Mature systems use it as a stable object layer and add structures that give objects meaning, reachability, policy, and efficient physical representation.

SystemWhat content addressing contributesWhat the surrounding system adds
GitStable identities for immutable objectsBlobs, trees, commits, branches, tags, history traversal, and packfiles
VentiImmutable archival blocks named by digestRoots, trees, indexes, caches, and archival policy
Backup systemsExact reuse of files or chunksScanning, chunking, snapshot catalogs, retention, encryption, and restore workflows
OCI imagesDigest-based identification and verification of blobs and manifestsRegistries, tags, media types, distribution, and platform selection
Bazel remote cachingContent-addressed build inputs and outputsAction keys, action results, execution policy, and reproducibility assumptions
IPFSContent identifiers for blocks and directed acyclic graphsChunking, codecs, routing, transfer, mutable naming, and pinning

Git is the most familiar teaching example. Its object database stores content-addressed blobs, trees, commits, and tags, as described in Git Objects. A branch such as main is not itself an immutable content address; it is a mutable reference that selects a commit.

Branch: mainCommit objectParent commitTree objectBlob: README.mdBlob: src/main.rs  mutable reference  



This separation lets Git retain stable historical objects while allowing a branch name to advance. Git also demonstrates that logical identity and physical encoding can evolve separately: packfiles can store objects compactly using deltas without changing the identities used by commits and trees.

Venti is an early, influential example of a network storage system built around content-addressed immutable blocks. Its clients build archival data structures above the block store, reinforcing the same boundary: CAS provides stable blocks, while higher layers provide roots and interpretation.

Container and build systems use the idea for distribution and reuse. The OCI image specification’s descriptors carry digests used to identify and verify referenced content. Bazel remote caching separates a content-addressable store for files from an action cache that maps actions to results. In both cases, CAS is one component in a larger protocol.

Distributed CAS makes another boundary visible. An IPFS content identifier identifies content independently of a particular host, but knowing a CID does not guarantee that a reachable peer currently provides the data. Content identity and content availability are different properties.

Backup systems commonly combine all three concepts introduced earlier: chunking chooses deduplication units, content addressing names them, and snapshot or incremental metadata records recoverable states. Storage savings and restore behavior therefore belong to the complete backup design, not to hashing alone.

6. Why CAS Needs a Chunked Representation

The unit of deduplication in a CAS is the object. If an entire file is one object, two identical files share one identity, but changing one byte changes the identity of the entire file. Without another encoding such as delta compression, the edited version contributes another whole-file payload.

Whole-file CASChunked CASVersion 1H(entire file)One-byte editVersion 2new whole-file IDABCedit BAreusedB′newCreused    same IDsame ID




A chunked representation changes the deduplication unit from the whole file to smaller byte regions. Each chunk receives its own content identity, and a small manifest records their order. After a localized edit, unchanged chunks retain their identities and can be reused; only the affected boundary region and the manifest need new objects.

ChangeWhole-file CASChunked CAS
Exact copyReuse one whole-file objectReuse all chunks and usually the same manifest
Localized overwriteRetain another whole-file payloadRetain affected chunks and a new manifest
Insertion or deletionRetain another whole-file payloadReuse depends on whether chunk boundaries resynchronize
Incremental transferSend the changed whole-file objectSend missing chunks and the new manifest

For example, if a 1 GiB file is stored as one CAS object, a one-byte edit can add another 1 GiB payload. With chunking, the added payload is instead proportional to the affected chunking region plus a small manifest. The exact amount depends on chunk size and boundary behavior; chunking improves the reuse opportunity but does not promise that every edit changes exactly one chunk.

CAS supplies identity and exact reuse; chunking chooses the smaller regions that CAS can reuse.

This creates the next design question: where should the boundaries fall? Fixed-size boundaries are simple, but an insertion can shift every later chunk. The next section introduces content-defined chunking, which chooses boundaries from the content so the stream can resynchronize after a localized edit.