Why this shape
NumPy is both a Python import root and a native extension. Sifr treats those as separate trust decisions. Matrix multiplication also tends to involve temporary arrays, dtype choices, and result shaping. That adaptation belongs in a package-local Python bridge undersrc/python_bridges/.
Five rules drive the NumPy path:
- Authorize Python and native roots separately.
[trust].pythonallows execution;[trust].python-nativeallows loading the native extension in-process. - Keep the Sifr boundary typed. Expose
multiply_matrices(...) -> Result[...]from a bridge instead of leakingndarraydetails into every caller. - Opaque Python values are non-send by default. Do not assume a NumPy object can move across threads without an explicit contract.
- Public Python calls are blocking. Call NumPy from
@blocking_iocode, or offload from async Sifr. - Zero-copy is opt-in and fail-closed. Buffer and DLPack declarations never silently copy. Use them only when you need a view, not for ordinary multiply-and-return workflows.
Package setup
Install NumPy in the root uv project, then declare trust:sifr.toml
Bridge the multiply workflow
Put adaptation code in the owning package’ssrc/python_bridges/:
src/python_bridges/linalg.py
Declare and call
bridge.linalg.multiply resolves only against this package’s inventoried bridge modules. Generated binaries embed that bridge under an isolated runtime namespace; they do not read the .py file from disk at runtime.
When to use buffers instead
Use@python.buffer when Sifr code must read or write array memory without copying the whole payload into nested lists:
Next steps
- Buffer, Arrow, and DLPack contracts: Embedded Python Interop
- Simpler typed package call: schwifty
- Callback-heavy brokers: kafka-python
