stable roots
Keep long-lived responsibilities narrow
Routing, contracts, transport middleware, and storage-facing primitives should stay legible enough to defend compatibility over time.
Plumego is easiest to evaluate when you can see which responsibilities stay in the kernel, which ones branch outward into extensions, and where the default request path begins and ends.
stable roots
Routing, contracts, transport middleware, and storage-facing primitives should stay legible enough to defend compatibility over time.
extension rule
Product or protocol-specific expansion should begin in x/* families so the kernel does not absorb every fast-moving concern.
canonical path
The website, docs, and reference app all point toward one readable route from bootstrap to write path before asking readers to branch into deeper packages.
Every Plumego service sits at the intersection of three module layers and a machine-readable control plane. The diagram below shows the full structure, import direction, and how specs/ governs what changes are allowed.
Plumego becomes easier to trust when the kernel, the canonical reading path, and the outward expansion families are all visible with actual repository facts behind them.
Optional protocol and product capability — evolves faster, never absorbed into the kernel
The one reading path every newcomer follows — bootstrap → wiring → route registration → request handling
Long-lived compatibility surface — slower-moving, narrow, explainable without repository archaeology
The module hierarchy is only one track. The control plane (specs/, docs/, tasks/)
runs in parallel and holds the machine-readable rules that govern how the module code may be changed.
Both tracks are first-class — neither is subordinate to the other.
Module code track
Control plane track
internal/checks/ enforces the rules in specs/ against the module code at CI time.
Neither humans nor AI coding agents bypass this gate.
If a new reader cannot tell whether a change belongs to stable roots, a capability family, or the canonical app path, the site is not doing enough explanatory work.
kernel
Core modules carry the strongest compatibility burden. They should stay narrow, boring, and easy to reason about in code review.
extensions
These families are useful, but they do not pretend to share the same stability profile as the smallest kernel surface.
workflow
The default app path gives teams one bootstrap model, one routing flow, and one shared place to begin before extension work starts.
The quickest architecture read starts at the reference app, confirms the app-local wiring, and only then expands outward into packages that obviously belong to the feature or protocol being added.
Keep the entry boring so ownership starts in visible code.
Bootstrap discipline protects readability.
Route registration is the first architectural map.
Expansion follows classification, not convenience.
Route registration is the first architectural map of a service. In Plumego, it lives in one explicit file — readable in any code review without opening framework source.
func (a *App) Routes() http.Handler {
r := router.New()
r.Use(middleware.RequestID())
r.Use(middleware.Logger(a.log))
r.Use(middleware.Recovery(a.log))
r.Get("/api/items", a.items.List)
r.Post("/api/items", a.items.Create)
r.Get("/api/orders", a.orders.List)
r.Get("/health", health.Handler(a.checker))
// full route map visible here — no hidden registration
return r
} type App struct {
log log.Logger
db *sql.DB
items *items.Handler
orders *orders.Handler
checker health.Checker
}
func New(cfg *config.Config, deps AppDeps) (*App, error) {
// explicit — every dependency named here
return &App{
log: deps.Logger,
db: deps.DB,
items: items.NewHandler(deps.DB),
orders: orders.NewHandler(deps.DB),
checker: health.NewChecker(),
}, nil
} The architecture page should not just name the kernel. It should also explain what kind of work belongs there and which root names are a warning sign that capability concerns are drifting inward.
stable-root work intent
Change kernel, lifecycle, route structure, transport contracts, transport middleware, auth primitives, or storage primitives.
discouraged roots
These names are useful as product or protocol concerns, but they are poor default roots because they blur the ownership boundary of the kernel.
extension work intent
Change product capability, business feature, protocol adaptation, or extension behavior.
Both layers matter. The difference is that one protects the default path for almost every service, while the other protects optional capability work from leaking inward.
Extension families are not just module names — each one owns a concrete capability domain. Use these five scenarios to decide which x/* family is relevant before opening the module primer.
multi-tenant saas
x/tenant adds enforced tenant identity, per-tenant quota, and policy evaluation outside the HTTP kernel. Stable roots carry the transport baseline; x/tenant carries the tenant layer.
Open x/tenant primerai service
x/ai adds provider abstraction, session lifecycle, and streaming response handling alongside a stable net/http kernel. AI capability work stays outside the default request path.
Open x/ai primerapi gateway
x/gateway adds reverse proxy, upstream health, and load balancing at the edge without changing the stable routing model used by upstream services.
Open x/gateway primerreal-time
x/websocket adds full-duplex WebSocket handling that coexists with standard HTTP routes, using the same stable middleware chain and handler shape.
Open x/websocket primerobservability
x/observability adds exporter, tracer, collector, and adapter wiring without touching the stable routing model. Instrumentation stays outside the request kernel.
Open x/observability primerrest api
x/rest adds a typed resource interface, consistent response envelopes, and validation hooks across all CRUD endpoints — so each new resource follows the same structural contract.
Open x/rest primermessaging
x/messaging connects message queues, pubsub flows, scheduled jobs, and inbound webhook transport under one family entrypoint — keeping async concerns outside the HTTP kernel.
Open x/messaging primerfrontend
x/frontend serves embedded or directory-based assets as a transport-layer concern. Cache-busting, pre-compressed delivery, and directory safety stay in the extension layer.
Open x/frontend primerBeta families have API snapshots, owner sign-off, and promotion evidence — safe to evaluate for production. Experimental families are tested but not API-frozen; start with the family entrypoint and expect iteration.
API surface frozen between minor release refs, tested and promoted with owner sign-off. Safe to evaluate for production with known stability.
x/rest beta REST resource APIs
Build CRUD resources with a typed resource interface, consistent response envelopes, and validation hooks — without reinventing the pattern per endpoint.
x/gateway beta API gateway & proxy
Route and rewrite HTTP traffic between upstream services. Includes dynamic service discovery, load balancing, and edge rewrite rules.
x/websocket beta Real-time WebSocket
Manage connected WebSocket clients with a Hub model. Broadcast messages, handle disconnect, and keep transport concerns out of your business logic.
x/observability beta Observability & ops tooling
Attach structured metrics, trace exporters, and dev-mode diagnostics without touching the stable request path. Includes devtools and ops sub-packages.
x/tenant beta Multi-tenant SaaS
Resolve tenant identity at the transport layer and enforce per-tenant policy without letting tenant logic leak into stable roots or handler code.
x/frontend beta Frontend asset serving
Serve static assets, embed SPA shells, and handle cache-busting strategies as a transport-layer concern — without polluting handler or business logic.
x/messaging beta Messaging, queues & webhooks
Connect message queues, publish/subscribe flows, scheduled jobs, and inbound or outbound webhook transport — all under one family entrypoint. Subordinate primitives (mq, pubsub, scheduler, webhook) remain experimental.
Included in repo quality gates and tested, but API surface is not frozen. Start from the family entrypoint before exploring sub-packages.
x/ai experimental AI streaming & tool routing
Build AI streaming SSE endpoints with provider contracts, session state management, and explicit tool invocation policy — without coupling AI logic to the HTTP kernel. Stable-tier subpackages (provider, session, streaming, tool) have beta evidence.
x/data experimental Data access & storage
Add structured data access primitives on top of the store stable root — typed queries, migrations, and repository patterns for common persistence scenarios. x/data/file and x/data/idempotency have beta surface evidence.
x/fileapi experimental File upload & download
Handle multipart uploads, chunked downloads, and temporary signed URL generation — separated cleanly from the HTTP kernel.
x/openapi experimental OpenAPI document generation
Generate OpenAPI 3.1 specifications from registered routes and operation hints — dependency-free JSON/YAML output wired through the CLI.
x/resilience experimental Resilience & circuit breaking
Wrap upstream calls with retry logic, circuit breakers, and timeout policies — composable primitives that stay outside the stable transport layer.
x/rpc experimental gRPC server and gateway
Optional gRPC server lifecycle helpers, outbound connection pooling, and HTTP-over-RPC gateway adapters alongside standard HTTP routes.
x/validate experimental Request validation
Explicit BindJSON and Bind call sites in handlers that decode JSON and return structured contract.APIError responses — no tag-based global validator.
The architecture is not only a package story. It is also a repository story: docs teach the path, the reference app proves it, synced facts publish the boundaries, app-local code keeps service ownership readable, and the control plane turns architecture rules into machine-readable signals.
docs
Docs teach the canonical path, request flow, and public mental model before readers start inferring structure from package names alone.
Open docsreference
reference/standard-service keeps the default bootstrap and route ownership visible so newcomers have one repository baseline to compare against.
Read the reference appgenerated facts
Synced facts turn stable roots, extension families, and maturity signals into machine-readable signals instead of duplicated marketing claims.
Check project statusapp-local code
internal/app is where service-specific wiring stays obvious. It should remain the first stop before deeper capability packages enter the conversation.
See example pathscontrol plane
specs/ holds dependency rules, task routing, and change recipes in machine-readable form. These signals keep import boundaries and ownership decisions consistent across the repository.
Read repo control planeArchitecture answers where a change belongs. The next question is how to run it — start with Getting Started, then use Examples to see it in action. If you are still deciding whether Plumego fits, the adoption fit check is the better starting point.