Skip to Content
Mélodium 0.11.1 is now available!
BlogMélodium 0.11.1: caches, artifacts, and secrets of Cadence.CI

Mélodium 0.11.1: caches, artifacts, and secrets of Cadence.CI

Quentin Vignaud

Written by

Quentin Vignaud

November 7, 2026

Mélodium

Mélodium 0.11.1 is out. The language does not change, but the standard library grows a lot: CI/CD steps get caches and artifacts, values of a stream can run in tracks of their own, and new packages compress data and archive directories while it flows. Programs run by Cadence.CI  can also read the secrets of their organization.

Caches and artifacts for CI/CD steps

A step can now restore a directory before its commands and save it once they succeed. In the CI Pipeline showcase, crates and build outputs live in the cache directory of the step’s data volume, and |cache makes them the cache named cargo:

variables = |wrap<StringMap>(|map([ |entry("REPO_URL", repo_url), |entry("CARGO_HOME", "/mnt/data/cache/cargo"), |entry("CARGO_TARGET_DIR", "/mnt/data/cache/target") ])), cache = |wrap<Cache>(|cache(|wrap<string>("data"), "cache", "cargo")),

Every save is a new version, and the next run restores the last one, so nothing needs a bump when dependencies change. When the name depends on the program, such as a digest of Cargo.lock, the Cached variants of the steps (stepOnCached, simpleStepCached…) take it on a cache_name input. A miss or a failed save only logs a warning.

Artifacts work the same way from the other end. |artifact names the files of a directory, and |artifact_file a single file, and steps take them as artifacts: once the commands succeed, each artifact is pushed to the run on Cadence.CI , in a track of its own, before the step finishes. Outside of steps, work/artifact pushes and fetches artifacts directly; fetched files are checked against their SHA-256, and a file that differs is removed.

None of this is a feature of the engine: caches and artifacts are written in Mélodium, from the pieces below. Saves are archived with tar, checked for secrets, compressed with zstd, and sent in parts while they are made. Restores are downloaded with resumption and extracted next to their target before replacing it.

Scatter and gather

std/flow/scatter gives each value of a stream a track of its own. scatter makes one track per value, at most parallel at once, and the scattered source gives each track its value and a value shared by all of them. Tracks inherit the contexts of the track that made them, along with @Scattered, and tell how they ended through gather. A track that ends without telling counts as failed. scatterGathering also collects the values tracks give to gatherValue, in the order they end.

Artifacts use it to push each file in a track of its own, and uploads to send their parts side by side.

Two engine changes made this possible, both visible to Rust library authors: sources of models can now be generic, and the tracks they create can inherit the contexts of their parent track.

Data on the move

Four packages gained what caches and artifacts needed, and what other programs will need too:

  • compression is new. It compresses and decompresses byte streams while they flow with zstd, gzip, zlib, deflate, and brotli, and decompresses deflate64. Every format is pure Rust and works on WebAssembly.
  • fs archives a directory of any file system into a tar stream with fs/archive::tar, and extracts one with untar. Archives are deterministic, links stay links and are never followed, and extraction never writes outside of its target. File systems also learn to describe, list, link, remove, and rename entries, and fs/dir::files lists the regular files of a tree.
  • http takes prepared requests , such as those presigned by object storage. http/client::download resumes after a cut connection, without giving a byte twice, and http/upload sends data in parts, each retried on its own.
  • crypto gives SHA-256 digests of streams and byte vectors, and json reads values by key , index , or JSON pointer , builds objects  and arrays , and converts typed values to and from JSON (|into_json, |from_json).

Secrets of Cadence.CI

Programs run by Cadence.CI , with melodium run or melodium dist, get three new secret sources:

  • api: reads the secrets stored for the organization of the run, such as api:prod/db-password;
  • api-id: gives an identity token of the run for an audience, so that a service trusting Cadence.CI  can authenticate it, such as Vault : vault::Vault(auth="jwt", jwt="api-id:vault");
  • api-run:token gives the token of the run itself, for requests to its endpoints.

None of them is ever plainly revealed, and elsewhere they are refused with the reason. The api, api-id, and api-run schemes are reserved: a model registering a source under one of those names makes the launch fail. In the other direction, work/secret::store saves a secret a program produced, such as a generated password, for later runs to read through api:.

Data leaving the engine was the last gap. Log messages were already masked, but a file sent elsewhere was not. std/secret::scan checks a byte stream for any value the engine revealed, as is, in base64, URL-encoded, or as raw bytes, and stops the stream when it finds one, naming the secret but never the value. Caches and artifacts go through it before they are sent.

The Secrets page of the documentation covers the new sources.

Volumes stay within their walls

work/resources::getFileSystem now gives the file system of a volume rooted at its directory, whatever the executor. Paths with .., absolute paths, and links leading out of the volume are refused, including links that other executors sharing the volume create in the meantime. |local_filesystem behaves the same when given a root.

Genesis checks are back

The engine checks the tracks of sources again when building a program, a check suspended since February 2025. It now runs in time proportional to the size of the program, whatever the number of paths leading to each build. Connection loops and missing contexts fail before the program starts again, instead of during the run.

Fixes

  • simpleStep and simpleStepWithInput now give their max_duration to the worker, which kept the default of one hour.
  • The storage of their container is raised to the size of its /mnt/data volume when out_storage is larger, instead of having the request refused.
  • They also take tags, hints given with the worker request, such as name=build.
  • json fromNumberU64 and fromNumberI64 no longer narrow numbers to 16 bits, nor panic on larger ones.
  • Compose gives containers whose names are not valid hostnames, such as names with _, a hostname where invalid characters become -, instead of failing.

What to update

  • Service containers: |service_container takes secret_env after env. Add a _ to existing calls, as the CI Pipeline showcase does.
  • Pull secrets: secrets sent with a worker request go by name when stored on Cadence.CI (api:), by value when their transmission is value, and are refused when local. Pull secrets with the default policy are now refused. Compose refuses secrets given by name.
  • File systems: paths leading out of a volume, or out of the root of |local_filesystem, are refused. ContainerFileSystem and KubeFileSystem are removed.
  • Genesis: programs with connection loops or missing contexts in the tracks of sources used to start, and are refused again.

Full details for every crate are in the CHANGELOG . As usual, upgrade with cargo install melodium or grab a prebuilt binary.