Skip to content

Plugin architecture: static linking forces bloat-vs-variant tradeoff — explore a runtime-extensible plugin model #721

Description

@huang195

Problem

Plugins are statically linked into the authbridge-proxy / authbridge-envoy binaries and selected at build time (default-on with -tags exclude_plugin_*, or opt-in like -tags include_plugin_contextguru). This creates two coupled problems:

  1. Bloat vs. selection. A heavy plugin's transitive deps are linked in whether used or not. context-guru pulls tiktoken-go (BPE vocab), tree-sitter grammars, starlark, and bifrost — measured impact on the stripped authbridge-proxy (darwin/arm64, -s -w -trimpath, CGO_ENABLED=0):

    build binary gzipped
    default 31 MiB 10.5 MB
    +include_plugin_contextguru 47 MiB 14.9 MB
    added +16.5 MB (+50%) +4.3 MB (+40%)

    So it's kept opt-in — which means it can't ship in the default/released binary.

  2. Variant/asset explosion. Every "which plugins" choice is a different binary we must build and publish. We already publish 8 release assets (abctl + authbridge-proxy × 4 platforms), plus image variants (authbridge, authbridge-lite, authbridge-cpex). Adding a context-guru build would be combinatorial. Build-time selection pushes the cost onto the release matrix and confuses users about "which binary do I need."

This surfaced while scoping a context-guru follow-up demo: there's no clean way to let a user try an opt-in plugin without either bloating the default binary or publishing yet another asset (explicitly undesirable).

Goal

Decouple "which plugins run" from "which binary we ship." Target: true runtime extensibility — add/swap plugins into a running (or restarted-but-not-rebuilt) host without recompiling and republishing the host, and without linking every plugin's deps into one binary.

Options considered (brainstorm — not yet decided)

  • A. Externalize heavy data, not code (per-plugin quick win). Load vocab/grammars from files/download at runtime so the plugin code compiles in cheaply. Cheapest, but plugin-specific and doesn't give runtime extensibility. Worth measuring context-guru's code-vs-data split regardless.
  • B. Two-tier binaries (lean + full). Caps the explosion at 2; doesn't solve bloat or give extensibility. Stopgap only.
  • C. Out-of-process plugins (gRPC / HashiCorp go-plugin). Lean host, plugins as separate executables over a local socket (Terraform/Vault model). Mature. But per-request IPC latency (bad for hot-path parsers, fine for slow plugins), and plugins are still per-platform binaries → the asset matrix returns.
  • D. WASM plugins (wazero, pure-Go host). Load .wasm on demand; host stays lean. Two properties hit our pain directly: on-demand loading, and .wasm is architecture-independent → one artifact per plugin, not four (kills the matrix explosion); plus sandboxing + language-agnostic. Costs: biggest lift (stable host↔guest ABI, body copying across the boundary), WASM perf overhead, and risk that some heavy deps (tree-sitter, bifrost) don't compile cleanly to wasip1.
  • E. Go native plugin (.so). Ruled out — host/plugin Go-version + dep lockstep, no cross-OS, no unload.

Leaning: a hybrid — keep the lean, hot-path core plugins (jwt/token-exchange, parsers) statically linked, and move only heavy/optional plugins (context-guru today; future ML/policy plugins) to a dynamic mechanism. WASM (D) is the most complete fix (also solves the asset-count problem); out-of-process (C) is the proven fallback. Note existing precedent for a stable plugin boundary: envoy-sidecar ext_proc and the cpex FFI framework.

Open design questions

  • Trust boundary — first-party plugins loaded dynamically, or safely running untrusted/third-party plugins? (Untrusted → sandboxing → WASM basically mandatory.)
  • Hot-path vs. coarse-grained — must dynamic plugins run per-request (body rewrite) or only at coarser hooks? Drives the overhead budget and the ABI surface.
  • Distribution / swap mechanism — mounted artifact (.wasm in a ConfigMap/volume), OCI/registry pull, or a watched directory? Must fit "no new per-platform release assets."
  • Plugin language — Go-only, or polyglot (WASM buys language-agnostic; go-plugin is Go-centric)?
  • "Runtime" scope — hot-reload into a live process, or is "restart the sidecar with a new plugin mounted, no rebuild" sufficient?

Non-goals (for now)

  • Not implementing anything yet — this is a design/discussion issue to pick a direction.
  • Constraint captured: no new per-release binary assets (we already publish 8).

Assisted-By: Claude (Anthropic AI) noreply@anthropic.com

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status
New/ToDo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions