I called a snapshot portable until I asked what another machine would need in order to read it.
On the original host, restoration was straightforward. The hypervisor knew the format. The virtual devices were familiar. Disk paths still existed. The same backend could load the memory and register state it had written. That was local recovery, not portability.
Move that file to a browser, a mobile device, a different hypervisor, or a bare-metal board and the word snapshot stops explaining much.
“This process can resume here” and “this program can resume somewhere else” are different properties.
The second one needs more than pages and registers. It needs an addressable description of the execution state, the complete dependency closure behind that state, a portable contract for the destination, and a fresh decision about which authority is allowed to survive the move.
This is the direction behind Hologram’s space contract: a browser, native machine, mobile device, or bare-metal board becomes a place Hologram can run by implementing the same storage, resolution, runtime, and host-capability surface.
The suspended session is no longer “the VM file on node 17.”
It becomes an object that another conforming space can resolve and admit.
A machine snapshot carries the machine with it
A hypervisor snapshot may include guest memory, vCPU state, disk overlays, emulated-device state, and backend-specific metadata.
That is tremendously useful. It also carries assumptions about the environment that created it:
- the same virtual hardware model
- a compatible hypervisor format
- matching firmware and kernel behavior
- equivalent attached devices
- expected storage paths
- compatible network identity and external resources
flowchart TD
V[Hypervisor snapshot] --> M[Memory pages]
V --> C[CPU state]
V --> D[Virtual devices]
V --> P[Backend metadata]
M --> H{Compatible host?}
C --> H
D --> H
P --> H
H -- yes --> R[Restore locally]
H -- no --> X[Format or environment mismatch]
For rollback, warm starts, debugging, and same-backend recovery, that may be exactly the right representation.
It is not yet a general application portability contract.
The design question is which state belongs to the program and which state belongs to the machine that happened to host it.
Name the suspended state by what it contains
Let be a canonical snapshot object containing the runtime state required to continue the application.
Its identity is:
Rather than embedding or referring to one host’s paths, the snapshot points to other objects by address:
- the application or module archive
- memory state
- filesystem roots
- model weights
- configuration
- runtime version
- event-log position
- requested capabilities
flowchart LR
S[Snapshot κ] --> A[Application archive κ]
S --> M[Memory state κ]
S --> F[Filesystem root κ]
S --> C[Configuration κ]
S --> E[Event position κ]
S --> R[Runtime contract version]
The snapshot becomes the root of a content graph rather than a pointer into one node’s directory tree.
That changes what migration means. The destination resolves the root, walks every reachable dependency, verifies each object, checks that it can implement the requested runtime contract, and only then attempts to resume.
Location is no longer the identity of the suspended work.
The closure, not the root file, is what moves
Copying the snapshot object alone is not enough.
If it refers to a model archive, five tensors, a filesystem manifest, a configuration object, and a runtime module, the destination needs all of them.
Let be the transitive set of addressed dependencies reachable from the snapshot root. Resume requires:
flowchart LR
S[Resolve snapshot κ] --> G[Walk dependency graph]
G --> L{Object local?}
L -- yes --> V[Verify address]
L -- no --> F[Fetch through sync]
F --> V
V --> N{More dependencies?}
N -- yes --> G
N -- no --> B[Begin resume]
Content addressing makes every local store a potential cache of the same immutable object space. The destination can fetch only what it lacks and verify each object when it arrives.
The mutable question becomes “which root is current?” rather than “which sectors of this host-specific disk image have changed?”
That is a better unit for deduplication, synchronization, and offline transfer.
“Somewhere else” has to be a contract
Portable state is only useful when the destination knows what services the program expects.
Hologram calls a conforming host a space. A space provides a common surface such as:
- a
KappaStorefor local addressed content - a resolver or synchronization path for missing objects
- a container or execution runtime
- entropy and clocks
- a spawner or event-loop seam
- an interaction surface
flowchart TB
App[Portable application / snapshot]
App --> Contract[Space contract]
Contract --> Browser[Browser space]
Contract --> Native[Native space]
Contract --> Mobile[iOS space]
Contract --> Bare[Bare-metal space]
Browser --> OPFS[OPFS + WASM engine]
Native --> FS[Native store + fast engine]
Mobile --> INT[Interpreter + platform storage]
Bare --> HAL[Block device + run loop]
A browser may realize the store through OPFS and run the engine through WebAssembly. A native host may use a filesystem-backed store and a faster native executor. A small device may use an interpreter and a block device.
Those differences remain behind the contract. The application should not acquire a separate identity or semantics because the destination uses another realization.
A host either implements the required surface and passes compatibility tests, or the resume is refused. “Portable” should not mean “quietly drops the parts this target cannot provide.”
Authority does not belong inside the luggage
The most dangerous part of moving execution is ambient authority.
A serialized memory image can contain live credentials, host paths, temporary leases, network handles, or process-local capability tokens. Copying those bytes does not make them meaningful—or safe—on the destination.
The destination has to make its own admission decision:
sequenceDiagram
participant A as Source space
participant K as Address store
participant B as Destination space
participant P as Destination policy
A->>A: suspend runtime state
A->>K: store snapshot and closure
K-->>A: snapshot κ
B->>K: resolve and verify snapshot closure
B->>P: request capabilities for resume
P-->>B: fresh attenuated grant or refusal
B->>B: restore state with new handles
The clean relationship is:
The destination may grant less authority than the source had. It must not grant more because a stale handle happened to survive in memory.
This is the same rule that appears in secure microVM forks: computation can move; authority has to be admitted again.
A snapshot is allowed to describe what the program would like to reconnect to. It is not allowed to carry unquestioned permission to do so.
Stable references replace host paths
A portable snapshot cannot depend on:
/var/lib/app/model.bin
or:
node-17:/mnt/volume-3
Those strings identify placements inside one environment.
The portable state should refer to the underlying object:
κ:model-weights
κ:filesystem-root
κ:configuration
The destination resolver can find that object in local OPFS, a native store, a peer, a registry gateway, or a bundled archive.
flowchart LR
R[Object reference κ] --> L[Local store]
R --> P[Peer sync]
R --> G[Registry gateway]
R --> B[Bundled archive]
L --> V[Verify on receipt]
P --> V
G --> V
B --> V
Placement can change without changing the snapshot’s identity.
That separation is what lets a suspended program survive a different storage topology.
Some live state cannot honestly move
Not every resource has a portable continuation.
An open TCP connection to a third-party server, a sensor halfway through a read, a backend-specific GPU kernel, a file descriptor into an unaddressed host path, or a nondeterministic external interaction cannot simply be serialized and expected to resume elsewhere.
The runtime needs an explicit response:
- refuse suspension while the resource is live
- close it and record a reconnect intent
- translate it into an addressed logical resource
- use a backend-specific extension and narrow the portability claim
- mark the snapshot as host-bound
What it should not do is hide the dependency and continue calling the artifact portable.
A snapshot can carry a profile of its requirements:
The destination checks that profile before restore. A refusal is more useful than a partial resume that discovers the mismatch after state has already been instantiated.
Local and portable snapshots can coexist
This work does not make machine snapshots obsolete.
A full VM snapshot is excellent when the goal is fast same-backend restore, exact machine debugging, or preservation of an operating-system workload.
An application-level addressed snapshot is better when the goal is movement between host classes, deduplication, independent dependency resolution, and fresh authority on the destination.
flowchart TD
W[Running workload] --> M[Machine snapshot]
W --> A[Application snapshot]
M --> F[Fast same-backend restore]
M --> D[Detailed machine debugging]
A --> P[Cross-space portability]
A --> C[Content graph + deduplication]
A --> R[Fresh capability admission]
A system can use both. The machine snapshot can be a performance cache; the addressed application snapshot can be the durable portable representation.
The mistake is to confuse the fast local cache with the contract that another machine is expected to understand.
Resume is a state transition, not a file load
A trustworthy resume path verifies more than the outer object:
- the snapshot address
- every dependency address
- runtime and format versions
- compatibility with the destination space
- the requested capability set
- provenance or signature requirements
- that stale source-side sessions cannot reconnect
stateDiagram-v2
[*] --> Resolving
Resolving --> Verifying
Verifying --> Admitting
Admitting --> Restoring
Restoring --> Running
Resolving --> Refused: missing closure
Verifying --> Refused: digest mismatch
Admitting --> Refused: capability policy
Restoring --> Refused: incompatible runtime
Only after those transitions does the destination instantiate the runtime and restore the application state.
That makes resume auditable. It also gives each failure a named boundary instead of reducing every problem to “snapshot failed to load.”
Portability belongs to the whole stack
A portable language is not enough. Neither is portable bytecode, a portable archive, or a portable memory format by itself.
The execution moves only when identity, storage, dependencies, runtime semantics, capabilities, and host contracts line up.
Try moving the smallest thing first
Do not start with a full VM image. Pick one suspended program and write its closure:
- what state does it need to resume?
- which bytes name that state?
- which dependencies are outside the snapshot?
- which host capabilities must the destination provide?
- which authority must be minted again?
If the list ends with “whatever the original machine happened to have,” the artifact is a local checkpoint. That is still useful. It just is not portable yet.
That is why I like the word space more than backend here. A backend sounds like a code-generation target. A space is a complete place where an object can be resolved, admitted, run, observed, suspended, and resumed.
The snapshot looked portable while I was only asking the original machine to read its own work.
A program becomes portable when another space can reconstruct what it needs, refuse what it cannot support, mint new authority, and continue without pretending the destination is the source. “Somewhere else” has to be an explicit contract rather than a hopeful file copy.