Move Artifacts
The bus is a coordination layer, not a file transfer. When work produces something large — a model checkpoint, a video frame, a directory tree — you move it as a content-addressed blob and put only a small reference on the bus.
Reference, don't embed
The event carries a BlobRef; the bytes travel separately, on demand, to whoever
actually needs them. An event that carries the payload has turned a coordination
message into a transfer, and every subscriber pays for it whether they wanted the
bytes or not.
The address is the content
Blobs are addressed by their BLAKE3 hash. The address is the content, which has consequences worth stating rather than deriving:
- There is no canonical home to provision. Blobs live on whichever nodes have capacity and drift toward the nodes that read them.
- A reference is verifiable. Bytes that hash to the reference are the right bytes, from whichever peer served them.
- Identical content is one blob. Storing the same bytes twice does not double the storage.
Three guarantees the transfer path makes
Peak memory is one chunk (~4 MiB), regardless of total size. A blob larger than RAM transfers fine; the transfer is streamed, not buffered.
A directory fetch is all-or-nothing. It either becomes the complete tree or leaves the destination untouched. There is no partially-materialized state to clean up.
A producer reading back its own write never sees a gap. The write path returns a token the read path waits on — read-your-own-writes, so the common write-then-immediately-read sequence is not a race.
Serving must be installed before fetching
A node cannot serve chunks, and in most bindings cannot issue fetches, until blob transfer has been installed on it with a storage adapter. This is one call and it is easy to omit, because omitting it produces a fetch that fails to find a holder rather than an error naming the missing install.
Bridged tools have no artifacts
Artifacts are a native capability. Tools brought in through the
MCP bridge are mcp_bridge tier —
request/response only, no artifacts. If the work needs to move bytes, it has to be
a native capability, not a wrapped one. This is a tier boundary, not a limitation
to route around.
Where the full surface lives
This page is the entry point per binding. The storage and gravity model, the
adapter configuration, overflow behaviour and the operator CLI
(net-mesh transfer send-blob / recv-blob / send-dir / recv-dir) are in
Blob Storage (Dataforts) and the
CLI Reference.
Move it — Rust
The transfer functions are free functions in net_sdk::transport that take the
mesh first.
Install, then fetch
use net_sdk::transport;
use std::sync::Arc;
transport::serve_blob_transfer(&mesh, adapter.clone()); // once per node
// From a known holder.
let bytes = transport::fetch_blob(&mesh, holder_node_id, &blob_ref).await?;
// Or let the mesh discover a holder by content hash.
let bytes = transport::fetch_blob_discovered(&mesh, &blob_ref).await?;
// Directories: manifest + leaves, materialized atomically.
let stats = transport::fetch_dir(&mesh, holder_node_id, &manifest_ref, dest, 8).await?;serve_blob_transfer(&mesh, adapter) takes an Arc<MeshBlobAdapter> and returns
nothing — there is no handle to keep and no error to check. fetch_dir's last
argument is a concurrency count, not a timeout.
Reference, don't embed
node.emit(&serde_json::json!({
"frame_id": "abc123",
"blob": blob_ref, // the consumer fetches on demand
}))?;What the errors distinguish
fetch_blob and fetch_blob_discovered both return TransferError, and they fail
differently on purpose. Discovery maps "no peer served it" to
TransferError::AllPeersFailed rather than a not-found, so a caller can tell
nobody has this blob apart from this holder does not have it.
BlobRef::Tree is not supported by the discovery path. Small and Manifest
are.
Verify it worked
let bytes = transport::fetch_blob_discovered(&mesh, &blob_ref).await?;
assert_eq!(bytes.len() as u64, blob_ref.size(), "short read");
println!("fetched: {} bytes", bytes.len());The content is self-verifying — the hash in the reference is the check — so a length assertion is about the transfer completing, not about the bytes being right.
Next: Errors and recovery.