Re-scoped by /reconcile-tracker 2026-08-08. The kernel half of this ticket is superseded — #1773 (compute), #1775 (GLSL), #1776 (batching) and #1777 (graphics + ray tracing) deliver it, and the bridge-installation approach this body proposed is superseded by ADR decision 5 (docs/decisions/python-kernel-api.md), which deletes the bridge traits entirely rather than installing them from the wheel's spawn path. Every [NEEDS DECISION] in this ticket's own comment now has an ADR answer.
The cpu-readback half survives, and it is narrower than this body implies.
What already works — no ticket owed
A Python author CPU-reads a pixel-buffer-backed surface today with zero glue. lock() / as_numpy() route through host_visible_pixel_plane() (sdk/streamlib-python-wheel/src/python_gpu_surface_pixel_exchange.rs:113-122) — these are host-visible linear allocations, so no GPU→CPU copy runs at all. The parity bar is already discharged here.
The actual gap
A numpy-only author cannot get a texture-backed surface — a kernel output — onto the CPU.
lock() raises "surface has no host mapping; it is a DEVICE_LOCAL allocation" (src/python_processor_context.rs:317-330); the host DLPack arm fails the same way (:416-427) and as_numpy requests device="cpu" (:465-468), landing on it. The only working path is a CUDA capsule over the OPAQUE_FD device-export staging, which needs a live CUDA runtime plus torch/cupy to copy device→host — numpy.from_dlpack cannot consume a CUDA capsule. #1779's as_device_tensor() does not close this: it delivers a device tensor for third-party GPU packages, not CPU bytes.
This gap only becomes reachable once #1773/#1777 let a Python author produce kernel outputs, and #1774 gives the engine the readback capability. So it is real, and it is new.
Why this is design-first, not an implementation ticket
The plan states the capability (§Graphics: "Python reaches every GPU capability Rust authoring reaches … and CPU readback") but no plan entry states the spelling. There is a genuine fork:
- Extend
GpuSurfaceHandle's host arm so a texture-backed surface refills a host-mappable staging and as_numpy() simply works on kernel outputs — the one-system shape, likely over the existing device-export staging.
- Or a dedicated readback vocabulary, resurrecting the legacy
acquire_read / acquire_write / try_acquire_* context managers (sdk/streamlib-python/python/streamlib/adapters/cpu_readback.py:487-520).
Either way the contended wire response (escalate_request.yaml:143-148, handler subprocess_escalate.rs:1112-1146) needs a non-exception Python shape — in the legacy vocabulary it yielded None. That is an architecture decision; tickets implement the plan, they never make it.
Depends on #1774 (the engine capability) and #1773 (something to read). Its resolution may ADD a bullet to docs/plan/changes/python-kernel-surface.md or ride a small follow-up change — an /align call, not an implementer's.
Original body (pre-reconciliation, preserved 2026-08-08)
Named disposition from #1714's escalate-parity research (owner, 2026-08-06). Three of three,
and the largest.
A Rust processor can build and dispatch compute, graphics and ray-tracing kernels and build
acceleration structures. A Python processor cannot spell any of it — not because the path is
missing, but because the wheel never grew the vocabulary.
What already exists
Every op is defined and served. packages/escalate/schemas/escalate_request.yaml carries
register_compute_kernel, run_compute_kernel, register_graphics_kernel, run_graphics_draw,
register_acceleration_structure_blas, register_acceleration_structure_tlas,
register_ray_tracing_kernel, run_ray_tracing_kernel, run_cpu_readback_copy and
try_run_cpu_readback_copy; handle_escalate_op implements all of them; and the wheel's spawn
host already attaches the bridge that routes a helper's escalate frames into that handler
(python_helper_process_spawn_host.rs:484-488). The old subprocess SDK drove them through
adapter classes — sdk/streamlib-python/python/streamlib/adapters/vulkan.py is the template,
and examples/polyglot-vulkan-{compute,graphics,ray-tracing} are the live consumers.
What is missing
- Wheel-side spelling — the Python surface a processor calls.
- Bridge installation. The four kernel/readback domains are trait-dispatched and their
implementations are installed by application setup glue today
(core/context/compute_kernel_bridge.rs:21-27; e.g.
examples/polyglot-vulkan-compute/src/main.rs:298). Without an installed bridge the host
answers a typed err. Under the importable-library model there is no app main.rs to do it,
so the wheel's spawn path has to.
Shape constraint
The subprocess-Vulkan carve-out stands (.claude/rules/polyglot.md): import-side only —
no allocation, no modifier choice, no kernel construction child-side. Everything privileged
escalates. ARCHITECTURE.md §Graphics already decides the surface: "the engine's kernel
primitives are exposable to Python as configured blocks: shader/compute source and binding
config passed from Python, compiled and executed by the engine on its device — no user-side
Vulkan, ever. Shape only; the Python-facing API design is its own session."
That last clause means this needs an API design pass before implementation.
Re-scoped by
/reconcile-tracker2026-08-08. The kernel half of this ticket is superseded — #1773 (compute), #1775 (GLSL), #1776 (batching) and #1777 (graphics + ray tracing) deliver it, and the bridge-installation approach this body proposed is superseded by ADR decision 5 (docs/decisions/python-kernel-api.md), which deletes the bridge traits entirely rather than installing them from the wheel's spawn path. Every [NEEDS DECISION] in this ticket's own comment now has an ADR answer.The cpu-readback half survives, and it is narrower than this body implies.
What already works — no ticket owed
A Python author CPU-reads a pixel-buffer-backed surface today with zero glue.
lock()/as_numpy()route throughhost_visible_pixel_plane()(sdk/streamlib-python-wheel/src/python_gpu_surface_pixel_exchange.rs:113-122) — these are host-visible linear allocations, so no GPU→CPU copy runs at all. The parity bar is already discharged here.The actual gap
A numpy-only author cannot get a texture-backed surface — a kernel output — onto the CPU.
lock()raises "surface has no host mapping; it is a DEVICE_LOCAL allocation" (src/python_processor_context.rs:317-330); the host DLPack arm fails the same way (:416-427) andas_numpyrequestsdevice="cpu"(:465-468), landing on it. The only working path is a CUDA capsule over the OPAQUE_FD device-export staging, which needs a live CUDA runtime plus torch/cupy to copy device→host —numpy.from_dlpackcannot consume a CUDA capsule. #1779'sas_device_tensor()does not close this: it delivers a device tensor for third-party GPU packages, not CPU bytes.This gap only becomes reachable once #1773/#1777 let a Python author produce kernel outputs, and #1774 gives the engine the readback capability. So it is real, and it is new.
Why this is design-first, not an implementation ticket
The plan states the capability (§Graphics: "Python reaches every GPU capability Rust authoring reaches … and CPU readback") but no plan entry states the spelling. There is a genuine fork:
GpuSurfaceHandle's host arm so a texture-backed surface refills a host-mappable staging andas_numpy()simply works on kernel outputs — the one-system shape, likely over the existing device-export staging.acquire_read/acquire_write/try_acquire_*context managers (sdk/streamlib-python/python/streamlib/adapters/cpu_readback.py:487-520).Either way the
contendedwire response (escalate_request.yaml:143-148, handlersubprocess_escalate.rs:1112-1146) needs a non-exception Python shape — in the legacy vocabulary it yieldedNone. That is an architecture decision; tickets implement the plan, they never make it.Depends on #1774 (the engine capability) and #1773 (something to read). Its resolution may ADD a bullet to
docs/plan/changes/python-kernel-surface.mdor ride a small follow-up change — an/aligncall, not an implementer's.Original body (pre-reconciliation, preserved 2026-08-08)
Named disposition from #1714's escalate-parity research (owner, 2026-08-06). Three of three,
and the largest.
A Rust processor can build and dispatch compute, graphics and ray-tracing kernels and build
acceleration structures. A Python processor cannot spell any of it — not because the path is
missing, but because the wheel never grew the vocabulary.
What already exists
Every op is defined and served.
packages/escalate/schemas/escalate_request.yamlcarriesregister_compute_kernel,run_compute_kernel,register_graphics_kernel,run_graphics_draw,register_acceleration_structure_blas,register_acceleration_structure_tlas,register_ray_tracing_kernel,run_ray_tracing_kernel,run_cpu_readback_copyandtry_run_cpu_readback_copy;handle_escalate_opimplements all of them; and the wheel's spawnhost already attaches the bridge that routes a helper's escalate frames into that handler
(
python_helper_process_spawn_host.rs:484-488). The old subprocess SDK drove them throughadapter classes —
sdk/streamlib-python/python/streamlib/adapters/vulkan.pyis the template,and
examples/polyglot-vulkan-{compute,graphics,ray-tracing}are the live consumers.What is missing
implementations are installed by application setup glue today
(
core/context/compute_kernel_bridge.rs:21-27; e.g.examples/polyglot-vulkan-compute/src/main.rs:298). Without an installed bridge the hostanswers a typed
err. Under the importable-library model there is no appmain.rsto do it,so the wheel's spawn path has to.
Shape constraint
The subprocess-Vulkan carve-out stands (
.claude/rules/polyglot.md): import-side only —no allocation, no modifier choice, no kernel construction child-side. Everything privileged
escalates.
ARCHITECTURE.md§Graphics already decides the surface: "the engine's kernelprimitives are exposable to Python as configured blocks: shader/compute source and binding
config passed from Python, compiled and executed by the engine on its device — no user-side
Vulkan, ever. Shape only; the Python-facing API design is its own session."
That last clause means this needs an API design pass before implementation.