FULLY EXTENSIBLE ARCHITECTURE. YOUR CODE COMPILES INTO YOUR BINARY.
Implement one Extension trait to contribute routes, migrations, jobs, and providers. Your crate compiles into one binary that inherits RBAC middleware, audit_events, and rate limits. Link-time registration, no runtime reflection.
Cargo Dependency, Not SaaS
A governance vendor shipping SaaS owns your upgrade clock. The moment a platform engineer customises it, the customisation is a fork to maintain or a support ticket that blocks a release. systemprompt.io inverts that. It is a Rust crate you pull into your workspace. Add systemprompt = "x.x" to Cargo.toml, implement one trait, run cargo build --release, and the binary you ship contains the governance core and your proprietary plugins in one process. Your code never leaves your build.
The trait the core team uses is the trait you use. The in-tree domain plugins (AI, MCP, Content, Analytics, Agent, Users, OAuth, Files) are ordinary implementations of it, not a privileged host. The AI plugin contributes model providers and schemas. The OAuth plugin contributes HTTP routes and RBAC roles. The MCP plugin contributes tool providers and background jobs. A plugin you write sits alongside them. The build-vs-buy answer for a CTO is "keep the extension model you would have built, without maintaining the host".
Plugins are not a runtime module system with version-skew risk. Registration wires your type into the binary at link time through the inventory crate. If the crate compiles against the workspace, the plugin is present. No dynamic loading, no dlopen, no manifest negotiated over a socket, no "did my plugin start" question at deploy.
- One Cargo dependency — Pin the version in Cargo.toml. The build is deterministic and offline. A security patch is a Cargo version bump and a rebuild, not a coordinated upgrade across a host and a plugin registry.
- Your plugins sit beside the core plugins — AI, MCP, Content, Analytics, Agent, Users, OAuth, and Files are ordinary implementations of the same trait. A plugin you write is a peer, not a second-class integration.
- Link-time registration, no dlopen — Plugins register at link time. If the crate compiles, the plugin is present. No runtime reflection, no dependency on a plugin server to load modules in production.
- shared/extension/src/traits.rs The Extension trait a plugin implements. Declares routes, schemas, jobs, providers, roles, migrations.
- shared/extension/src/registry/discovery.rs Boot-time collector that walks every registered plugin, sorts by priority, returns the ordered list.
- shared/extension/src/registry/mod.rs Registration struct the macro emits. Uses the inventory crate for link-time collection.
- domain/ai/src/extension.rs AI domain plugin. Same trait a customer plugin implements, no privileged host.
- domain/mcp/src/extension.rs MCP domain plugin. Contributes tool providers and background jobs through the standard trait.
- domain/content/src/extension.rs Content domain plugin. Uses template and page-data surfaces of the trait.
- domain/agent/src/extension.rs Agent domain plugin. Demonstrates schema, route, and job registration in one place.
- domain/files/src/extension.rs Files domain plugin. Narrow implementation contributing schemas and a router.
One Extension Trait
A platform engineer evaluating a plugin model wants one answer. What is the minimum code to ship a new endpoint that inherits the governance library's policy? In systemprompt.io it is one trait. Return a router for HTTP routes. Return schema and migration lists for new database tables. Return job objects for background work. Return AI provider or MCP tool-provider lists for new capabilities. Return role definitions for new RBAC tiers. The governance pipeline (the request-path gate described in Governance Pipeline) applies to every route and every provider your plugin adds, because the gate sits ahead of the dispatcher.
The trait lists these surfaces deliberately so the full contract fits one screen, not a half-dozen "extension point" interfaces. Every method defaults to returning nothing, so a narrow plugin contributing a single route is three lines. A wide plugin contributing schemas, routes, jobs, providers, roles, and admin pages uses the same interface. The only mandatory method is the metadata call that names the plugin and declares dependencies.
Every surface you add inherits cross-cutting behaviour without wiring. A route mounts behind the same JWT and audience middleware that guards core routes. A table runs through the same migration ledger with checksum verification, so schema drift fails at startup. A job runs on the platform scheduler and writes to the same audit log as core jobs. You do not opt into governance. You cannot opt out.
- Routes, tables, and jobs without glue — A route mounts behind the same RBAC middleware that guards core routes. A table runs through the same migration ledger, so schema drift fails at startup. A job runs on the platform scheduler and writes to the same audit log.
- Providers inherit the governance pipeline — A model provider contributed by your plugin is called through the same dispatcher as core providers. Scope check, secret scan, blocklist, and rate limit run before your provider sees a call. You write the integration, not the policy layer.
- Templates, pages, and feeds if needed — Template providers, component renderers, page data providers, prerenderers, sitemap and RSS providers are trait methods. A backend-only plugin returns empty from each. A page-heavy plugin uses them without a second framework.
- shared/extension/src/traits.rs The Extension trait. Every surface a plugin can contribute is a method on this trait.
- shared/extension/src/metadata.rs Metadata, schema definitions, and the role shape a plugin returns for RBAC contributions.
- shared/extension/src/migration.rs Migration struct with version, name, SQL, checksum. Verified at startup against drift.
- shared/extension/src/router.rs Router wrapper a plugin returns. Mounts under the same auth middleware as core routes.
- shared/extension/src/context.rs Construction-time context. Exposes database, config, event bus through traits.
- domain/oauth/src/extension.rs OAuth plugin. Contributes router, RBAC roles, and schemas through the same trait.
- domain/users/src/extension.rs Users plugin. Narrow contribution focused on schemas and routes.
- domain/analytics/src/extension.rs Analytics plugin. Contributes a route and a job, emits events consumed elsewhere.
Typed Narrow Traits
Implementing the full Extension trait for a plugin that only adds one API carries breadth the plugin does not use. For narrow plugins, the crate ships typed variants so the type system enforces what the plugin is for. An API-only trait with a base path and an auth flag. A config-namespace trait with JSON-schema validation. A job-only trait. An AI provider and MCP tool provider trait. A schema-only trait. A plugin that should only add routes cannot return a schema or a job, because the trait has no method for it.
Dependencies between plugins are checked at compile time, not boot. The builder that composes a plugin set uses a type-level list. A plugin whose declared dependencies are not already in the list fails to compile. The error names the missing dependency. "My plugin crashed at startup because a sibling was not registered" becomes "my build fails and the compiler tells me which sibling".
At runtime, every plugin is treated uniformly. The registry stores plugins behind a trait object with downcasting methods, so code that needs "all plugins that contribute a schema" asks once and gets a typed view without reflection. A lookup by string ID serves admin surfaces. A lookup by Rust type serves code composing features at build time. One store, one validation pass, one load order.
- Narrow traits for narrow plugins — API-only, config-only, job-only, provider-only, and schema-only variants live in a typed module. A routes-only plugin cannot declare a job because the trait has no method for it, so the class of bug where a plugin claims X but does Y is a compile error.
- Dependencies checked by the compiler — The builder uses a type-level list, so a plugin whose dependencies are not registered fails the build. The error names the missing dependency. A platform engineer acts on a compile diagnostic rather than a deploy-time panic.
- One registry, two lookup modes — Lookup by string ID serves admin surfaces and diagnostics. Lookup by Rust type serves code composing features at build time. The same plugin serves both without re-registration.
- shared/extension/src/typed/api.rs API-only plugin trait. Forces a base path and auth flag, exposes a router.
- shared/extension/src/typed/config.rs Config-namespace plugin trait. Declares a config prefix, JSON schema, and validator.
- shared/extension/src/typed/job.rs Job-only plugin trait. A plugin that only schedules background work implements this.
- shared/extension/src/typed/provider.rs Provider-only plugin trait. Returns AI model providers and MCP tool providers.
- shared/extension/src/typed/schema.rs Schema-only plugin trait, supporting embedded SQL and file-based loading.
- shared/extension/src/typed_registry.rs Registry serving both string-ID lookup and type-safe retrieval. One store, two access paths.
- shared/extension/src/builder.rs Builder that composes plugins at the type level with compile-time dependency checking.
- shared/extension/src/any.rs Type-erased wrapper and downcasting methods used to serve capability queries.
- shared/extension/src/types.rs Dependency-list type machinery driving compile-time dependency checking in the builder.
Boot-Time Validation
A plugin that only surfaces a problem under traffic is an incident waiting to happen. systemprompt.io runs the plugin pipeline at startup, before the HTTP listener opens. Every plugin linked into the binary registers through the author's macro. At boot, the registry collects registrations, instantiates each plugin, inserts by ID, and sorts by declared priority (lower priorities load first). If the binary compiled with the plugin, the plugin is present. No directory scan, no manifest volume, no sidecar handshake.
After collection, a validation pipeline runs four checks before the application accepts a request. Dependency resolution walks each plugin's declared dependencies and fails if an ID is missing from the registry. A depth-first cycle check returns the exact cycle path on failure, so a staff engineer reads the ordered loop rather than "circular dependency somewhere". API path validation checks plugin routers against reserved core prefixes (/api/v1/oauth, /api/v1/users, /api/v1/agents, /api/v1/mcp, /api/v1/stream, /api/v1/content, /api/v1/files, /api/v1/analytics, /api/v1/scheduler, /api/v1/core, /api/v1/admin) and rejects collisions. Duplicate IDs fail registration immediately, so a typo never lands silently on top of an existing plugin.
For the two less common shapes (standalone MCP server binaries and CLI extension binaries), the loader scans an extensions directory for manifests, parses them into discovered-plugin records, and runs the same validation pipeline. Dependencies must resolve. The graph must be acyclic. Paths must not collide. IDs must be unique. A CISO asking "what code runs inside this binary" gets one answer from one validation path, whether the plugin compiled in or was discovered from disk.
- Link-time registration, no directory scan — Plugins linked into the binary register at link time through the macro. The registry collects at boot, sorts by priority, hands off an ordered list. No filesystem scan, no manifest parse, no dynamic load.
- Four-stage validation before traffic — Dependency resolution, cycle detection returning the exact path on failure, reserved-path check, and duplicate-ID rejection. All four run before the listener opens, so a routing collision or missing dependency is a boot failure, not a 2am page.
- Same validation for compiled and discovered plugins — For standalone MCP and CLI binaries, the manifest-scanning loader produces plugin records that run the same validation. The audit story is one story.
- shared/extension/src/registry/discovery.rs Boot-time discovery function. Collects, instantiates, and orders every registered plugin.
- shared/extension/src/registry/validation.rs Four-stage validation pipeline: dependencies, cycles, reserved paths, duplicate IDs.
- shared/extension/src/registry/mod.rs Registry struct and registration record emitted by the macro. One store, one load path.
- infra/loader/src/extension_loader.rs Loader for standalone MCP and CLI binaries. Scans manifests and runs the same validation.
- infra/loader/src/extension_registry.rs Binary-path resolution for standalone plugin binaries. One audit answer, one debug path.
- shared/extension/src/capabilities.rs Capability traits a plugin receives: config access, database access, event bus.
- shared/extension/src/error.rs Loader error variants: duplicate plugin, circular dependency with path, missing dependency.
Your Binary, Open Core
A CTO buying SaaS governance buys the vendor's upgrade clock. Buying systemprompt.io is buying a Cargo dependency. Pin the version, update when the team is ready, and the binary compiled today runs identically in five years as long as the crate sits in your lockfile. The migration struct tracks version, name, SQL, and a checksum, so schema changes are deterministic and auditable. An auditor answers "what DDL ran in production" from the migration ledger, not a vendor's changelog.
Every deployment is shaped by its use case. A healthcare company adds HIPAA audit tables and retention jobs through the schema and job surfaces of the trait. A financial services firm adds compliance reports and RSS feeds through the page-data and feed surfaces. A dev tools company adds a marketplace UI through the router, component renderer, and template data extender. The core provides governance, auth, and observability. Your plugins make it your product, on the same trait and through the same registry.
The model covers three deployment shapes. Library plugins compile into the main binary and inherit everything including the governance pipeline. MCP server plugins run as standalone binaries discovered through manifests, useful when a tool integration has different resource or lifecycle needs. CLI plugins add subcommands for operators who live in a terminal. All three pass the same validation and share the same capability traits. "Will my plugin work" reduces to "does it compile".
- You own the upgrade clock — Pin the version in Cargo.toml and update when the team decides. The migration ledger tracks every schema change with a checksum an auditor verifies, so 'what DDL ran in production' is answered from your artifacts.
- Same trait, different products — Healthcare adds HIPAA tables and retention jobs. Finance adds compliance pages and RSS feeds. Dev tools add marketplaces through templates and routes. Each sits on the same trait and inherits the same governance.
- Three deployment shapes, one validation pipeline — Library plugins compile in and inherit governance. MCP servers run standalone via manifests. CLI plugins add operator subcommands. All three pass the same dependency and path validation.
- Cargo.toml Workspace manifest. A downstream project pins this crate like any Cargo dependency.
- shared/extension/src/migration.rs Migration struct with checksum verification. Answers 'what DDL ran?' from the ledger.
- shared/extension/src/capabilities.rs Capability traits the plugin receives: configuration, database, event bus access.
- infra/loader/src/extension_loader.rs Manifest-based loader for standalone MCP and CLI plugin binaries. Shared validation.
- shared/extension/src/traits.rs The trait every plugin shape implements. Registration macro wires it in at link time.
Founder-led. Self-service first.
No sales team. No demo theatre. The template is free to evaluate — if it solves your problem, we talk.
Who we are
One founder, one binary, full IP ownership. Every line of Rust, every governance rule, every MCP integration — written in-house. Two years of building AI governance infrastructure from first principles. No venture capital dictating roadmap. No advisory board approving features.
How to engage
Evaluate
Clone the template from GitHub. Run it locally with Docker or compile from source. Full governance pipeline.
Talk
Once you have seen the governance pipeline running, book a meeting to discuss your specific requirements — technical implementation, enterprise licensing, or custom integrations.
Deploy
The binary and extension code run on your infrastructure. Perpetual licence, source-available under BSL-1.1, with support and update agreements tailored to your compliance requirements.
Build your product on open core.
Clone the template, implement your plugins, compile a binary that is yours. One Extension trait, typed narrow variants, boot-time validation, no runtime reflection.