What This Page Covers
There are four cartridge SDKs: Rust, Go, Swift/Objective-C, and Python. They’re at different stages of completeness and they target different surface areas:
| Language | Package | What’s in it today |
|---|---|---|
| Rust | machfab-cartridge-sdk (re-exports capdag::*) |
LLM payload types and standard URN constants |
| Go | machfab-cartridge-sdk-go |
LLM payload types mirrored from the Rust SDK |
| Swift / Objective-C | MACHFABCartridgeSDK (Swift Package) |
Minimal — CSCartridgeCaps wrapper for caps assembled in app code |
| Python | capdag on PyPI (with the ops framework as a transitive dep) |
Full cartridge runtime: CartridgeRuntime, CapManifest, CapUrnBuilder, Op, DryContext/WetContext |
The high-level cartridge runtime — the thing that gives you CartridgeRuntime, Op, request handling, peer invocation, and manifest construction — is canonical in the Python SDK today. The Getting Started tutorial walks through the entire surface end to end against a real cartridge. If you’re writing your first cartridge, do it in Python and follow that guide.
The Rust and Go SDKs ship the payload types for LLM-style cartridges (generation requests, streaming responses, vocab and model-info responses) plus the standard cap URN constants the rest of the system uses to address them. They are appropriate when you’re writing a cartridge that participates in the LLM, embeddings, or vision pipelines that MachineFabric ships with — you can build on the same payload contract the bundled cartridges use without reverse-engineering the JSON.
The protocol the SDKs sit on top of — the Bifaci wire format, the cartridge runtime contract, handler patterns, peer invocation, progress and logging — is specified once in CapDAG and is the same on every host:
- CapDAG cartridge runtime
- CapDAG cartridge anatomy
- CapDAG handler patterns
- CapDAG model cartridges
- CapDAG content cartridges
- CapDAG input/output
- CapDAG peer invocation
- CapDAG progress and logging
This page documents the SDK surfaces — the concrete types and constants you’ll import.
Python SDK (capdag)
Installation:
pip install capdag
Python 3.12 or newer is required. capdag pulls in the ops framework as a transitive dependency.
Core imports
from capdag.bifaci.cartridge_runtime import (
CartridgeRuntime,
Request,
WET_KEY_REQUEST,
)
from capdag.bifaci.frame import FrameType
from capdag.bifaci.manifest import CapManifest, default_group
from capdag.cap.definition import (
Cap, CapArg, CapOutput,
PositionSource, StdinSource,
)
from capdag.standard.caps import CAP_IDENTITY
from capdag.urn.cap_urn import CapUrn, CapUrnBuilder
from ops import DryContext, Op, OpMetadata, WetContext
CartridgeRuntime
runtime = CartridgeRuntime.with_manifest(manifest)
runtime.register_op_type(cap_urn_string, OpClass)
runtime.run() # takes over stdin/stdout, runs the bifaci loop
with_manifest(manifest) builds a runtime against a CapManifest. register_op_type(urn, OpClass) binds a Cap URN string to an Op subclass; the runtime constructs a fresh instance per request. run() enters the bifaci protocol loop.
The string passed to register_op_type must be the canonical URN form — derive it from a CapUrn built with CapUrnBuilder and call .to_string() once, never hand-write the URN string.
CapManifest
manifest = CapManifest(
name="sentiment-tagger",
version="0.1.0",
channel="nightly", # "nightly" or "release"
registry_url=None, # None for dev, "https://..." for published
description="...",
cap_groups=[default_group([identity, tag_sentiment])],
)
channel and registry_url must agree with the engine’s compile-time configuration and the on-disk cartridge.json install record; mismatches are rejected at discovery time. default_group(caps) packs caps into a single registration group; cartridges with adapter caps can declare their own groups.
Cap, CapArg, CapOutput
cap = Cap(
urn=cap_urn, # a CapUrn object
title="Tag sentiment",
command="tag-sentiment", # CLI command name
)
cap.cap_description = "Classify text as positive, neutral, or negative."
cap.args = [
CapArg(
media_urn="media:text;sentiment-input;textable",
required=True,
sources=[
StdinSource("media:text;sentiment-input;textable"),
PositionSource(0),
],
arg_description="UTF-8 text to classify.",
)
]
cap.output = CapOutput(
media_urn="media:sentiment-tag;textable",
output_description="One of 'positive', 'neutral', 'negative'.",
)
Sources tell the runtime where to find an argument’s value: StdinSource(media_urn) reads from stdin, PositionSource(n) reads CLI position n, and there are flag-based sources for CLI flags. A cap with multiple sources for one arg accepts whichever the caller provides.
CapUrnBuilder
urn = (
CapUrnBuilder()
.marker("tag-sentiment") # freeform op tag
.in_spec("media:text;sentiment-input;textable") # input media URN
.out_spec("media:sentiment-tag;textable") # output media URN
.build()
)
urn_str = urn.to_string() # canonical byte form for register_op_type
marker(tag) adds a freeform tag with no key=value form. Use tag(key, value) for key=value tags. in_spec and out_spec set the input and output media URNs. The builder canonicalizes everything (alphabetical tag order, normalized media URNs); the only reliable URN string is the one you get back from .to_string() after .build().
Op
class TagSentimentOp(Op):
async def perform(self, dry: DryContext, wet: WetContext) -> None:
req: Request = wet.get_required(WET_KEY_REQUEST)
text = _collect_text(req.take_frames())
tag = classify(text)
req.emitter().emit_cbor(tag)
def metadata(self) -> OpMetadata:
return OpMetadata.builder("TagSentimentOp") \
.description("Classify text as positive / neutral / negative") \
.build()
perform(dry, wet) is the async handler. The request lives in wet under WET_KEY_REQUEST; pull it out with wet.get_required(WET_KEY_REQUEST). req.take_frames() returns a queue of incoming CHUNK/END frames; req.emitter() gives you back-channel emitters (emit_cbor, emit_chunk, etc.). The runtime emits END for you when perform returns.
The runtime constructs a fresh Op per request. Don’t carry per-request state on the instance; use locals.
Standard caps
from capdag.standard.caps import CAP_IDENTITY # cap:
CAP_IDENTITY is mandatory in every manifest. CAP_DISCARD and CAP_ADAPTER_SELECTION are also available as standard caps with default implementations the runtime can register for you. See Capabilities → Standard Capabilities.
For the LLM, embeddings, and model-management URN helpers (build a Cap URN for llm-summarization, embeddings-generation, model-download, etc.), see the standard URN builders in capdag.standard.caps and the LLM payload types below.
Rust SDK (machfab-cartridge-sdk)
[dependencies]
machfab-cartridge-sdk = { git = "https://github.com/machfab/machfab-cartridge-sdk" }
capdag = { git = "https://github.com/machfab/capdag" }
The crate re-exports capdag::* and adds LLM-specific payload types. The cartridge runtime, manifest, and URN builders live in capdag itself.
LLM payload types
use machfab_cartridge_sdk::{
LlmGenerationRequest, RequestType,
ConstraintSpec, ToolDefinition,
LlmStreamMessage,
LlmVocabResponse, LlmModelInfo,
};
// Build a generation request
let req = LlmGenerationRequest {
prompt: "Summarize this paragraph.".into(),
model_spec: "huggingface:meta-llama/Llama-3.2-1B-Instruct".into(),
request_type: Some(RequestType::Generate),
max_tokens: Some(256),
temperature: Some(0.2),
..LlmGenerationRequest::with_defaults()
};
let json = req.to_json()?;
LlmGenerationRequest fields (all optional except prompt and model_spec):
request_type, system_prompt, request_id, max_tokens, temperature, top_k, top_p, min_p, seed, grammar, json_schema, constraint, chat_template, stop_sequences, max_context_length, batch_size, rope_freq_base, rope_freq_scale, repeat_penalty, hf_token.
RequestType is Generate |
GetVocab |
GetInfo. ConstraintSpec is JsonSchema |
Regex |
Grammar |
ToolCall. |
Streaming responses arrive as LlmStreamMessage values, one per line of media:llm-text-stream;ndjson:
use machfab_cartridge_sdk::LlmStreamMessage;
let msg = LlmStreamMessage::from_line(line)?;
match msg {
LlmStreamMessage::Token { token, .. } => print!("{}", token),
LlmStreamMessage::Status { .. } => { /* progress */ }
LlmStreamMessage::Complete { .. } => break,
LlmStreamMessage::ToolRequest { .. } => { /* tool call */ }
LlmStreamMessage::Error { message, .. } => return Err(message.into()),
}
LlmVocabResponse { vocab: Vec<String>, vocab_size: Option<usize> } and LlmModelInfo { model_spec, vocab_size, context_length, embedding_dim, file_size_bytes, head_count, layer_count, supports_chat, supports_tools } are the responses to CAP_LLM_VOCAB and CAP_LLM_MODEL_INFO respectively.
Standard cap URN constants
use machfab_cartridge_sdk::{
CAP_LLM_INFERENCE_GGUF, CAP_LLM_INFERENCE_MLX,
CAP_LLM_INFERENCE_CANDLE, CAP_LLM_INFERENCE_CONSTRAINED,
CAP_LLM_VOCAB, CAP_LLM_MODEL_INFO,
CAP_GENERATE_EMBEDDINGS, CAP_EMBEDDINGS_DIMENSIONS,
CAP_DESCRIBE_IMAGE,
};
Backend classification
use machfab_cartridge_sdk::{backend_for_model_spec, BACKEND_GGUF, BACKEND_MLX, BACKEND_CANDLE};
let backend = backend_for_model_spec("huggingface:mlx-community/Llama-3.2-1B-Instruct-4bit");
// → BACKEND_MLX
backend_for_model_spec routes a model spec string to the runtime that should serve it. Cartridges that wrap a single backend use this to advertise only the model specs they can handle.
Media URN constants
use machfab_cartridge_sdk::{
MEDIA_LLM_GENERATION_REQUEST, // "media:llm-generation-request;json;record"
MEDIA_LLM_TEXT_STREAM, // "media:llm-text-stream;ndjson"
MEDIA_LLM_VOCAB_RESPONSE, // "media:llm-vocab-response;json;record"
MEDIA_LLM_MODEL_INFO_RESPONSE, // "media:llm-model-info;json;record"
};
For the cartridge runtime itself (handler registration, manifest construction, peer invocation in Rust), see the capdag crate documentation and the CapDAG handler patterns document.
Go SDK (machfab-cartridge-sdk-go)
Installation:
go get github.com/machfab/machfab-cartridge-sdk-go
The Go SDK mirrors the Rust LLM payload surface one-to-one.
LLM payload types
import (
"encoding/json"
"github.com/machfab/machfab-cartridge-sdk-go/llm"
)
req := llm.NewGenerationRequestWithDefaults("Summarize this paragraph.",
"huggingface:meta-llama/Llama-3.2-1B-Instruct")
maxTokens := uint32(256)
req.MaxTokens = &maxTokens
body, _ := req.ToJSON()
llm.GenerationRequest carries the same fields as the Rust struct, with optional values as pointers. ConstraintSpec and ToolDefinition mirror the Rust shapes.
// Stream parsing
msg, err := llm.StreamMessageFromLine(line)
switch msg.Type {
case llm.StreamToken: print(msg.Token)
case llm.StreamComplete: return
case llm.StreamError: return errors.New(msg.Message)
}
Standard caps and backend constants
import "github.com/machfab/machfab-cartridge-sdk-go/llm"
llm.CapLLMInferenceGGUF
llm.CapLLMInferenceMLX
llm.CapLLMInferenceCandle
llm.CapLLMInferenceConstrained
llm.CapLLMVocab
llm.CapLLMModelInfo
llm.CapGenerateEmbeddings
llm.CapEmbeddingsDimensions
llm.CapDescribeImage
llm.BackendForModelSpec("huggingface:mlx-community/...")
// → llm.BackendMLX
Media URN constants
llm.MediaLLMGenerationRequest
llm.MediaLLMTextStream
llm.MediaLLMVocabResponse
llm.MediaLLMModelInfoResponse
For runtime construction (registering handlers, building a manifest, peer invocation) the canonical Go path is to consume the same capdag Go primitives the engine uses; check the machfab-cartridge-sdk-go repository for the latest runtime helpers.
Swift / Objective-C SDK (MACHFABCartridgeSDK)
Installation:
// Package.swift
dependencies: [
.package(url: "https://github.com/machfab/machfab-cartridge-sdk-objc", from: "1.0.0")
]
The Swift / Objective-C SDK is the smallest of the four. It currently exposes a single class — CSCartridgeCaps — which acts as a wrapper around an array of CSCap values built up at runtime:
@interface CSCartridgeCaps : NSObject <NSCopying, NSCoding>
@property (nonatomic, readonly, strong) NSArray<CSCap *> *caps;
+ (instancetype)new;
- (void)addCap:(CSCap *)cap;
- (NSArray<NSString *> *)capUrns;
@end
Cartridges authored against the Objective-C SDK assemble their cap declarations through the lower-level capdag bindings and aggregate them into a CSCartridgeCaps. There is currently no higher-level manifest builder, no MACHFABDocumentMetadata-style payload type, and no Swift-native standard-cap helper library — the surface area you’ll touch from Swift is the protocol primitives plus this aggregator. If you’re writing a content cartridge in Swift today, the realistic path is to consume the bridging types from machfab-mac/MachineFabricSDK directly; for a brand-new cartridge from outside the macOS app, Python or Rust is a smoother starting point.
URN and Media-Type Reference
A few patterns recur:
- Cap URN format —
cap:in="media:X";<tags>;out="media:Y". Quotes around media URN values are part of the canonical form. Tags are sorted alphabetically. - Media URN format —
media:domain;tag1;tag2;.... Domain is the leading identifier (e.g.pdf,text,llm-generation-request). Tags refine;;textablemeans “human-readable text,”;recordmeans “JSON record,”;listmeans “an array.” - Argument media defs —
media:string,media:integer,media:number,media:boolean,media:object,media:binary. The full media URN scheme (refinement tags, coercion rules) is in CapDAG media URNs.
URNs are opaque. Always construct them with CapUrnBuilder (Python) or the equivalent capdag builder, then derive the canonical string with .to_string() (or equivalent). Hand-written URN strings will not match the canonical form for any non-trivial cap, and dispatch will silently fail with NoHandlerError.