Artifacts: bytes a node produced, addressed by their content

A checkpoint is not a message. DType.ARTIFACT carries a reference — digest,
size, media type, name — so everything on the wire stays JSON and thirty
megabytes never sit in Redis, which answers the vision's open binary-payload
question by narrowing it: inline codecs would only serve payloads too small to
be worth a round trip, and nothing asks for that.

The store is content-addressed rather than per-run, for three reasons that all
pay later: a sweep whose fifty configs share one preprocessed input stores it
once, a reference stays valid however it is passed around because it names
content instead of a location, and the digest is what a stage cache will
compare — so building it in now is what keeps that from being a change to the
message contract.

Node code calls fluksio.save_artifact/load_artifact and cannot tell whether it
is writing the engine's own directory or putting bytes over HTTP, which is
what will let the same flow run on a remote worker unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AD8SfVhzXBG2nAfFcVh3iD
This commit is contained in:
2026-08-18 17:09:27 +02:00
co-authored by Claude Fable 5
parent 3f5bb12ed0
commit b1cb8b41bc
14 changed files with 1481 additions and 93 deletions
@@ -59,6 +59,7 @@ const DTYPES: DType[] = [
"series",
"record",
"list",
"artifact",
]
/** What a list may hold. One declared level: no list of lists. */
@@ -805,6 +806,9 @@ const PLACEHOLDER: Record<DType, string> = {
series: '{"lines": []}',
record: "{}",
list: "[]",
// Bytes never travel as a message: the node stores them and returns what
// the next one opens.
artifact: 'fluksio.save_artifact(b"", "result.bin")',
}
const SCAFFOLD_DOC =