DON'T WAIT ON A ROADMAP. COMPILE YOUR OWN.
Extending a governance vendor's product means filing a request and waiting for their next release. systemprompt.io is a library, not a black box: your own code compiles into the same binary and inherits every governance control without you wiring it.
Cargo Dependency, Not SaaS
A governance vendor shipping SaaS owns your upgrade clock. The moment a staff 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 extensions in one process. Your code never leaves your build.
The trait the core team uses is the trait you use. The in-tree domain extensions, from AI to Files, are ordinary implementations of it, not a privileged host. The AI extension contributes model providers and schemas. The OAuth extension contributes HTTP routes and RBAC roles. The MCP extension contributes tool providers and background jobs. An extension 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".
Extensions 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 extension is present. No dynamic loading, no dlopen, no manifest negotiated over a socket, no "did my extension 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 an extension registry.
- Your extensions sit beside the core extensions — AI, MCP, Content, Analytics, Agent, Users, OAuth, and Files are ordinary implementations of the same trait. An extension you write is a peer, not a second-class integration.
- Link-time registration, no dlopen — Extensions register at link time. If the crate compiles, the extension is present. No runtime reflection, no dependency on an extension server to load modules in production.
- shared/extension/src/traits.rs The Extension trait an extension implements. Declares routes, schemas, jobs, providers, roles, migrations.
- shared/extension/src/registry/discovery.rs Boot-time collector that walks every registered extension, 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 extension. Same trait a customer extension implements, no privileged host.
- domain/mcp/src/extension.rs MCP domain extension. Contributes tool providers and background jobs through the standard trait.
- domain/content/src/extension.rs Content domain extension. Uses template and page-data surfaces of the trait.
- domain/agent/src/extension.rs Agent domain extension. Demonstrates schema, route, and job registration in one place.
- domain/files/src/extension.rs Files domain extension. Narrow implementation contributing schemas and a router.
One Extension Trait
A staff engineer evaluating an extension 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 extension 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 extension contributing a single route is three lines. A wide extension contributing schemas, routes, jobs, providers, roles, and admin pages uses the same interface. The only mandatory method is the metadata call that names the extension 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 built-in 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 built-in scheduler and writes to the same audit log.
- Providers inherit the governance pipeline — A model provider contributed by your extension 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 extension returns empty from each. A page-heavy extension uses them without a second system.
- shared/extension/src/traits.rs The Extension trait. Every surface an extension can contribute is a method on this trait.
- shared/extension/src/metadata.rs Metadata, schema definitions, and the role shape an extension 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 an extension 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 extension. Contributes router, RBAC roles, and schemas through the same trait.
- domain/users/src/extension.rs Users extension. Narrow contribution focused on schemas and routes.
- domain/analytics/src/extension.rs Analytics extension. Contributes a route and a job, emits events consumed elsewhere.
Typed Narrow Traits
When an extension that should only add one endpoint can technically return a database schema or a background job, the class of bug where an extension claims one thing and quietly does another is a runtime surprise. For narrow extensions, the crate ships typed variants so the type system enforces what the extension is for: an API-only variant exposes a base path and an auth flag, a config-namespace variant carries JSON-schema validation, and there are job-only, provider-only, and schema-only variants alongside them. An extension that should only add routes cannot return a schema or a job, because the type it implements has no method for it.
Dependencies between extensions are checked at compile time, not boot. The builder that composes an extension set uses a type-level list. An extension whose declared dependencies are not already in the list fails to compile. The error names the missing dependency. "My extension crashed at startup because a sibling was not registered" becomes "my build fails and the compiler tells me which sibling".
At runtime, every extension is treated uniformly. The registry stores extensions behind a trait object with downcasting methods, so code that needs "all extensions 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 extensions — API-only, config-only, job-only, provider-only, and schema-only variants live in a typed module. A routes-only extension cannot declare a job because the trait has no method for it, so the class of bug where an extension claims X but does Y is a compile error.
- Dependencies checked by the compiler — The builder uses a type-level list, so an extension whose dependencies are not registered fails the build. The error names the missing dependency. A staff 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 extension serves both without re-registration.
- shared/extension/src/typed/api.rs API-only extension trait. Forces a base path and auth flag, exposes a router.
- shared/extension/src/typed/config.rs Config-namespace extension trait. Declares a config prefix, JSON schema, and validator.
- shared/extension/src/typed/job.rs Job-only extension trait. An extension that only schedules background work implements this.
- shared/extension/src/typed/provider.rs Provider-only extension trait. Returns AI model providers and MCP tool providers.
- shared/extension/src/typed/schema.rs Schema-only extension 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 extensions 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
An extension that only surfaces a problem under traffic is an incident waiting to happen. systemprompt.io runs the extension pipeline at startup, before the HTTP listener opens. Every extension linked into the binary registers through the author's macro. At boot, the registry collects registrations, instantiates each extension, inserts by ID, and sorts by declared priority (lower priorities load first). If the binary compiled with the extension, the extension 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 extension'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 extension routers against the eleven reserved core prefixes, /api/v1/oauth and /api/v1/mcp among them, and rejects collisions. Duplicate IDs fail registration immediately, so a typo never lands silently on top of an existing extension.
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-extension 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 extension compiled in or was discovered from disk.
- Link-time registration, no directory scan — Extensions 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 extensions — For standalone MCP and CLI binaries, the manifest-scanning loader produces extension 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 extension.
- 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 extension binaries. One audit answer, one debug path.
- shared/extension/src/capabilities.rs Capability traits an extension receives: config access, database access, event bus.
- shared/extension/src/error.rs Loader error variants: duplicate extension, 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 extensions make it your product, on the same trait and through the same registry.
The model covers three deployment shapes. Library extensions compile into the main binary and inherit everything including the governance pipeline. MCP server extensions run as standalone binaries discovered through manifests, useful when a tool integration has different resource or lifecycle needs. CLI extensions add subcommands for operators who live in a terminal. All three pass the same validation and share the same capability traits. "Will my extension 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 extensions compile in and inherit governance. MCP servers run standalone via manifests. CLI extensions 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 extension receives: configuration, database, event bus access.
- infra/loader/src/extension_loader.rs Manifest-based loader for standalone MCP and CLI extension binaries. Shared validation.
- shared/extension/src/traits.rs The trait every extension 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 extensions, compile a binary that is yours. One Extension trait, typed narrow variants, boot-time validation, no runtime reflection.