rossoctl / rossoctl/cortex

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

Open
#721 1 comment 0 reactions 1 assignee View on GitHub

@araujof is already working on this.

Since Jul 30, 2026.

Dominant language
Go
Stars
13
Forks
40
Avg merge
12h 17m
Merged PRs (30d)
156

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.