Skip to content

Asset bundles

Many jobs start from a package of files rather than from one: the twelve photos of a flat for sale, the images of a product with its packshots and details, the press kit of an exhibition, the pictures a hotel sends for a campaign. Two things are true of every such package. The number of files changes — eight photos today, fifteen tomorrow — and each file carries information that decides what happens to it: this one is the cover, that one the kitchen, this one may be used only on the website.

A workflow with a fixed number of image inputs cannot express either. A bundle can. It is one document, the bundle manifest (madoo.bundle-manifest/v1), that lists the uploaded files, one entry each, with what Madoo needs to trust the file and what you need to use it. The workflow receives the whole package on one input, checks every file once, and then processes each one — however many there are.

{
"schema": "madoo.bundle-manifest/v1",
"bundleId": "apollo-exhibition-press-kit",
"assets": [
{
"id": "aldrin",
"relativePath": "press-kit/apollo/apollo11-aldrin.jpg",
"storageRef": "<the uploaded asset path>",
"mediaType": "image/jpeg",
"sizeBytes": 262221,
"sha256": "8f2c…",
"metadata": { "role": "cover", "caption": "Buzz Aldrin walks near a leg of the Lunar Module, Apollo 11, 1969" }
}
]
}

Some fields are yours, some are Madoo’s:

Field Who writes it Meaning
bundleId you a name for the package
id you, or derived from the file name a stable name for the file inside the package
relativePath you, or the file name where the file sits in the package, as a portable path (photos/kitchen-01.jpg)
metadata you anything the workflow needs: a role, a room, a caption, the rights
storageRef Madoo the uploaded file’s path in your workspace — never a public URL
mediaType, sizeBytes Madoo read from storage
sha256 Madoo the file’s checksum, so a replaced or damaged file is caught

Never write the technical fields by hand. Compose the manifest from the uploaded files: POST /api/v1/bundle-manifests/compose on REST, compose_bundle_manifest on MCP and in the AI tools, Build bundle in the editor. You give the asset paths and the metadata; Madoo checks that each file is yours and uploaded, reads its type and size, and computes the checksum.

  • Any number of files. The same workflow takes three photos or three hundred (up to 10 000), without redesign.
  • The information travels with the file. The role, the room, the caption stay attached to the right photo through every step, and come back in the results.
  • Checked once, before any work. Every file is verified before the workflow fans out: it belongs to your workspace, its content really is the declared type, its size and checksum match. A file that fails never reaches a paid step.
  • One input, the same everywhere. A bundle is a JSON document: the editor, REST, MCP, the AI Agent and the AI Assistant compose, check and pass it the same way, and a client runs the workflow with one value.
  • Portable. A storageRef is a path in Madoo storage, not a signed link that expires, so the manifest stays valid; sharing or cloning a workflow copies its saved bundle’s files and rewrites the paths.

A table of file names is not a substitute: a CSV of paths is only text, and nothing checks that the files exist or belong to you. Use a bundle for files, and a dataset for rows of data.

Bundle Manifest Input (input/bundle_manifest) receives the manifest — saved in the node as a default, or supplied at run time as a JSON object on the input named after the node; the runtime one wins — and checks it:

Setting Options
Validation fail stops the run at the first file that fails its checks; warn goes on with the files that pass and reports the others
Verify Checksum always requires a checksum on every file and verifies it; if_present verifies the ones declared; never skips it

It produces bundle (the checked manifest, with only the accepted files), asset_count, valid and issues (what was rejected and why, with the path of the field). In warn mode valid is false whenever a file was left out: the run goes on and still ends completed — the rejected files are left out before the fan-out, and only valid and issues say so. Wire them to an output (or a check) when the issues must reach whoever supplied the package. Choose fail when every file matters (a contract, a legal notice), warn when the job can go on without one photo.

The manifest is JSON, so the pattern is the one for any JSON list:

  1. Extract (utility/extract) reads assets from bundle, as JSON.
  2. JSON Enumerator (enumerate/json) gives one iteration per file. Map storageRef to an image output (or video, audio, document), id to text, and each metadata field you need — metadata.role, metadata.caption — to text.
  3. The nodes after it run once per file. To treat one role differently, compare the role with a Predicate and pass the file through a Filter; to collect the results, end with an aggregator.

The bundle lab is the press kit of an Apollo exhibition: four NASA photos (public domain), each with a role — cover, portrait, gallery — and a caption. The workflow checks the bundle (warn, checksum always), measures every photo with Media Technical Info, builds an index with id, role, caption and size, and delivers the cover, chosen by its role.

The lab, left to right: the press kit photos; Assets; One photo at a time; Measure and Is the cover; the press kit index and Only the cover

The kit as composed The same kit, Earthrise declared with a wrong checksum
asset_count 4 3
valid true false
issues none ASSET_CHECKSUM_MISMATCH at /assets/3/sha256
Index aldrin (cover, 1280 × 1249), crew (portrait, 1005 × 1280), earth-horizon and earthrise (gallery, 1280 × 1280) the first three
Cover the Aldrin photo the Aldrin photo

Both runs complete in a few seconds at no cost. In the second, the file whose bytes do not match its declared checksum is left out before any photo is processed, and the report names it; the rest of the kit goes on.

Terminal window
node madoo-install-example.mjs lab-bundle --api <your Madoo API URL> --key <key.json> --run

The installer uploads the four photos, composes the bundle with checksums and saves it in the workflow. The second run passes its own manifest at run time: run-inputs-tampered.json.

A complete example with AI steps is F07 — property listing from a photo bundle: eight photos of a flat, each checked, described by an AI model, the cover chosen by role, laid out as a listing.

A bundle is counted only when the workflow runs: the estimate of a workflow whose paid steps follow the fan-out reports the cost per file and marks the count as deferred. Deferred is not free. When the package can be large, run with admission_limits — max_iterations_per_node and max_admitted_milli_credits — so a bundle bigger than expected stops before it spends more than you meant (Executions).

Up to 10 000 files per bundle; id and bundleId up to 128 characters; a runtime manifest up to 4 MiB. Paths are relative: no leading /, no .., no URLs. Only files uploaded to your workspace are accepted — external links never are.

Issue or code Cause Fix
ASSET_NOT_FOUND, ASSET_UPLOAD_PENDING the file is not in storage, or its upload is not finished upload it again, or wait for the upload to complete
ASSET_SIZE_MISMATCH, ASSET_MEDIA_TYPE_MISMATCH the declared size or type does not match the stored file compose the manifest instead of writing it
ASSET_CONTENT_SIGNATURE_MISMATCH the file’s content is not the declared type (a .jpg that is not a JPEG) upload the right file
ASSET_CHECKSUM_MISMATCH, ASSET_CHECKSUM_REQUIRED the bytes changed since the manifest was made, or a checksum is missing with always compose the manifest again from the current files
DUPLICATE_ASSET_ID, DUPLICATE_RELATIVE_PATH, INVALID_RELATIVE_PATH two files share a name or path, or a path is not portable give each file its own id and a relative path
EXTERNAL_URL_NOT_ALLOWED a file is a URL upload it (or import it from its URL) and use its asset path
FORBIDDEN a file belongs to another workspace use files of the workspace that runs the workflow
BUNDLE_MANIFEST_INVALID Validation is fail and a file failed its checks read the first issue in the message, or use warn
INVALID_MANIFEST the manifest is not a valid madoo.bundle-manifest/v1 document compose it; pass it as a JSON object, not as a string of JSON
invalid_asset_path (the run is refused) the manifest file itself was passed as an asset_path input pass the manifest as the JSON value of the bundle input, not as a file
MEDIA_SOURCE_INVALID a file mapped to an image (or video, audio) port is not that kind of media; the fan-out stops before any item map only matching files to media ports, or filter by mediaType first