LayerFS: Filesystem Storage for Parallel AI Agents
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.
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.
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.
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.
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.
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.
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 model
A name identifies
If the content changes
If the content moves
Location-addressed
A mutable place
The name may stay the same
The name usually changes
Content-addressed
One exact byte sequence
The address changes
The 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.
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.
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.
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:
Concept
Primary question
Basic mechanism
Content addressing
How is an object named?
Derive identity from content
Deduplication
Which repeated data can share storage?
Store one physical instance and reuse it
Incremental backup
What 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.
A minimal CAS needs a narrow set of responsibilities:
accept a sequence of bytes;
derive its object identifier;
retain one immutable object for that identifier;
locate an object from its identifier; and
optionally rehash returned bytes to verify them.
ClientHashLocate by IDImmutable storeVerifyputIDpublishget(object ID)getchecked bytes
Component
Responsibility
Not responsible for
Hasher
Derive an identity from all object bytes
Choosing human-readable names
Locator
Resolve an object ID to physical storage
Defining the object’s meaning
Object storage
Retain immutable payload bytes
Checkpoints, branches, or directories
Verifier
Confirm returned bytes match the requested ID
Repairing a damaged object
Client or higher layer
Keep useful names and relationships between objects
Changing 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.
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.
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 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.
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.
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.
System
What content addressing contributes
What the surrounding system adds
Git
Stable identities for immutable objects
Blobs, trees, commits, branches, tags, history traversal, and packfiles
Venti
Immutable archival blocks named by digest
Roots, trees, indexes, caches, and archival policy
Backup systems
Exact reuse of files or chunks
Scanning, chunking, snapshot catalogs, retention, encryption, and restore workflows
OCI images
Digest-based identification and verification of blobs and manifests
Registries, tags, media types, distribution, and platform selection
Bazel remote caching
Content-addressed build inputs and outputs
Action keys, action results, execution policy, and reproducibility assumptions
IPFS
Content identifiers for blocks and directed acyclic graphs
Chunking, 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.
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.
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.
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.
Change
Whole-file CAS
Chunked CAS
Exact copy
Reuse one whole-file object
Reuse all chunks and usually the same manifest
Localized overwrite
Retain another whole-file payload
Retain affected chunks and a new manifest
Insertion or deletion
Retain another whole-file payload
Reuse depends on whether chunk boundaries resynchronize
Incremental transfer
Send the changed whole-file object
Send 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.