Skip to content

App guests run in workers: per-app blast zone, preemptive kill #45

Description

@lannbot

Recording a direction settled in discussion (2026-08-20), for eventually — not scheduled.

Current state (post the visor extraction, #44)

The blast zone is isolated for pixels and faults, not for time or memory:

  • Pixels: app DOM lives in an opaque-origin sandboxed iframe (visor/frame/) — structurally unreadable, unpaintable.
  • Traps: a guest trap surfaces as a promise rejection on the runner chain, which survives by construction (visor/surface/runner.ts); flush-on-trap applies pre-trap ops. Tested differentially: the lab guest commits one surface violation per probe id and the harness requires identical trap vectors across backends (spikes/todomvc/host/harness.ts).
  • CPU: the guest executes on the visor's main thread (deltic's embedder instantiates in the calling realm; no workers in the runtime). An app that spins wedges the whole tab — including the kill button. Pause/resume are cooperative gates a spinning guest never yields to.
  • Memory: guest linear memory is tab memory.

So the #22 kill ceremony's promise ("input delivery stops and the app's surface is removed") holds, but "the app stops burning your CPU" does not — kill is honest against yielding guests and decorative against spinning ones.

Target

Guest execution moves into a Worker per app:

  • The surface (handle table + op queue, visor/surface/surface.ts) moves worker-side with the wasm instance — it is already architected for this split: handles allocated surface-side, serializable ops streamed out, the frame backend already consumes streamed ops.
  • The worker's MessagePort can be transferred directly to the app's iframe, so ops flow guest thread → frame document with the visor's thread out of the data path entirely.
  • kill becomes worker.terminate(): preemptive, works mid-spin — the ceremony becomes honest. Memory becomes terminate-able too (hard caps are a separate question).

Known costs: deltic instantiation + artifact fetch move into the worker; drain becomes a two-hop round trip; the demo's engine panes each grow a worker. NOTES already records the framework-side intent ("App logic runs in workers on the framework side (deltic, runtime-linked); UI ↔ shell ↔ component is a two-hop RPC path, acceptable for UI latencies") — this issue is the spike-side execution of it.

Refs: #16 (execution model), #22 (kill/input-suspension ceremony), #5 (frame sandboxing), #44 (the extraction that makes the surface split mechanical).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions