Skip to main content

YOUR GOVERNANCE SERVER IS YOUR WEB SERVER.

The same binary that governs your AI agents serves your website, blog, and documentation. CMS, content publishing, and static site generation, with no additional services required.

Your Governance Server Is Your Web Server

The same Rust binary that governs AI tool calls also serves HTTP requests, renders templates, and delivers static assets. The ApiServer struct binds to a single port, applies security headers via inject_security_headers, traces every request through inject_trace_header, and normalises paths with remove_trailing_slash. No Nginx. No Apache. No separate web tier. One process, one port, one set of logs.

The TemplateRegistry manages Handlebars templates with a priority-based provider system. CoreTemplateProvider discovers templates from the filesystem, TemplateRegistryBuilder composes providers, loaders, extenders, component renderers, page data providers, and page prerenderers into a single registry. Templates render server-side with zero JavaScript required for initial page load. YAML configuration controls layout, branding, navigation, and feature pages.

This is not a bolted-on feature. The website you are reading right now (systemprompt.io) runs on systemprompt.io. Every page, every blog post, every documentation article is served by the same binary described on this site. The ApiServer::serve method fires StartupEvent::ServerBinding and server_listening lifecycle events, then hands off to Axum's native serve. Millisecond startup, zero overhead.

  • No Separate Web Tier — ApiServer binds a single port with inject_security_headers, inject_trace_header, and remove_trailing_slash middleware. No reverse proxy required.
  • Server-Side Rendering — TemplateRegistry with priority-based CoreTemplateProvider discovers and renders Handlebars templates. Zero JavaScript for initial page loads. Fast, accessible, SEO-friendly.
  • Self-Hosted Proof — systemprompt.io itself runs on this binary. The product serves its own marketing site, documentation, and blog, via the same ApiServer::serve call.

Content Publishing Pipeline

Content is Markdown files with YAML frontmatter, the same open format used for skills and governance policies. The IngestionService reads Markdown files from configured directories, parses ContentMetadata frontmatter (title, description, author, slug, keywords, kind), computes a SHA-256 version_hash, and upserts into the markdown_content table. Write a blog post in your editor, add it to the content directory, and publish with a single CLI command. No CMS login. No web forms.

The execute_content_ingestion job loads ContentConfigRaw from YAML, iterates get_enabled_sources, and calls ingest_directory for each source with configurable IngestionOptions (override_existing, recursive). The ContentRepository handles creates and updates. The DefaultContentProvider implements the ContentProvider trait with methods for get_content, get_content_by_slug, list_content, and search, exposing content to templates and API routes.

Blog posts, documentation, guides, and legal pages all flow through the same pipeline. The Content struct carries ContentKind (Article, Guide, Tutorial), SourceId, optional CategoryId, and links_metadata() for internal cross-references. The list_content_by_source_handler and get_content_handler API routes serve content as JSON or Markdown via content negotiation.

  • Markdown In, HTML Out — IngestionService parses ContentMetadata frontmatter, computes SHA-256 version_hash, and upserts via ContentRepository. Configurable IngestionOptions for override and recursive scanning.
  • YAML-Configured Sources — ContentConfigRaw defines sources with source_id, category_id, path, and indexing options. execute_content_ingestion iterates get_enabled_sources automatically.
  • Content API — DefaultContentProvider implements ContentProvider trait with get_content, get_content_by_slug, list_content, and search. Content negotiation serves JSON or Markdown.

Search & Discovery

Full-text search is built into the binary via PostgreSQL. No Elasticsearch, no Algolia, no additional infrastructure. The markdown_fts table stores a tsvector column for every content item, indexed with a GIN index (idx_markdown_fts_search) for sub-millisecond lookups. The SearchRepository provides two query paths: search_by_keyword for free-text ILIKE queries across title, description, and body, and search_by_category for filtered browsing with CategoryId.

The SearchService orchestrates search logic. It accepts a SearchRequest with query text and optional SearchFilters (category_id), resolves the appropriate repository method, and returns a SearchResponse with typed SearchResult objects. Each result carries ContentId, slug, title, description, image, view_count from the content_performance_metrics join, SourceId, and CategoryId. Results are ranked by total_views DESC; popular content surfaces first.

The query_handler API endpoint accepts JSON SearchRequest payloads and returns SearchResponse. The DefaultContentProvider also exposes search through the ContentProvider trait's search method, making full-text search available to templates and prerenderers. One search infrastructure for content, documentation, and guides; all queries stay within your PostgreSQL instance.

  • PostgreSQL Full-Text Search — markdown_fts table with tsvector column and GIN index (idx_markdown_fts_search). Sub-millisecond lookups. No external search service. No additional infrastructure.
  • Content Indexing — SearchRepository provides search_by_keyword (ILIKE across title, description, body) and search_by_category (filtered by CategoryId). Results ranked by total_views from content_performance_metrics.
  • Search API — query_handler accepts SearchRequest with SearchFilters, returns SearchResponse with typed SearchResult objects. Also available via ContentProvider trait's search method.

Engagement Analytics

Web content has no analytics by default. The choices are typically Google Analytics (which sends every visitor to a third party and ships an opaque script to the browser), a paid SaaS (which adds another vendor, another contract, and another data export problem), or building it in-house. None of these let a compliance officer answer a question like "show me every click this user made on every campaign link last quarter" with a single SQL query against a database the organisation already owns.

Link tracking, UTM generation, click attribution, and behavioural metrics live in the same binary, written to the same Postgres instance, queryable with the same credentials as the rest of the application. The LinkGenerationService exposes a base generate_link and four specialised wrappers: generate_social_media_link, generate_internal_content_link, generate_external_cta_link, and generate_external_content_link. Each wrapper sets the UTM source, medium, and campaign defaults appropriate to its use case and delegates to generate_link, which writes a CampaignLink row with a generated short code.

The LinkAnalyticsService handles attribution. track_click records session, user, context, referrer, device, and country, and detects first clicks via check_session_clicked_link. get_link_performance returns a LinkPerformance with click_count, unique_click_count, conversion_count, and conversion_rate. get_campaign_performance aggregates by CampaignId; get_content_journey_map traces how users move from one piece of content to the next.

Per-page behaviour is captured by the EngagementEvent model: 18 behavioural fields per row including time_on_page_ms, time_to_first_interaction_ms, time_to_first_scroll_ms, max_scroll_depth, scroll_velocity_avg, scroll_direction_changes, click_count, mouse_move_distance_px, keyboard_events, copy_events, focus_time_ms, blur_count, tab_switches, visible_time_ms, hidden_time_ms, is_rage_click, is_dead_click, and reading_pattern. The record_engagement handler resolves the page slug through ContentRouting::resolve_slug and writes the event with session and user attribution. Aggregates roll up into content_performance_metrics, which the search ranker reads back to order results by total_views DESC.

  • Four Specialised Link Generators — LinkGenerationService exposes generate_social_media_link, generate_internal_content_link, generate_external_cta_link, and generate_external_content_link. Each delegates to a base generate_link that writes a CampaignLink with a short code and UTM JSON.
  • Click Attribution In SQL — LinkAnalyticsService::track_click records session, user, device, country, and first-click state. get_campaign_performance and get_content_journey_map return aggregated rows you can query directly.
  • 18 Behavioural Fields Per Page View — EngagementEvent records scroll depth, scroll velocity, direction changes, rage and dead clicks, reading pattern, focus and blur time, tab switches, and visible time. No third-party script. Rows live in your Postgres instance.

This Website Runs On It

systemprompt.io practices what it preaches. This website (the marketing pages, the documentation, the blog, the feature pages you are reading) is served by the same binary available in the template repository. There is no separate marketing site. No WordPress. No Webflow. This is systemprompt.io running systemprompt.io. The ApiServer, TemplateRegistry, IngestionService, SearchService, and LinkAnalyticsService described on this page are the same instances serving this page.

Every page is configured in YAML. The navigation you see at the top, the feature sections on this page, the footer links: all defined in configuration files via ContentConfigRaw that deploy instantly via the CLI. Change the YAML, run the publish command, and the website updates. The ContentRouting trait resolves URLs to slugs, the DefaultContentProvider fetches content, and the TemplateRegistry renders it. No code deploy required.

For organisations evaluating systemprompt.io, this means one fewer service to manage. Your AI governance infrastructure, your internal documentation, your team-facing dashboards, and your public website can all run on the same binary. The Extension trait's page_prerenderers() method generates static HTML at build time. Fewer services means fewer attack surfaces, fewer deployments, and fewer things to monitor.

  • YAML-Configured Pages — ContentConfigRaw and ContentRouting drive navigation, feature pages, layout, and branding. Change YAML, run publish. No code deploys.
  • One Service, Multiple Roles — AI governance, web serving, content publishing, search, engagement analytics, and documentation, all from the same binary on the same port via ApiServer.
  • Fewer Services, Fewer Risks — Every service you eliminate is an attack surface removed. page_prerenderers() generates static HTML at build time. One process to monitor, one artifact to deploy.

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

One binary. Governance and web serving.

The same infrastructure that governs your AI agents publishes your content.