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.
What a manifest contains
Section titled “What a manifest contains”{ "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.
Why a bundle and not N image inputs
Section titled “Why a bundle and not N image inputs”- 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
storageRefis 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
Section titled “Bundle Manifest Input”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.
Processing each file
Section titled “Processing each file”The manifest is JSON, so the pattern is the one for any JSON list:
- Extract (
utility/extract) readsassetsfrombundle, as JSON. - JSON Enumerator (
enumerate/json) gives one iteration per file. MapstorageRefto animageoutput (orvideo,audio,document),idto text, and each metadata field you need —metadata.role,metadata.caption— to text. - 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 lab
Section titled “The lab”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 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.
node madoo-install-example.mjs lab-bundle --api <your Madoo API URL> --key <key.json> --runThe 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.
Credits
Section titled “Credits”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).
Limits
Section titled “Limits”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.
Errors
Section titled “Errors”| 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 |