What This Page Covers
A cartridge is a small program. Fifty lines of Python — or Rust, Swift, or Go — and your tool joins the system. The Transmute menu picks it up. The planner routes through it. Other cartridges call it. You didn’t write a plugin; you wrote a citizen.
That part — what a cartridge is, how it speaks the wire protocol, how it registers handlers, how peer invocation works between cartridges — is the cross-platform CapDAG cartridge runtime. It’s specified once and the same on every host:
- CapDAG cartridge runtime — entry point, handler registration, CLI/cartridge mode
- CapDAG cartridge anatomy — manifest shape, cap definitions, lifecycle
- CapDAG handler patterns —
Optrait, argument extraction, output emission - CapDAG model cartridges — three-phase architecture for ML cartridges
- CapDAG content cartridges — document-processing patterns
- CapDAG input/output — InputStream, OutputStream, stream lookup
- CapDAG peer invocation — calling other cartridges from a cartridge
- CapDAG progress and logging — LOG frames, progress mapping, keepalive
For the wire format itself (frame layout, handshake, multiplexed streams) see the Bifaci protocol documents.
This page is the macOS-specific layer on top of all of that: how MachineFabric finds cartridges on disk, how they’re isolated by the operating system, what you have to sign and notarize to ship one, and how you package the whole thing into a .pkg installer users can double-click.
For a from-scratch tutorial that builds a real cartridge end to end, jump to Getting Started. The Python tutorial there goes from empty directory to installed dev cartridge.
Where Cartridges Live on Disk
MachineFabric scans two cartridge roots at startup and watches them for changes:
MachineFabric.app/Contents/Resources/cartridgesrv-signed/cartridges/— bundled cartridges that ship with the application./Library/Application Support/MachineFabric/Cartridges/— user-installed cartridges. This is what.pkginstallers write into and what you populate by hand during development.
Inside either root, every cartridge follows the layout the CapDAG spec mandates:
{cartridges-root}/{slug}/{channel}/{name}/{version}/
cartridge.json
{entry-binary}
{slug} is dev for unpublished cartridges; for cartridges published through a registry it’s a deterministic SHA-256 hash of the registry URL. {channel} is nightly or release. {name} and {version} come from the cartridge’s manifest and must match the directory names exactly — the host verifies this at discovery time and rejects mismatches as bad_installation.
The cartridge.json install record is the host’s pre-spawn validation contract. Required fields: name, version, channel, registry_url (must be present, may be null), entry, installed_at. The full validation rules and the failure modes you’ll see in the cartridges view (manifest_invalid, bad_installation, entry_point_missing, handshake_failed) are walked through in Getting Started step 7.
Sandboxing and Process Isolation
Cartridges run in a sandboxed XPC service, separate from both the engine and the user-facing app. MachineFabric never loads cartridge code into its own process.
The flow at runtime:
- The XPC service has the manifests of every discovered cartridge in memory.
- When a cap is dispatched to a cartridge, the service spawns the cartridge’s entry binary as a child process.
- The cartridge speaks the Bifaci protocol on stdin/stdout — handshake, then frame-multiplexed requests and responses.
- The XPC sandbox restricts what the cartridge can do: by default no network, no access to user files outside what’s explicitly handed in.
- Heartbeats keep the connection alive; a missed heartbeat tears the cartridge down.
The host runtime side of this — process spawn, frame routing, health monitoring — is CapDAG host runtime. The relay layer that bridges multiple cartridges through a single connection is relay switch and relay topology.
What’s macOS-specific is the XPC envelope and the sandbox profile. Cartridges that need entitlements beyond the default (network access for a cloud-proxy cartridge, for example) declare them in the .pkg and have to be reviewed before being accepted into the canonical manifest.
Code Signing and Notarization
Cartridges distributed through MachineFabric — i.e. anything that ends up in the canonical cartridge manifest — must be:
- Code-signed with a Developer ID certificate.
- Notarized by Apple.
- Packaged as a signed
.pkginstaller.
For development you can drop unsigned cartridges directly into /Library/Application Support/MachineFabric/Cartridges/ and approve them in System Settings → Privacy & Security. This is what the Getting Started tutorial does.
For distribution, the build pipeline looks like:
# 1. Build a universal binary (Rust example)
cargo build --release --target aarch64-apple-darwin
cargo build --release --target x86_64-apple-darwin
lipo -create -output mycartridge \
target/aarch64-apple-darwin/release/mycartridge \
target/x86_64-apple-darwin/release/mycartridge
# 2. Sign the binary with hardened runtime
codesign --sign "Developer ID Application: Your Name (TEAMID)" \
--options runtime \
--timestamp \
mycartridge
# 3. Stage the install layout
mkdir -p pkg-root/{slug}/{channel}/mycartridge/1.0.0
cp mycartridge cartridge.json pkg-root/{slug}/{channel}/mycartridge/1.0.0/
# 4. Build the installer package
pkgbuild --root ./pkg-root \
--identifier com.yourcompany.mycartridge \
--version 1.0.0 \
--install-location "/Library/Application Support/MachineFabric/Cartridges" \
mycartridge.pkg
# 5. Sign the package
productsign --sign "Developer ID Installer: Your Name (TEAMID)" \
mycartridge.pkg \
mycartridge-signed.pkg
# 6. Notarize and staple
xcrun notarytool submit mycartridge-signed.pkg \
--apple-id [email protected] \
--team-id TEAMID \
--password "@keychain:AC_PASSWORD" \
--wait
xcrun stapler staple mycartridge-signed.pkg
The {slug}/{channel}/ portion of the install path follows the CapDAG layout rules; for a registry-published cartridge {slug} is the SHA-256 of the registry URL.
Languages and SDKs
The cartridge protocol is language-agnostic. Any binary that speaks Bifaci on stdin/stdout is a valid cartridge. We maintain SDKs in four languages:
| Language | Package | Notes |
|---|---|---|
| Rust | machfab-cartridge-sdk (and capdag crate) |
Reference implementation |
| Go | machfab-cartridge-sdk-go |
Go module, mirrors the Rust API |
| Swift / Objective-C | MACHFABCartridgeSDK |
Swift Package |
| Python | capdag on PyPI (with the ops framework as a transitive dep) |
Used in the Getting Started tutorial |
The SDKs do two things: they implement the Bifaci protocol so you don’t have to, and they expose typed data structures (FileMetadata, DocumentOutline, DisboundPage) so cartridges that produce the same kind of output produce JSON of the same shape. See SDK Reference for the data structures, and the CapDAG handler patterns document for how to register and implement the actual Op instances.
You don’t have to use an SDK. A shell script that prints a JSON manifest on manifest and writes the right CBOR frames on stdout is a cartridge. The SDKs are convenience, not contract.
Implementation Differences Between Hosts
MachineFabric’s macOS host is the reference implementation. The CapDAG cartridge runtime is implemented in both Rust (the canonical version) and Swift (the version used inside the macOS app for in-process cartridges). The two are kept in lockstep on the protocol surface but differ in module coverage; if you’re writing a cartridge that needs unusual host integration, see CapDAG Rust vs Swift for the matrix of what each implementation supports.
For task-level integration (how a cartridge invocation appears in the MachineFabric task UI, how progress events surface, how memory pressure on macOS feeds back into cartridge lifecycle), see task integration, error handling, and memory pressure detection.
Publishing
To make your cartridge available through MachineFabric’s cartridge browser, propose it on cartridge-shelf, the public submissions inbox. Every submission is reviewed by hand and merged into the canonical cartridges.machinefabric.com manifest that ships with every install.
The full submission process and inclusion criteria — what the repository must contain, what cap URN validity entails, and what happens between submission and acceptance — are in Contributing.