ONE BINARY. ONE INTERFACE.
Identity, governance, deployment, jobs, logs, and MCP server lifecycle live behind one CLI. Agents call the same commands a human types. A profile switch sends the next command to a different environment. The audit trail does not care which caller it was.
Eight Domains, One Mental Model
Identity lives in one tool. Governance lives in another. Deployment is a script. Analytics is a dashboard. The MCP servers have a console of their own. Every operator action crosses tool boundaries, and nothing shares an audit trail. The cost is not the tools. The cost is the seams between them.
The systemprompt CLI collapses those seams into one binary with one grammar: systemprompt <domain> <command> [args]. Eight domains live behind it. core owns skills, content, files, contexts, plugins, and hooks. infra runs services, databases, jobs, and logs with request tracing. admin handles users, RBAC, agent orchestration, and runtime configuration. cloud covers OAuth login, profile switching, tenant provisioning, deployment, and secrets. analytics exposes conversations, agents, tools, requests, sessions, content, traffic, costs, and an overview. web manages content types, templates, assets, and sitemaps. plugins lists extensions, discovers MCP capabilities, and calls MCP tools directly. build compiles the Rust workspace and the MCP extension binaries.
Every level uses the same clap derive pattern, so --help works at every depth. The Cli struct in args.rs defines eight subcommand variants and nothing else; the eight domain modules under commands/ own their own subtrees. That is the whole top-level surface.
- One Grammar at Every Depth —
systemprompt <domain> <command> [args], all the way down. The Cli struct in args.rs has eight subcommand variants. There is no second pattern to learn. - Help Generated From the Type System — Every command, argument, and option is a clap derive.
--helpat any level renders from the same definitions the parser uses. The CLI describes itself to humans and to agents reading--json. - Typed Output for Pipelines —
CommandResultwraps every command in a typed envelope with artifact type and rendering hints. CI scripts and agents parse the same shape every command emits.
Agents Call the Same Interface as Humans
Most agent integrations end up as a parallel API. The agent gets a thin wrapper with fewer permissions, a separate audit log, and a different failure mode. Two months in, the wrapper has drifted from the real CLI, and the audit trail has two formats no compliance officer wants to reconcile.
Two MCP servers in this codebase expose the same operations a human would type. The systemprompt MCP server proxies CLI commands. The marketplace MCP server registers 24 tools across skills, agents, plugins, MCP servers, secrets, and analytics, each with typed input and output schemas. Both servers gate every tool call through enforce_rbac_from_registry in their handle_tool_call implementations. The middleware, the role model, and the audit record are the same for an agent caller and a human caller.
Tool discovery is runtime, not documentation. The marketplace server's list_tools() returns the registry built in tools/registry.rs; an agent reads it and learns what is callable on this deployment. Add a tool, rebuild, and the agent picks it up on the next list_tools call.
- One RBAC Path for Both Callers — Both MCP servers route every tool call through
enforce_rbac_from_registrybefore dispatch. Agent and human requests share one permission model and one audit record. - 24 Tools, Typed Schemas — Skills, agents, plugins, MCP servers, secrets, and analytics. Each tool ships JSON Schema for input and output. Agents bind to the schema, not to a hand-written guide.
- Discovery Lives in the Server —
list_tools()returns whatever the registry holds at startup. Add a tool totools/registry.rs, rebuild, and connected agents see it on their next discovery call.
- systemprompt server RBAC gate <code>enforce_rbac_from_registry</code> called inside <code>handle_tool_call</code>
- marketplace server RBAC gate Same middleware on every marketplace tool call
- marketplace tools registry <code>list_tools()</code> reads the registry built at startup
- marketplace tool dispatch Routes a tool name to its typed handler
- systemprompt MCP manifest Server registration
- marketplace MCP manifest Server registration
Same Binary, Every Environment
Run a command against the wrong environment once and the lesson sticks. Most CLIs make that easy: a stale shell variable, a forgotten kubectl context, an SSH tunnel left open. The blast radius lives in operator memory, not in the tool.
The systemprompt CLI resolves a target through one explicit cascade. resolve_session in cli/src/session/resolution/mod.rs checks config.profile_override (the --profile flag) first, then SYSTEMPROMPT_PROFILE in the environment, then the active session in SessionStore, and only then the default ProfileBootstrap. There is one function and four cases in order. The order is in source.
A profile is a YAML file under .systemprompt/profiles/<name>/profile.yaml holding the database URL, server endpoints, secrets, and runtime settings for a target. Switching profiles changes the next command's destination. The audit record format does not change with the destination, so a query against local PostgreSQL and a query against production end up in the same shape on the way out.
- One Function, Four Cases, In Order —
resolve_sessionconsults--profile, thenSYSTEMPROMPT_PROFILE, then the active session inSessionStore, thenProfileBootstrap. The order is fixed in source, not in shell convention. - Profiles Are Files, Not Flags — Every target lives in
.systemprompt/profiles/<name>/profile.yamlwith its own database URL, endpoints, and secrets. Reviewing what production points at is reading one file. - OAuth, Not SSH —
cloud authhandles login and token refresh against the cloud control plane. No SSH tunnel, no bastion, no copy-paste of credentials into shell history.
- resolve_session cascade <code>--profile</code> > <code>SYSTEMPROMPT_PROFILE</code> > active session > bootstrap default
- ProfileOpts <code>--profile</code> declared as a global clap argument
- cloud profile commands Create, edit, show, delete profiles
- cloud auth commands OAuth login, logout, whoami
- cloud tenant commands Tenant provisioning
- cloud deploy commands Deployment with secret sync
- session module <code>SessionStore</code> and resolution
Interchangeable Components
An extension is a Rust type that implements the Extension trait in shared/extension/src/traits.rs. The trait declares many methods (schemas, router, jobs, page data providers, page prerenderers, component renderers, site auth, roles, migrations, required assets, and more) and almost all of them ship with a default. An extension overrides only the methods it actually contributes. WebExtension overrides routes, jobs, providers, and assets. EmailExtension overrides far less. Both register the same way and are discovered the same way at startup.
Three tiers cover the integration patterns. Library extensions compile into the binary. WebExtension, MarketplaceExtension, and EmailExtension each call register_extension!(), which submits them to the inventory crate's linker-time collection. CLI extensions ship as separate binaries discovered through a manifest.yaml; the CLI invokes them as subprocesses. MCP extensions run as HTTP servers that implement ServerHandler.
Registration requires no central list. src/lib.rs defines __force_extension_link(), which uses core::hint::black_box to keep the linker from stripping the inventory submissions. Add an extension to the workspace, call the macro in its crate, rebuild. Remove it and rebuild. There is no registry file to edit. build.rs parses every YAML under services/ at compile time, so a malformed config never reaches a running binary.
- One Trait, Many Defaults —
Extensiondeclares the full surface and ships defaults for almost every method. An extension overrides only the methods that pull weight. - Three Tiers, One Discovery Path — Library (compiled in via
register_extension!), CLI (subprocess viamanifest.yaml), MCP (HTTP viaServerHandler). The host loads each tier without manual wiring. - Linker-Time Registration —
inventorycollects extensions through the linker.__force_extension_linkblocks stripping. Adding an extension is adding a crate, not editing a registry.
- Extension trait Trait declaration with defaults
- WebExtension registration <code>register_extension!(WebExtension)</code>
- MarketplaceExtension registration <code>register_extension!(MarketplaceExtension)</code>
- EmailExtension registration <code>register_extension!(EmailExtension)</code>
- Job trait Trait implemented by every background job
- Extension link guard <code>__force_extension_link</code> keeps inventory submissions
- Extension discovery <code>inventory::collect!</code> at startup
Configuration as Code, Deployed Instantly
The operator problem with most stacks is not configuration. It is the gap between configuration and effect. A YAML file lives in one repo, a deployment lives in another, a job runner is somewhere else, and the audit log of who flipped what lives in a fourth place. Reconciling them is the work.
In this codebase, declarative state lives under services/. Agents in services/agents/, MCP servers in services/mcp/, skills in services/skills/, web navigation and homepage and features in services/web/config/. build.rs parses every file at compile time, so a malformed YAML never reaches a running binary. systemprompt core skills sync, systemprompt core content publish, and systemprompt admin agents sync apply changes against the active profile, idempotently.
Background work is part of the same binary, not a separate runner. Each extension contributes a list of jobs from its fn jobs() implementation. The web extension returns fifteen jobs covering content ingestion, CSS bundling, asset copying, prerendering, sitemap, llms.txt, robots.txt, redirects, the publish pipeline, analytics aggregation, daily traffic and activity reports, retention cleanup, the Slack gateway, and admin traffic reports. The marketplace extension returns nine more covering admin CSS bundling, gamification, secret migration, Paddle plan seeding, Anthropic plugin imports, usage retention, daily summaries, achievement emails, and session analysis backfill. Each job implements the Job trait with a name and a cron schedule and runs on the same audit trail as a CLI command.
- YAML In, Live Changes Out — Edit a file under
services/, run the matching sync command.build.rsvalidates every YAML at compile time, so a broken file never ships. - Jobs Live in the Binary —
WebExtension::jobsreturns 15 jobs.MarketplaceExtension::jobsreturns 9. Each implementsJobwith a cron schedule and runs in-process. No sidecar runner. - Logs and Tracing in the Same CLI —
infra logscovers stream, list, search, summary, export, and full audit reconstruction.systemprompt infra logs audit <id> --fullrebuilds an AI request from one terminal.
- services/ tree Declarative configuration root
- WebExtension::jobs 15 jobs returned from one function
- MarketplaceExtension::jobs 9 jobs returned from one function
- Job trait Trait every job implements
- infra logs commands Stream, search, audit, export
- infra jobs commands Job inspection and dispatch
- Audit log model Structured record format
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.
One binary. One CLI. Eight domains.
Clone the template. Build the binary. Run identity, governance, jobs, deployment, and MCP lifecycle from one terminal.