Skip to main content

YOUR CODE. YOUR BINARY.

systemprompt.io is a Cargo dependency, not a subscription. The Extension trait exposes 51 methods (32 capability methods plus 19 has_*() compile-time predicates). Eight domain extensions in systemprompt-core and five typed extension traits show the pattern. Your code compiles into your binary. We never see it.

A Library You Extend, Not a Service You Subscribe To

The reader's problem: every governance vendor ships a black-box service that updates underneath you, and the moment you customise it you are off the supported path. systemprompt.io inverts that. Add systemprompt = "x.x" to your Cargo.toml, implement the Extension trait (51 methods, all but metadata() have sensible defaults), and run cargo build --release. The binary you ship contains the systemprompt.io core and your proprietary extensions in the same process. The register_extension! macro in traits.rs wires your type into the inventory crate at link time. Your source never leaves your build.

Eight domain extensions in systemprompt-core show the pattern in production: AI, MCP, Content, Analytics, Agent, Users, OAuth, and Files. Each implements the same Extension trait with different method combinations. The AI extension registers llm_providers() and schemas(). The MCP extension adds tool_providers() and jobs(). The OAuth extension provides router() and roles(). There is no second class of integration. The library uses the same trait you use.

This is not a plugin system with runtime compatibility risk. Extensions are compiled into your binary via inventory::submit! and discovered by inventory::iter::<ExtensionRegistration>. If it compiles, it works. No version conflicts at deploy time. No runtime reflection. No dependency on our servers to load modules. ExtensionRegistry::discover() iterates registrations, inserts them by ID, and sorts by priority(), all at startup.

  • Cargo Dependency Model — Add systemprompt.io to your Cargo.toml like any other crate. Pin your version. The register_extension! macro handles compile-time registration via inventory::submit!.
  • Eight Domain Extensions — AI, MCP, Content, Analytics, Agent, Users, OAuth, and Files. Each lives in crates/domain//src/extension.rs and implements the same Extension trait with different method combinations.
  • Compile-Time Discovery — The inventory crate registers extensions at link time via ExtensionRegistration structs. ExtensionRegistry::discover() iterates them, sorts by priority(), and validates. Zero runtime reflection.

51 Methods Across Every Domain

An engineer evaluating a governance library wants to know exactly where the seams are before they commit. The Extension trait in traits.rs publishes them: 51 methods, 32 capability methods that contribute behaviour and 19 has_*() predicates that let the registry skip unused capabilities without instantiating them. Add HTTP API routes via router(), which returns an ExtensionRouter built on Axum. Define database tables via schemas() returning Vec<SchemaDefinition>, with versioned migrations() that run on startup. Register background jobs via jobs() returning Vec<Arc<dyn Job>>.

Add LLM providers via llm_providers() returning Vec<Arc<dyn LlmProvider>> for OpenAI, Anthropic, Google Gemini, Groq, or your own internal model. Build MCP tool servers via tool_providers() returning Vec<Arc<dyn ToolProvider>>. Render custom web pages through nine separate web rendering extension points: page_data_providers(), page_prerenderers(), component_renderers(), template_providers(), template_data_extenders(), frontmatter_processors(), content_data_providers(), rss_feed_providers(), and sitemap_providers(). Define configuration namespaces via config_prefix() with JSON Schema validation through config_schema() and validate_config(). Create RBAC roles via roles() returning Vec<ExtensionRole>.

Every method has a sensible default (vec![] or None). Implement only what your product needs. The metadata() method is the only required implementation. A minimal extension that adds one API route is three lines of trait implementation. A full extension that adds schemas, routes, jobs, auth, and admin UI uses the same trait interface. The priority() method (default 100) controls loading order. The dependencies() method declares required extensions, validated at startup.

  • Routes, Tables, and Jobs — router() returns Axum Router, schemas() returns Vec, jobs() returns Vec>. migrations() adds versioned DDL with checksum tracking. migration_weight() controls execution order.
  • LLM and MCP Providers — llm_providers() returns Vec>, tool_providers() returns Vec>. The governance pipeline applies automatically to every request through every registered provider.
  • Nine Web Rendering Points — page_data_providers(), page_prerenderers(), component_renderers(), template_providers(), template_data_extenders(), frontmatter_processors(), content_data_providers(), rss_feed_providers(), sitemap_providers().

Typed Extension System

The wide Extension trait covers every surface, but for an extension that only adds an API or only adds jobs, that breadth is overhead the type system cannot help with. systemprompt.io provides five typed extension traits for compile-time specialisation: ApiExtensionTyped for HTTP routes with base_path() and requires_auth(), ConfigExtensionTyped for YAML namespaces with config_prefix(), config_schema(), and validate_config(), JobExtensionTyped for background tasks returning Vec<Arc<dyn Job>>, ProviderExtensionTyped for AI and tool providers returning Vec<Arc<dyn LlmProvider>> and Vec<Arc<dyn ToolProvider>>, and SchemaExtensionTyped for database tables with SchemaDefinitionTyped supporting both embedded SQL and file-based schemas.

The ExtensionBuilder in builder.rs enforces dependency ordering at the type level using heterogeneous lists (HList). Calling .extension(ext) checks that E::Deps: Subset<R>. The extension's declared dependencies must already be registered in the builder's type-level list. Dependency violations are compile errors, not runtime panics. The builder provides three registration methods: extension() for basic types, schema_extension() for types implementing SchemaExtensionTyped, and api_extension() for types implementing ApiExtensionTypedDyn.

Each typed extension is wrapped in a type-erased AnyExtension trait object via ExtensionWrapper, SchemaExtensionWrapper, or ApiExtensionWrapper. The AnyExtension trait provides downcasting methods (as_schema(), as_api(), as_config(), as_job(), as_provider()) so the registry can query capabilities without knowing concrete types. The TypedExtensionRegistry stores extensions by ID and by TypeId, enabling both string-based lookup via get() and type-safe retrieval via get_typed::<E>().

  • Five Typed Traits — ApiExtensionTyped (base_path, requires_auth), ConfigExtensionTyped (config_prefix, validate_config, config_schema), JobExtensionTyped (jobs), ProviderExtensionTyped (llm_providers, tool_providers), SchemaExtensionTyped (schemas, migration_weight).
  • Type-Safe Dependencies via HList — ExtensionBuilder uses heterogeneous lists to enforce dependency ordering at compile time. E::Deps: Subset ensures all dependencies are registered before the dependent extension. Compile error, not runtime panic.
  • Type-Erased Registry with Downcasting — TypedExtensionRegistry stores Box with lookup by string ID via get() and by TypeId via get_typed::(). Five as_*() downcasting methods for capability queries.

Extension Discovery and Validation

An extension that only loads at first request is too late: a missing dependency or a routing collision needs to surface at boot, not under traffic. The inventory crate handles compile-time registration. The register_extension! macro calls inventory::submit! with an ExtensionRegistration struct containing a factory function. At startup, ExtensionRegistry::discover() iterates all registrations via inventory::iter::<ExtensionRegistration>, instantiates each extension through its factory, and inserts it into a HashMap<String, Arc<dyn Extension>>. Extensions are sorted by priority() (lower values load first). The discovery process runs once at startup with no file system traversal for library extensions.

The validation pipeline in registry/validation.rs runs four checks before the application starts. Dependency validation via validate_dependencies() verifies every ID returned by dependencies() exists in the registry. Cycle detection in detect_cycles() uses a DFS graph traversal with three-colour marking (constants WHITE, GRAY, BLACK) to find circular dependency chains, returning the exact cycle path in the error. API path validation via validate_api_paths() checks that extension routers use /api/ prefixes and do not collide with the 11 reserved paths in the RESERVED_PATHS constant (/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). Duplicate detection rejects extensions with conflicting IDs at registration time.

For standalone binaries (MCP servers, CLI tools), ExtensionLoader::discover() scans the extensions/ directory for manifest.yaml files, parses them into DiscoveredExtension structs, and builds a binary map. The discover_and_merge() method combines inventory-discovered extensions with injected extensions, then runs the full validation pipeline. Cloud deployments use path-based binary lookup instead of manifest scanning. Every extension (library, MCP, or CLI) passes through the same validation before it can execute.

  • Compile-Time Registration — register_extension! macro calls inventory::submit! with an ExtensionRegistration factory. ExtensionRegistry::discover() iterates via inventory::iter, inserts by ID, and sorts by priority(). Zero file system scanning.
  • Four-Stage Validation Pipeline — Dependency existence check, DFS cycle detection with three-colour marking (WHITE, GRAY, BLACK), API path validation against the 11 entries in RESERVED_PATHS, and duplicate ID rejection. All checks run before the application starts.
  • Zero-Overhead Loading — Library extensions discovered via inventory at link time. No file system traversal, no dynamic loading, no dlopen. MCP and CLI extensions discovered via manifest.yaml scanning. Cloud deployments use direct path lookup.

Your Product, Built on Open Core

The CTO problem this section answers: a SaaS governance vendor controls your upgrade clock and decides when your code breaks. Pin your systemprompt.io version in Cargo.toml and update when your team decides. The binary you compile today runs identically in five years. The Migration struct in migration.rs tracks version number, name, and SQL with checksum() verification, so schema changes are deterministic and auditable.

Every company's deployment is shaped by its use case. A healthcare company adds HIPAA-specific audit extensions implementing schemas() and jobs(). A financial services firm adds custom compliance reporting via page_data_providers() and rss_feed_providers(). A development tools company builds a marketplace dashboard using router(), component_renderers(), and template_data_extenders(). The core library provides governance, auth, and observability. Your extensions make it your product through the same 51-method trait.

Extension types cover every integration surface: library extensions compile into your main binary via register_extension!, MCP servers deploy as standalone binaries discovered through manifest.yaml, and CLI extensions add custom commands. The CapabilityContext struct in capabilities.rs exposes the HasConfig, HasDatabase, and HasEventBus traits, so your extensions get type-safe access to configuration, database, and event publishing without coupling to concrete implementations.

  • Version Control Is Yours — Pin your dependency version. Migration struct tracks version, name, SQL, and checksum(). No forced updates. No breaking changes pushed to your production.
  • Every Deployment Is Unique — Healthcare adds HIPAA extensions via schemas() and jobs(). Finance adds compliance via page_data_providers(). Dev tools add marketplaces via router() and component_renderers(). Same trait, different products.
  • Three Extension Types — Library extensions via register_extension! compile into your binary. MCP servers discovered via manifest.yaml. CLI extensions via clap. CapabilityContext provides HasConfig, HasDatabase, HasEventBus.

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

Build your product on open core.

Clone the template, implement your extensions, and compile a binary that is entirely yours. 51 trait methods. Five typed traits. No runtime reflection.