Skip to content

PATTERN Cited by 1 source

Per-request isolate per plugin

Pattern

When a plugin hook fires, spawn a fresh V8-style isolate (e.g. a Dynamic Worker) dedicated to that plugin, execute the plugin's hook handler, return the result, tear the isolate down. No plugin state persists between invocations except through explicitly-bound storage capabilities. Every plugin invocation is architecturally the plugin's first moment of life.

When to use

  • You're running many small, third-party plugins on the same host, each with its own trust posture.
  • You can afford millisecond-scale isolate start-up (V8 isolate, not container or VM).
  • You want strong isolation (one plugin's memory corruption, leak, or infinite loop cannot affect another plugin).
  • You want scale-to-zero plugin execution: idle plugins cost nothing.
  • You want agent-driven / LLM-written plugins that may be updated frequently and should never carry implicit state.

Preconditions

  • A V8-isolate-class runtime. Container-per-plugin is too expensive per-invocation; VM-per-plugin is way too expensive. Dynamic Workers / workerd are the explicit example Cloudflare positions.
  • A capability-based sandbox in that runtime. Fresh isolates with ambient authority would be no better than in-process plugin execution.
  • Explicit-binding storage for state that must persist across invocations (KV, Durable Objects, D1, R2). The isolate is ephemeral; its storage bindings are not.

How it differs from long-lived-isolate-per-plugin

Long-lived-per-plugin: each plugin gets its own long-running Worker (warm isolate retained across requests). Efficient; retains in-memory caches; typical Workers deployment pattern. But: state from one invocation persists to the next unless explicitly cleared; a plugin can accumulate latent bugs, memory leaks, or partial state over time.

Per-request-isolate-per-plugin: each hook invocation gets a fresh isolate. No cross-invocation state; no memory accumulation; no "plugin stuck in a bad state" failure mode. Cost: millisecond startup overhead per invocation. For plugin hooks that fire occasionally (content:afterSave, user:created, scheduled task), the overhead is small relative to the workload; for per-request plugins the trade is harder.

Relationship to Dynamic Workers

Dynamic Workers are Cloudflare's canonical per-request-isolate substrate — built for LLM-generated code in Project Think. The same property — "~100× faster and up to 100× more memory-efficient than a container. You can start a new one for every single request, run a snippet of code, and throw it away" — is what makes per-plugin-isolate-per-invocation viable as a plugin runtime.

EmDash is the first widely-visible deployment of Dynamic Workers as a plugin-hosting substrate (after Project Think's agent-tool use case). The pattern generalises: any host that needs to run many small, untrusted extensions on-demand can use the same substrate.

Consequence: lifetime-bounded security

A plugin cannot:

  • Leave a lingering socket open across invocations.
  • Cache sensitive data in process memory between calls.
  • Use a compromise in one invocation to affect the next.
  • Run a slow leak that eventually crashes the plugin runtime.

Everything a plugin might have done with persistent state has to go through an explicit storage binding — which is itself a declared capability in the manifest, visible at install time.

Consequence: scale-to-zero at the plugin tier

A plugin with no incoming hook invocations runs no isolate, holds no memory, costs nothing. A site with 100 installed plugins, 5 of which are actively firing, is only paying for those 5. The host's idle-cost model is set by active traffic, not installed plugin count — a meaningful economic improvement over plugin architectures that load everything into the host process at startup.

Worked example — EmDash

incoming request → EmDash core handles routing
  → content:afterSave hook fires
    → for each installed plugin with this hook:
        spawn Dynamic Worker isolate with capabilities
          from plugin's manifest
        invoke the plugin's hook function
        collect result (or error)
        tear isolate down
  → EmDash core assembles response

No plugin holds any state between hook invocations except through its declared storage capabilities. The host never has to worry about a plugin's in-memory garbage state.

Seen in

Last updated · 200 distilled / 1,178 read