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:
-
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.
-
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
Problem
Plugins are statically linked into the
authbridge-proxy/authbridge-envoybinaries and selected at build time (default-on with-tags exclude_plugin_*, or opt-in like-tags include_plugin_contextguru). This creates two coupled problems:Bloat vs. selection. A heavy plugin's transitive deps are linked in whether used or not.
context-gurupulls tiktoken-go (BPE vocab), tree-sitter grammars, starlark, and bifrost — measured impact on the strippedauthbridge-proxy(darwin/arm64,-s -w -trimpath,CGO_ENABLED=0):+include_plugin_contextguruSo it's kept opt-in — which means it can't ship in the default/released binary.
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)
lean+full). Caps the explosion at 2; doesn't solve bloat or give extensibility. Stopgap only.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..wasmon demand; host stays lean. Two properties hit our pain directly: on-demand loading, and.wasmis 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 towasip1.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-sidecarext_procand thecpexFFI framework.Open design questions
.wasmin a ConfigMap/volume), OCI/registry pull, or a watched directory? Must fit "no new per-platform release assets."Non-goals (for now)
Assisted-By: Claude (Anthropic AI) noreply@anthropic.com