v1.1.0 v1.0.0 · stable roots GA · x/* families carry explicit maturity labels View release posture →

System Topology

Architecture

Stable roots, extension families, and the canonical request path that define the Plumego mental model.

Read the architecture as a boundary map.

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

Keep long-lived responsibilities narrow

Routing, contracts, transport middleware, and storage-facing primitives should stay legible enough to defend compatibility over time.

extension rule

Push optional capability work outward

Product or protocol-specific expansion should begin in x/* families so the kernel does not absorb every fast-moving concern.

canonical path

Teach one request path first

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.

Three layers + control plane — the complete picture.

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.

Three cooperating zones — visible on one screen.

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.

Two parallel tracks — module code and control plane.

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

x/* extension families experimental · beta
canonical path reference/standard-service
stable roots core · router · contract · middleware · …

Control plane track

tasks/ executable work cards for agents and humans
specs/ task-routing · dependency-rules · change-recipes
docs/ human-readable architecture · primers · roadmap

internal/checks/ enforces the rules in specs/ against the module code at CI time. Neither humans nor AI coding agents bypass this gate.

Three layers you should be able to classify immediately

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.

workflow

Canonical request path

The default app path gives teams one bootstrap model, one routing flow, and one shared place to begin before extension work starts.

  • docs/getting-started
  • docs/reference-app
  • internal/app/app.go
  • internal/app/routes.go

How to inspect one request without guessing

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.

01

Edge request enters through net/http

Keep the entry boring so ownership starts in visible code.

02

App-local wiring assembles dependencies

Bootstrap discipline protects readability.

03

Routes classify the public request path

Route registration is the first architectural map.

04

Capability work branches only after the path is clear

Expansion follows classification, not convenience.

What the canonical path looks like in code.

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.

internal/app/routes.go
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
}
internal/app/app.go
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
}

Stable-root signals should stay explicit.

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.

discouraged roots

Capability-shaped names are usually a signal to branch outward

These names are useful as product or protocol concerns, but they are poor default roots because they blur the ownership boundary of the kernel.

  • ai
  • tenant
  • net
  • pubsub
  • rest
  • validator
  • utils
  • frontend

Stable roots vs extension modules: an ownership distinction, not a capability distinction.

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.

Aspect Stable roots Extension modules
Ownership test Does this belong to almost every service and need a stronger compatibility promise? Is this optional, protocol-specific, or product-shaped work that should not redefine the default path?
Change velocity Slower, narrower, and easier to defend in review because the compatibility burden is higher. Allowed to evolve faster as long as the boundary with the canonical path stays legible.
Reader expectation A newcomer should be able to understand why the module exists from the docs and the reference app alone. A newcomer should first understand why they need it before opening the package family.

What you can build with x/* extension families.

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

Per-tenant routing, quota, and JWT-backed policy

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 primer

ai service

Multi-provider AI with streaming and tool routing

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 primer

api gateway

Edge proxying, load balancing, and route composition

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 primer

real-time

WebSocket connections over the same HTTP server

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 primer

observability

Traces, metrics, and structured logs wired at the service boundary

x/observability adds exporter, tracer, collector, and adapter wiring without touching the stable routing model. Instrumentation stays outside the request kernel.

Open x/observability primer

rest api

CRUD resource controllers with consistent response conventions

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 primer

messaging

Async messaging, queues, and inbound webhook delivery

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 primer

frontend

Static asset serving and SPA embedding

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 primer

All x/* families, grouped by maturity.

Beta 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.

beta

Beta — API frozen between release refs

API surface frozen between minor release refs, tested and promoted with owner sign-off. Safe to evaluate for production with known stability.

experimental

Experimental — evaluate before adopting

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.

Repository shape should reinforce the same mental model.

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

Learning path and boundary explanation

Docs teach the canonical path, request flow, and public mental model before readers start inferring structure from package names alone.

Open docs

reference

Runnable service shape

reference/standard-service keeps the default bootstrap and route ownership visible so newcomers have one repository baseline to compare against.

Read the reference app

generated facts

Published inventory and maturity signals

Synced facts turn stable roots, extension families, and maturity signals into machine-readable signals instead of duplicated marketing claims.

Check project status

app-local code

The place where ownership stays visible

internal/app is where service-specific wiring stays obvious. It should remain the first stop before deeper capability packages enter the conversation.

See example paths

control plane

Machine-readable rules that reinforce ownership

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 plane

Continue with the reference path.

Architecture 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.