Binary distribution of SwiftPython for macOS applications that need in-process Python, isolated worker processes, long-lived full-duplex sessions, or Virtualization.framework-backed Linux tenants.
Current release: 0.6.0-duplex.8.4
The preceding public commercial artifact is 0.6.0-duplex.8.3. This release
hardens runtime ownership, conversions, callback lifetimes and worker queues,
while preserving the public API and wire protocols. Embedded imports preserve
signed app resources. The two examples are Particle Showcase and Iris.
Product page: Best Byte AI
- Particle Showcase: NumPy moves 1,048,576 particles. Swift and Metal render their shared positions. Includes live controls and a 1080p video exporter with measured timings and buffer verification.
- Iris: select a sample, inspect a held-out mistake, and change features to see a fitted scikit-learn model predict again. One Python worker retains the datasets and model while Swift owns the interface.
Both builders bundle hash-locked numerical packages alongside the matched runtime. The resulting development apps run offline without a host Python installation. See Examples for build instructions.
Use this package under one of:
- AGPL-3.0;
- the free Small Organization Commercial Grant in LICENSE; or
- a written commercial license.
Read LICENSE before distributing an application.
| Path or release asset | Purpose |
|---|---|
SwiftPythonRuntime.xcframework |
Library-evolved consumer API surface |
SwiftPythonEngine.xcframework |
Private code-only dependency; no product or textual Swift module |
Python.xcframework |
Private self-contained CPython runtime linked by every public product |
SwiftPythonAudioInterop.xcframework |
Optional AVAudio capture/playback adapter |
SwiftPythonMetalInterop.xcframework |
Optional Metal leases, shared-arena mapping, and copy ledger |
SwiftPythonWorker |
Matched universal local ProcessPool sidecar |
SwiftPythonAudioProbe |
Fixed-path, kill/reap-bounded macOS hardware-readiness helper |
VMWorker/ |
Matched five-file generated protocol/helper/supervisor/worker set |
Entitlements/ |
Parent, worker, audio-probe, inherited-sandbox, and virtualization templates |
Examples/ |
Standalone packages compiled against this public distribution |
docs/api-guide/ |
Public API and deployment guide |
manifest.json release asset |
Version, source revision, protocols, byte sizes, SHA-256 records, and an explicit VM-image attestation or null |
The five XCFrameworks and prebuilt SwiftPythonWorker sidecar are universal
macOS binaries with matched arm64 and x86_64 slices. Keep every binary, helper,
image, and snapshot on one release version. Worker wire v6 is not compatible
with the published v0.5 worker wire v5.
The SwiftPythonCommercial-0.6.0-duplex.8.4.zip asset contains this complete
checkout. The five XCFramework zips are individual binary-target assets. Its
manifest.json is a separate asset and attests all five zips, the worker, the
audio probe, all five VM helpers, and the complete distribution. Its vmImage
field contains the same-version VM-image attestation when that gate runs, or
explicit null for a scoped non-VM release.
A launcher-bearing release uses manifest schema 3 and adds exactly one
audioHardwareProbeExecutable record for SwiftPythonAudioProbe, plus
protocols.audioHardwareProbe: 1. Its complete distribution must contain the
same helper bytes and both probe entitlement templates. The raw helper is not a
SwiftPM binary target; Python is the fifth target and remains private.
- macOS 15 or newer
- Swift 6 / Xcode command-line tools
- Apple Silicon or Intel for the matched universal ProcessPool sidecar
- Virtualization.framework entitlement and Apple Silicon for VM/Sandbox use
The package carries its own Python 3.13 runtime. Consumers do not install
Python, Homebrew, python.org packages, uv, or custom linker paths, and do not
set PYTHON_HOME/PYTHONHOME. App launch performs no runtime download or
extraction.
Pin the prerelease exactly:
// Package.swift
dependencies: [
.package(
url: "https://github.com/mikhutchinson/swiftpython-commercial.git",
exact: "0.6.0-duplex.8.4"
)
]Choose only the products the application uses:
.product(
name: "SwiftPythonRuntime",
package: "swiftpython-commercial"
)
.product(
name: "SwiftPythonAudioInterop",
package: "swiftpython-commercial"
)
.product(
name: "SwiftPythonMetalInterop",
package: "swiftpython-commercial"
)SwiftPythonAudioInterop and SwiftPythonMetalInterop are independent
optional products. A core-only consumer does not link AVFAudio or Metal through
those adapters. SwiftPythonEngine is intentionally not a product and must not
be imported. SwiftPM links it and the private Python binary target as
dependencies of each public product.
import SwiftPythonRuntime
let version: String = try await Python.run {
try String(pythonObject: Python.sys.version)
}
try await withProcessPool(workers: 2) { pool in
let value: Double = try await pool.invokeResult(
module: "math",
function: "sqrt",
args: [.python(144.0)]
)
print(version, value)
}For an app bundle, copy SwiftPythonWorker into
Contents/MacOS/SwiftPythonWorker, then sign nested code before the outer
app. Worker discovery also accepts SWIFTPYTHON_WORKER_PATH or the explicit
workerExecutablePath: initializer argument.
Python.xcframework is already a private dependency of every public product.
Xcode links and embeds Python.framework through the normal package graph; do
not add Python linker flags or select a host interpreter.
YourApp.app/
Contents/
Frameworks/
SwiftPythonEngine.framework/ # required private runtime code, embed once
Python.framework/ # embedded from the private package target
MacOS/
YourApp
SwiftPythonAudioProbe
SwiftPythonWorker
Info.plist
Embed the supplied signed SwiftPythonEngine.framework exactly once. The
application executable must resolve its @rpath install name through
@executable_path/../Frameworks; do not copy Engine into multiple nested
locations. The worker, Engine, and host load the same package-owned Python.
Copying a sidecar is not a signing or notarization step; use the
distribution-specific rules below.
The public audio launcher resolves only
Bundle.main.bundleURL/Contents/MacOS/SwiftPythonAudioProbe. A URL-based
SwiftPM dependency does not auto-embed that raw executable. Copy the helper
from the exact matching complete distribution or commercial checkout, keep its
direct app-relative Python load command unchanged, re-sign it as nested code,
and then sign the containing app. Do not add a PATH, build-directory, worker,
caller-supplied-path, XPC, autobuild, or in-process fallback.
PythonDuplexSession pins one worker ID and generation while input, output,
semantic control, and interruption progress independently. It never migrates or
replays after worker replacement.
let session = try await pool.openDuplexSession(
handler: .eval(
code: """
from swift_duplex import InputFrame
def run(session):
session.ready()
for event in session.iter_input():
if isinstance(event, InputFrame):
session.output.send(
bytes(event.buffer),
processed_input_through=event.sequence,
)
session.output.finish()
""",
entrypoint: "run"
)
)
do {
try await session.input.send(
DuplexInputFrame(payload: Data("hello".utf8))
)
try await session.input.finish()
for try await frame in session.output {
consume(frame.buffer)
try await session.acknowledgeOutput(
consumedThrough: DuplexPosition(
sequence: frame.position.sequence,
byteOffset: frame.buffer.count
)
)
}
_ = try await session.result()
await session.close()
} catch {
await session.cancel(reason: .user)
await session.close()
throw error
}Frame-only sessions require .frames. Bounded logical-message fragmentation
requires .messages. The local fixed-pool shared-ingress route requires
.managedBuffers and a ManagedBufferConfiguration; isolated providers may use
inline logical-message chunks instead. Backing paths, offsets, slot topology,
generational counters, and quarantine state are private.
Capability requirements are rechecked atomically against the exact generation
reserved for open.
See Chapter 10 and the runnable full-duplex API guide.
0.6.0-duplex.5 includes the isolated Sandbox surface. Its certified gate uses
the same source revision for:
- all five XCFrameworks and the local sidecar;
_swiftpython_wire.py,_swiftpython_duplex.py,swiftpython_protocol.py,swiftpython_supervisor.py, andswiftpython_worker.py;- the attested Ubuntu base image and warm snapshot;
- cold and warm vsock duplex/message workloads.
The release manifest identifies the verified image and records the exact guest helper hashes. Image/snapshot verification must reject a hash, version, protocol, supervisor, configuration, or restore-secret mismatch; a warm gate must not silently fall back to cold boot.
Deploy the five-file VMWorker/ directory together and point custom layouts
at it with:
SWIFTPYTHON_VM_WORKER_DIR=/absolute/path/to/VMWorkerSee Chapter 9 for image, snapshot, tenant, shell/PTY, and VM ProcessPool usage.
import SwiftPythonAudioInterop
import SwiftPythonMetalInteropDuplexAudioFormat validates PCM shape before capture/playback.
DuplexAudioCapture and DuplexAudioPlayback keep AVAudio callbacks
realtime-only; async pumps own session interaction.
On macOS, DuplexAudioHardwareProbeLauncher provides a bounded point-in-time
hardware readiness receipt using the fixed nested helper. The parent app owns a
nonempty NSMicrophoneUsageDescription, the first microphone prompt, and
com.apple.security.device.audio-input (required by App Sandbox and by
hardened-runtime prompting). The helper requires already
granted permission and never prompts. A ready receipt proves only one fresh,
muted, helper-owned shared engine at that instant; it does not certify the
caller's engine or a future route. See Chapter 11 for the public call shape and
the zero-required capture clock-health counters.
ManagedBuffer.makeMetalBufferLease maps a page-aligned managed buffer to an
MTLBuffer without copying those pages. Registered command buffers and access
completion hold the opaque allocation until safe reuse.
DuplexCopyLedger records actual zero-copy, bounded CPU-copy, or kernel-copy
routes. It does not turn capture-source, IOSurface, socket, or VM routes into a
blanket zero-copy claim.
See Chapter 11.
Select nested-code entitlements from the parent app's sandbox state, not from the certificate class:
| Parent | Worker template | Audio-probe template |
|---|---|---|
| Non-sandbox, any signing identity | SwiftPythonWorker.entitlements |
SwiftPythonAudioProbe.entitlements |
| Sandboxed, Apple Development or Developer ID | SwiftPythonWorker-sandbox.entitlements |
SwiftPythonAudioProbe-sandbox.entitlements |
Each inherited nested-code template contains exactly
com.apple.security.app-sandbox and com.apple.security.inherit.
Capabilities belong on the parent. For a sandboxed distribution, bundle
Python at Contents/Frameworks/Python.framework, verify the worker and probe
retain the published app-relative load contract, and same-team-sign all native
code. Sign the probe with the exact
identifier <parent signing identifier>.SwiftPythonAudioProbe at the fixed
path before signing the outer app.
codesign --force --sign "$SIGN_ID" --options runtime \
--entitlements Entitlements/SwiftPythonWorker.entitlements \
YourApp.app/Contents/MacOS/SwiftPythonWorker
codesign --force --sign "$SIGN_ID" --options runtime \
--identifier "${PARENT_SIGNING_IDENTIFIER}.SwiftPythonAudioProbe" \
--entitlements Entitlements/SwiftPythonAudioProbe.entitlements \
YourApp.app/Contents/MacOS/SwiftPythonAudioProbe
codesign --force --sign "$SIGN_ID" --options runtime \
--entitlements Entitlements/ConsumerApp.entitlements \
YourApp.appThe release gate notarizes the exact complete distribution and three app-shaped
consumer fixtures (non-sandbox, inherited sandbox, and virtualization), staples
them, applies quarantine provenance, requires
source=Notarized Developer ID, reruns duplex, and rechecks each signed bundle
for mutation. The virtualization fixture must complete 20 consecutive positive warm restores
and the full public VM tenant workload without accepting a cold fallback.
Before publication, scripts/consumer_path_smoke.sh derives a temporary
path-based binary manifest from this checkout. That keeps every local and
notarized fixture on the candidate XCFramework bytes instead of resolving the
preceding hosted tag or requiring the new asset URLs to exist early.
SWIFTPYTHON_AUDIO_PROBE_GATE controls only the device-dependent launcher
portion of that fixture:
offverifies embedding/signing structure but makes no launcher claim;containmentruns the public launcher and accepts a strict receipt or only a valid typed device/helper-timeout result after confirmed containment; either result is explicitly not release-gate evidence; andreadyrequires a strict.readyreceipt, including zero capture host-time fallbacks and device/host clock resets. The separately reportedplaybackInvalidSampleTimeCountis diagnostic, not a zero-required gate.
Notary mode refuses off and containment. It requires
SWIFTPYTHON_AUDIO_PROBE_GATE=ready, runs the Developer ID non-sandbox and
sandbox launcher fixtures before notarization, then reruns both exact stapled,
quarantined candidates through LaunchServices (open), without a worker-path
override. Each final run must expose a live exact bundle identifier and emit
one fresh nonce-bound success receipt before exit; retained stdout, stderr, and
receipt files are part of the gate evidence. All three app-shaped fixtures
embed and re-sign the helper; the virtualization fixture does not make a
redundant device claim.
Notary ready mode requires an explicit stable 4-32 character lowercase
alphanumeric SWIFTPYTHON_SMOKE_ID_SUFFIX; release automation uses
releasegate. That produces distinct, version-independent Developer ID,
sandbox, and virtualization test identities. The Developer ID and sandbox apps
each need one initial microphone grant for the release-machine user. Later
notary runs use a require-granted policy and never open a TCC prompt: missing,
denied, or reset permission fails the gate before notarization. The VM fixture
does not access the microphone.
Bootstrap the two grants once, interactively, without a notary profile. The
explicit bootstrap mode runs its preliminary direct smoke with the audio gate
off, then launches the stable Developer ID and sandbox apps through
LaunchServices with request-if-needed. Its receipts live under the disposable
smoke work directory and are not release evidence. Use the exact stable suffix
that later release runs use:
env -u SWIFTPYTHON_NOTARY_PROFILE \
SWIFTPYTHON_RELEASE_MANIFEST=/absolute/path/to/manifest.json \
SWIFTPYTHON_AUDIO_PROBE_GATE=ready \
SWIFTPYTHON_AUDIO_TCC_BOOTSTRAP=1 \
SWIFTPYTHON_SMOKE_ID_SUFFIX=releasegate \
SWIFTPYTHON_VM_RELEASE_GATE=0 \
scripts/consumer_path_smoke.shExecuting Contents/MacOS/ConsumerSmoke directly does not provision the app's
LaunchServices/TCC identity and is not a substitute for this bootstrap. The
bootstrap reuses macOS's normal signed-app update identity; it does not bypass
TCC or make the retained permission release evidence. Every candidate still
needs its own fresh helper receipt, notarization, staple, quarantine,
Gatekeeper, LaunchServices, and sealed-bundle checks. Do not derive the suffix
from a release version, path, machine, timestamp, or artifact hash, and do not
reuse a production application's bundle identifier.
swift build
swift test
Examples/ParticleShowcase/run.sh
Examples/IrisDemo/scripts/build_app.sh --open
SWIFTPYTHON_RELEASE_MANIFEST=/absolute/path/to/manifest.json \
scripts/audit_release_surface.sh "$CANDIDATE_VERSION"
SWIFTPYTHON_RELEASE_MANIFEST=/absolute/path/to/manifest.json \
SWIFTPYTHON_AUDIO_PROBE_GATE=containment \
scripts/consumer_path_smoke.shFor the final notarized VM release gate, supply the same-commit image and snapshot explicitly:
SWIFTPYTHON_NOTARY_PROFILE="<notarytool-keychain-profile>" \
SWIFTPYTHON_NOTARY_OUTPUT_DIR="$PWD/notarization" \
SWIFTPYTHON_RELEASE_MANIFEST=/absolute/path/to/manifest.json \
SWIFTPYTHON_AUDIO_PROBE_GATE=ready \
SWIFTPYTHON_AUDIO_TCC_BOOTSTRAP=0 \
SWIFTPYTHON_SMOKE_ID_SUFFIX=releasegate \
SWIFTPYTHON_VM_RELEASE_GATE=1 \
SWIFTPYTHON_VM_BASE_IMAGE=/absolute/path/to/base-ubuntu.img \
SWIFTPYTHON_VM_SNAPSHOT=/absolute/path/to/snapshot \
SWIFTPYTHON_VM_RESTORE_SECRET=/absolute/path/to/snapshot.restore-secret \
SWIFTPYTHON_VM_CLONE_DIR=/absolute/path/to/consumer-clones \
SWIFTPYTHON_VM_ITERATIONS=20 \
scripts/consumer_path_smoke.shEvery standalone example defaults to this checkout. For a published-tag proof,
set SWIFTPYTHON_COMMERCIAL_PACKAGE_URL and
SWIFTPYTHON_COMMERCIAL_PACKAGE_VERSION; prerelease identifiers are
preserved rather than collapsed to 0.6.0.
| Symptom | Check |
|---|---|
Library not loaded: Python.framework |
Verify the exact commercial package is pinned and Xcode embedded its private Python.framework; do not install or discover a host Python |
workerNotFound |
Copy the matched sidecar or set its explicit path |
helperNotFound or helper identity failure |
Embed the exact SwiftPythonAudioProbe at the fixed app path and re-sign it with the parent team and derived identifier |
| microphone preflight failure | Put the purpose string and com.apple.security.device.audio-input on the parent, obtain permission there, then launch |
| protocol/helper/media skew | Compare the release tag and manifest.json; never mix helpers |
Bad CPU type for the worker |
Verify the worker and private Python framework came from the same tag; both shipped slices are arm64 and x86_64 |
duplex featureUnavailable |
Inspect live capabilities and put requirements on the open |
| arena requirement rejected in VM | Expected: shared arena ingress is local UDS only |
| VM image/snapshot rejected | Rebuild all five helpers, image, and snapshot from this release |
| SPM fingerprint mismatch | Do not reuse tags; clear stale local resolution state and resolve the new version |
- Added a private, pruned
Python.xcframeworkdependency to every public product. Applications install no Python, Homebrew, python.org package,uv, linker path,PYTHONHOME, download, or extraction step. - Bound the host, worker, and audio probe to the embedded framework and
initialized CPython through explicit
PyConfigstate without trusting or mutating processPYTHONHOME. - Shipped a universal worker with exact worker/Python architecture parity and added zero-configuration hostile-environment, native-stdlib, asyncio timeout, callback, relocation, and cold-start release gates.
- Included the 8.2 asyncio import-order repair so
current_task(),asyncio.timeout, andasyncio.wait_foruse one coherent native registry in packaged workers. Public Swift APIs, worker wire v6, and duplex media v1 are unchanged.
- Rebased
SwiftPythonWorkerandSwiftPythonAudioProbeonto the standard@rpath/Python.framework/Versions/3.13/Pythoncontract. Packaged helpers no longer encode a machine-wide Python installation path. - Added fail-fast worker startup validation for the loaded CPython core, framework metadata, standard library, and native standard-library extension origins. A mixed runtime is rejected before user Python executes.
- Added release gates that reject absolute Python framework dependencies and a neutral two-root async-callback relocation stress. Public Swift APIs, worker wire v6, and duplex media v1 are unchanged.
- Consumers must embed one coherent Python framework at
Contents/Frameworks/Python.frameworkand re-sign the final nested code. Fresh artifacts require no downstream rewrite and consumers must not rely on one; a defensive audit/repair may remain as defense in depth if it rejects leftovers. A conflicting processPYTHONHOMEcannot redirect the packaged runtime; SwiftPython supplies its embedded root directly throughPyConfig.
- Added the raw
SwiftPythonAudioProbedistribution contract, schema-3 manifest audit, entitlement templates, and signed app-shaped containment/ readiness fixtures. Published0.6.0-duplex.7remains unchanged. - Added
PythonProcessPool.shedIdleWorkersAndWait(force:), typedPythonWorkerError.drainTimedOut(inFlight:seconds:), and explicit callback, worker-lifecycle, and transport-cleanup uncertainty events. These are source-additive and do not change worker wire v6. - Made
DuplexFailure,DuplexFailureCode, andDuplexFailureOriginHashablefor structured failure grouping without parsing descriptions.
- Added a backend-neutral accelerator contract with opaque backend IDs and shared lane, warm-up, residency, admission, pressure, and resource-evidence policies. Backend packages can now build on the public Runtime interface without adding a branded llama.cpp policy to SwiftPython.
- Added negotiated
duplex.accelerator.backend.v1admission for native workers while preserving the existing MLX source contract and worker-wire-v6 JSON shape. VM guests remain explicit about the capabilities they actually carry. - Fixed a terminal/acknowledgement race so a direction-end published after a successful output acknowledgement cannot lose the acknowledged cursor.
- Fixed the duplex control reader so an empty 60-second receive interval is a
liveness poll rather than a terminal
runtimeUnavailablefailure. Healthy workers may now remain silent while a model or accelerator is busy; actual worker death, channel closure, protocol failure, and explicit deadlines stay terminal. - Fixed the external
DuplexSessionexample so it no longer reaches the Runtime package's internal negotiated-configuration state, and extended the release-surface audit to type-check command-line examples against the shipped public module.
- Added opaque, generation-bound application-control receipts with repeatable owned, rejected, pending, and delivery-uncertain resolution. A timeout or cancellation no longer destroys ownership knowledge or permits replay.
- Preserved late control acknowledgement across terminal cleanup and replaced a worker after uncertain partial control-frame transport failure.
- Aligned native duplex Python timeout and argument exception types with the VM worker, while preserving arbitrary accelerator callable failures.
- Hardened complete-distribution manifest ordering, candidate-binary consumer gates, and interactive QEMU image construction.
- Added equality-only worker lifetime tokens for generation-safe consumer state without exposing generation counters.
- Added provider-neutral exact sandbox policy, sanitized lifecycle and failure diagnostics, explicit activity-stream loss, and confirmed termination.
- Preserved the code-only private Engine boundary introduced in
.3.
- Split proprietary transport, managed-memory, sandbox, and tuning code into
the private code-only
SwiftPythonEngineframework. - Removed Engine Swift module metadata and textual interfaces from the distribution; the public Runtime interface contains only consumer APIs.
- Replaced low-level arenas, leases, ring buffers, VM builders/configuration,
and policy thresholds with managed handles, presets, and
SandboxProvider. - Added private-Engine signing, embedding, one-copy load, interface-denylist, and consumer behavior gates.
- Worker wire v6 and first-class
PythonDuplexSessionwith bounded credit, half-close, control, interruption, terminal watermarks, and no replay. - Feature-negotiated logical messages with bounded fragmentation and reassembly, plus local owned fixed-pool shared-arena ingress.
- Separate optional Audio and Metal XCFramework products with realtime adapter contracts, lease-safe GPU completion, poison/quarantine semantics, and a route-specific copy ledger.
- Same-version five-helper VM image/snapshot line with authenticated vsock duplex and cold/warm restore gates.
- Dual SwiftPM/xcodebuild module layouts, strengthened external consumer, sandbox inheritance, notarization, Gatekeeper, and hosted-byte verification.
Prior release history is recorded in the source repository changelog and older commercial tags.