Skip to content

Migration and Upgrades

This guide explains how Plumego manages API changes across versions and what a typical upgrade looks like for stable roots and x/* extensions.

For the release stability model, see Release Posture.

  • The stability tiers and what they promise
  • How to upgrade stable roots across a minor version
  • How to track an experimental x/* package that is moving toward stability
  • What to check before and after any upgrade
LayerTierChange promise
Stable roots (core, router, contract, middleware, security, store, health, log, metrics)GANo breaking changes within a major version. Additions are backwards-compatible. Deprecations are announced with at least one minor version of lead time.
Extension families with declared stable subpackages (x/ai: provider, session, streaming, tool)stable subpackageSame as GA within the declared subpackage.
Extension families marked experimentalexperimentalMay change without a deprecation period. API shape, package names, and behavior are subject to revision.

Read module.yaml for each package you depend on. The status field is the authoritative source; the documentation pages summarize it but the file is the ground truth.

Stable root upgrades across a minor version follow a predictable path because the API surface only grows — it never removes or renames existing exported symbols without a deprecation cycle.

Terminal window
# Check what changed since your current version.
git log --oneline vX.Y.Z..vX.Y+1.0 -- core/ router/ contract/ middleware/ security/ store/ health/ log/ metrics/
# Run the dependency boundary check to confirm your code still satisfies import rules.
go run ./internal/checks/dependency-rules
# Run tests for every package your service touches.
go test ./...
Terminal window
# Confirm no new boundary violations were introduced.
go run ./internal/checks/dependency-rules
go run ./internal/checks/agent-workflow
go run ./internal/checks/module-manifests
# Run the full gate suite that CI uses.
make gates

Stable root changes that commonly require caller updates:

  • New required fields on config structs — if a struct gains a required field with no zero value, callers that used struct literals without field names break at compile time. Use named fields in struct literals to isolate this.
  • New methods on interfaces — if your code implements a stable interface (e.g. health.ComponentChecker, cache.Cache from store/cache), a new method addition breaks your implementation. Check the interface changelog before upgrading.
  • Middleware ordering contracts — if the documented ordering of middleware effects changes, review your Use() call order.

Tracking an experimental extension toward stability

Section titled “Tracking an experimental extension toward stability”

Experimental packages move toward stability through explicit tier promotion in module.yaml. The promotion is announced in docs/release/roadmap.md and the public releases page.

Terminal window
# Check the current tier for a package you depend on.
cat x/ai/module.yaml | grep -A3 "stability\|status\|tier"
# Subscribe to ROADMAP.md changes on the branch you track.
git log --oneline -- docs/release/roadmap.md

Wrap experimental dependencies behind an interface

Section titled “Wrap experimental dependencies behind an interface”

When your service depends on an experimental package, isolate the dependency behind a local interface. This limits the blast radius of an upstream API change to one adapter file:

// internal/ai/provider.go — your local interface
package ai
import "context"
type CompletionProvider interface {
Complete(ctx context.Context, prompt string) (string, error)
}
// internal/ai/adapter.go — the x/ai adapter
package ai
import (
"context"
"github.com/spcent/plumego/x/ai/provider"
)
type AnthropicAdapter struct {
client provider.Provider
}
func (a *AnthropicAdapter) Complete(ctx context.Context, prompt string) (string, error) {
resp, err := a.client.Complete(ctx, &provider.CompletionRequest{
Model: "claude-3-haiku-20240307",
Messages: []provider.Message{
provider.NewTextMessage(provider.RoleUser, prompt),
},
})
if err != nil {
return "", err
}
return resp.GetText(), nil
}

When x/ai promotes provider to stable or changes its API, you update adapter.go only — handlers and domain logic are unaffected.

When an experimental package reaches stability

Section titled “When an experimental package reaches stability”
  1. Check the module.yaml status field — it changes from experimental to stable.
  2. Review the promoted API against your adapter to see if any names changed.
  3. Remove the isolation wrapper if the stable API is now clean enough to depend on directly, or keep it if your domain’s abstraction boundary is still valuable.
  4. Run make gates to confirm everything compiles and passes.

When a stable root deprecates a symbol, it follows this sequence:

  1. The symbol is marked with a // Deprecated: comment in the source.
  2. A replacement is provided in the same minor version.
  3. The deprecated symbol remains available for at least one minor version.
  4. The symbol is removed in the next major version.

To find deprecated symbols in your current dependencies:

Terminal window
grep -r "Deprecated:" $(go env GOPATH)/pkg/mod/github.com/spcent/plumego@vX.Y.Z/

Migrate to the replacement before the major version cutover so upgrades remain incremental.