Skip to main content

Compose and composectl

composectl (sister repo at ~/git/webassembly-component-orchestration/) is a component-orchestration substrate. Pylon consumes it as a library, embedded in packaging/launcher-rs/pylon-launch.

Composectl gives us:

  • Content-addressed blob store at .compose/blobs/. Every component .wasm is stored by SHA-256 digest. The plan JSON refers to digests, not paths.
  • Trust store. Digests must be trusted before instantiation. pylon-launch trusts each digest embedded in the plan via composectl's Rust API (workaround while the standalone composectl trust add CLI is not yet exposed).
  • Plan JSON loader. A single JSON document names the root, the set of components, the compose-time bindings between them, and the runtime dynlink capabilities the host grants.

Plan JSON shape

The full reference is Plan JSON structure. A representative slice from plans/python-pylon-dynlink.json:

{
"version": "1",
"root": "python",
"linkage": "runtime",
"components": [
{ "id": "aead", "digest": [112, 58, 92, ...] },
{ "id": "arrow-core", "digest": [181, 17, 115, ...] }
],
"bindings": [
{
"consumer_id": "python",
"import_name": "arrow:core/array@0.1.0",
"provider_id": "arrow-core",
"export_name": "arrow:core/array@0.1.0"
}
],
"policy": {
"determinism": "relaxed",
"capabilities": [
{ "name": "dynlink:resolve", "level": "required" },
{ "name": "dynlink:invoke", "level": "required" }
]
}
}

Load-bearing fields:

  • root — which component to invoke run.run() on. Always python.
  • linkage: "runtime" — composectl resolves bindings at instantiation time (as opposed to "static", which requires pre-composed artifacts).
  • components[].digest — 32-byte SHA-256 of the blob in .compose/blobs/. Pylon-launch trusts each digest before handing it to composectl.
  • bindings — every non-wasi import declared by python.wasm needs one entry naming which component satisfies it. wac-graph matches on (consumer_id, import_name).
  • policy.capabilities — grants for runtime-resolved features. dynlink:resolve / dynlink:invoke gate the runtime-dynlink path.

The plan is auto-generated by scripts/build-pylon-dynlink-plan.py, which walks every non-wasi import declared by python.wasm, locates the corresponding cap component .wasm on disk (env-var-overridable paths), stages each into .compose/blobs/, and writes the JSON.