reload

package
v1.0.0-rc.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNoStackLoaded = errors.New("no stack loaded")

ErrNoStackLoaded is returned by Plan when the handler has no loaded configuration, or no stack path, to compare against.

Functions

func AllowUnauthenticated

func AllowUnauthenticated(stack *config.Stack, override bool) bool

AllowUnauthenticated applies the explicit CLI or stack insecure override.

func EffectiveBind

func EffectiveBind(stack *config.Stack, override string) string

EffectiveBind applies the listener's CLI, stack, and loopback precedence.

func NewSecurityPreflight

func NewSecurityPreflight(startup *config.Stack, bind string, insecure bool) func(*config.Stack) []string

NewSecurityPreflight freezes the resolved startup values and CLI overrides. The returned comparison reports field names only and never resolves the old side against a refreshed variable store.

Types

type ConfigDiff

type ConfigDiff struct {
	MCPServers MCPServerDiff
	Resources  ResourceDiff
	// NetworkChanged indicates if the network config changed (requires full restart)
	NetworkChanged bool
	// ClientsChanged indicates the per-client access (`clients:`) block changed.
	// It needs an in-memory policy refresh (via the reload's onConfigApplied hook)
	// but no container or network work, so it must still mark the diff non-empty.
	ClientsChanged bool
	// LimitsChanged indicates the rate-limit (`limits:`) block
	// changed. Like ClientsChanged it needs only an in-memory policy rebuild
	// via the onConfigApplied hook (in-memory bucket state rebuilds) but must still mark the diff non-empty.
	LimitsChanged bool
	// PolicyChanged indicates the per-tool call (`policy:`) block changed.
	// Like LimitsChanged it needs only an in-memory gate rebuild via the
	// onConfigApplied hook and must still mark the diff non-empty. A
	// policy-only edit does not recreate containers.
	PolicyChanged bool
	// ToolDiscoveryChanged indicates the gateway.tool_discovery block
	// changed (mode and always_expose, order-sensitive). Like PolicyChanged
	// it needs only an in-memory refresh via the onConfigApplied hook and
	// must still mark the diff non-empty. A discovery-only edit does not
	// recreate containers.
	ToolDiscoveryChanged bool
	// GroupsChanged indicates the tool-group (`groups:`) block changed.
	// Like ClientsChanged it needs only an in-memory policy rebuild via the
	// onConfigApplied hook but must still mark the diff non-empty.
	GroupsChanged bool
	// ExperimentalChanged indicates the `experimental:` flag map changed.
	// Like ClientsChanged it needs only an in-memory flag re-resolution via
	// the onConfigApplied hook but must still mark the diff non-empty.
	ExperimentalChanged bool
	// SkillsPolicyChanged indicates the skill exposure (`skills:`) block
	// changed. Like ClientsChanged it needs only an in-memory policy rebuild
	// via the onConfigApplied hook but must still mark the diff non-empty.
	SkillsPolicyChanged bool
	// ModelPreferencesChanged indicates the `model_preferences:` block
	// changed. Like ClientsChanged it needs only the compiled projection
	// model policy swapped via the onConfigApplied hook (the next
	// projection reconcile applies it); no containers are touched.
	ModelPreferencesChanged bool
	// RunsChanged indicates the `runs:` block changed. Like ClientsChanged
	// it needs only the in-process recorder swapped via onConfigApplied;
	// no containers are touched.
	RunsChanged bool
}

ConfigDiff represents the differences between two stack configurations.

func ComputeDiff

func ComputeDiff(old, new *config.Stack) *ConfigDiff

ComputeDiff computes the differences between two stack configurations.

func (*ConfigDiff) IsEmpty

func (d *ConfigDiff) IsEmpty() bool

IsEmpty returns true if there are no changes.

type DirWatcher

type DirWatcher struct {
	// contains filtered or unexported fields
}

DirWatcher monitors a directory tree for changes and triggers a debounced callback. Unlike Watcher, which targets a single file, DirWatcher watches a whole subtree (e.g. the registry skills directory, where each skill is its own directory containing SKILL.md plus optional supporting files).

fsnotify is not recursive, so the watcher adds the root and every existing subdirectory, and adds newly-created subdirectories as it observes them. The callback is expected to re-read the tree from disk (a full reload), so the watcher does not need to capture every individual event — it only needs to fire shortly after a burst of changes settles.

func NewDirWatcher

func NewDirWatcher(root string, onChange func() error) *DirWatcher

NewDirWatcher creates a recursive directory watcher rooted at root. onChange is called (after debouncing) whenever an entry under root is created, written, removed, or renamed.

func (*DirWatcher) SetDebounce

func (w *DirWatcher) SetDebounce(d time.Duration)

SetDebounce sets the debounce duration for change events.

func (*DirWatcher) SetLogger

func (w *DirWatcher) SetLogger(logger *slog.Logger)

SetLogger sets the logger for watcher events.

func (*DirWatcher) Watch

func (w *DirWatcher) Watch(ctx context.Context) error

Watch starts watching the directory tree for changes. It blocks until the context is cancelled.

The root may not exist yet (the registry skills directory is created lazily). Watch never creates it — that is the caller's job — and instead watches the nearest existing ancestor so the root's eventual creation is observed and the subtree armed. Staying write-free keeps the watcher safe to run against a directory another goroutine may be removing (e.g. a test's temp dir).

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler manages hot reload for a running stack.

func NewHandler

func NewHandler(stackPath string, currentCfg *config.Stack, gateway *mcp.Gateway, rt *runtime.Orchestrator, port, basePort int, v config.VaultLookup, vs config.VaultSetLookup) *Handler

NewHandler creates a reload handler.

func (*Handler) CurrentConfig

func (h *Handler) CurrentConfig() *config.Stack

CurrentConfig returns the current stack configuration.

func (*Handler) HasUnresolved

func (h *Handler) HasUnresolved() bool

HasUnresolved reports whether a secret failure is waiting to be retried.

func (*Handler) Initialize

func (h *Handler) Initialize(ctx context.Context, stackPath string) (*ReloadResult, error)

Initialize cold-loads a stack into a running stackless daemon. Candidate preparation and preflight preserve accepted state on rejection.

func (*Handler) OverridePath

func (h *Handler) OverridePath() string

OverridePath returns the selected override path, which may not exist yet.

func (*Handler) Plan

func (h *Handler) Plan(ctx context.Context) (*config.PlanDiff, error)

Plan compares the stack file at h.stackPath with the configuration the daemon loaded. It does not apply the candidate, run security preflight, or modify currentCfg. Store-backed image, url, and network details are reported without values.

func (*Handler) Reload

func (h *Handler) Reload(ctx context.Context) (*ReloadResult, error)

Reload reloads the configuration from disk and applies changes.

func (*Handler) RetrySecrets

func (h *Handler) RetrySecrets(ctx context.Context) (recovered []string, still []string)

RetrySecrets re-resolves each unresolved workload with a fresh resolver. Success starts and registers it. Failure keeps the entry and updates the recorded message.

func (*Handler) SecretObservation

func (h *Handler) SecretObservation() SecretObservation

SecretObservation returns copies of the in-memory resolution maps.

func (*Handler) SeedResolved

func (h *Handler) SeedResolved(values map[string]string, unresolved map[string]error)

SeedResolved records the startup pass so the first reload does not treat every secret as rotated. A missing key later is a seed, not a rotation.

func (*Handler) SeedResolvedStack

func (h *Handler) SeedResolvedStack(stack *config.Stack)

SeedResolvedStack records the startup resolved stack so a later recovery can merge a server into it instead of replacing the running definitions.

func (*Handler) SetLogger

func (h *Handler) SetLogger(logger *slog.Logger)

SetLogger sets the logger.

func (*Handler) SetNoExpand

func (h *Handler) SetNoExpand(noExpand bool)

SetNoExpand sets whether to skip OpenAPI env var expansion.

func (*Handler) SetOnConfigApplied

func (h *Handler) SetOnConfigApplied(fn func(*config.Stack))

SetOnConfigApplied registers a hook fired after a successful reload swaps currentCfg. Today only the telemetry persistence layer hooks this — it uses the new stack to refresh per-server file writers without restarting the gateway. The hook runs while the handler still holds its mutex, so callbacks must be quick and must not call back into Handler.

func (*Handler) SetOnResolvedApplied

func (h *Handler) SetOnResolvedApplied(fn func(*config.Stack))

SetOnResolvedApplied receives the resolved stack after a successful pass. It must not rebuild that stack from the literal configuration.

func (*Handler) SetOverride

func (h *Handler) SetOverride(path string, noOverride bool)

SetOverride records the override selection shared by Plan, Reload, and Initialize. An empty path with noOverride false discovers stack.override.yaml beside the stack file. A path whose file is absent is not an error; the merge is skipped until the file appears.

func (*Handler) SetRegisterServerFunc

func (h *Handler) SetRegisterServerFunc(fn func(ctx context.Context, server config.MCPServer, replicas []ReplicaRuntime, stackPath string) error)

SetRegisterServerFunc sets the callback for registering MCP servers.

func (*Handler) SetSecretResolverFactory

func (h *Handler) SetSecretResolverFactory(factory func(*config.Stack) secrets.StackResolver)

SetSecretResolverFactory installs the per-pass resolver. Nil keeps reload from running providers. Plan never calls it.

func (*Handler) SetSecurityPreflight

func (h *Handler) SetSecurityPreflight(fn func(*config.Stack) []string)

SetSecurityPreflight installs the startup-owned static security comparison.

func (*Handler) ValidateCandidate

func (h *Handler) ValidateCandidate(ctx context.Context, data []byte) error

ValidateCandidate runs the daemon's load pipeline on candidate bytes without writing them. It resolves the same vault references reload uses.

type MCPServerChange

type MCPServerChange struct {
	Name string
	Old  config.MCPServer
	New  config.MCPServer
}

MCPServerChange represents a modification to an existing MCP server.

type MCPServerDiff

type MCPServerDiff struct {
	Added    []config.MCPServer
	Removed  []config.MCPServer
	Modified []MCPServerChange
	// Disabled lists servers whose enabled flag flipped true to false.
	// Other field edits on the same flip stay here: the server is retired
	// and is not restarted, and pins are kept.
	Disabled []config.MCPServer
	// Enabled lists servers whose enabled flag flipped false to true.
	// They follow the Added start path.
	Enabled []config.MCPServer
	// Dormant lists servers that are disabled on both sides and whose other
	// fields differ. No runtime work runs. A server added while disabled is
	// also recorded here (Old.Name empty) so a non-initial reload still
	// swaps currentCfg; initial load strips those entries.
	Dormant []MCPServerChange
	// AutoscalePolicyChanges lists servers whose autoscale block fields
	// changed but whose other config is stable. The reload handler applies
	// these via Autoscaler.UpdatePolicy without restarting the server.
	AutoscalePolicyChanges []MCPServerChange
}

MCPServerDiff contains changes to MCP servers.

type ReloadResult

type ReloadResult struct {
	Code          string   `json:"code,omitempty"`
	ChangedFields []string `json:"changed_fields,omitempty"`
	Success       bool     `json:"success"`
	Message       string   `json:"message"`
	Added         []string `json:"added,omitempty"`
	Removed       []string `json:"removed,omitempty"`
	Modified      []string `json:"modified,omitempty"`
	// Disabled and Enabled name servers and resources whose enabled flag
	// flipped. Dormant edits are applied to currentCfg only and are not listed.
	Disabled []string `json:"disabled,omitempty"`
	Enabled  []string `json:"enabled,omitempty"`
	Errors   []string `json:"errors,omitempty"`
}

ReloadResult contains the result of a reload operation.

type ReplicaRuntime

type ReplicaRuntime struct {
	HostPort    int
	ContainerID string
}

ReplicaRuntime carries runtime handles for one replica that the reload handler has provisioned. Container replicas populate both fields; local- process, SSH, external, and OpenAPI replicas pass zero-valued entries.

type ResourceChange

type ResourceChange struct {
	Name string
	Old  config.Resource
	New  config.Resource
}

ResourceChange represents a modification to an existing resource.

type ResourceDiff

type ResourceDiff struct {
	Added    []config.Resource
	Removed  []config.Resource
	Modified []ResourceChange
	// Disabled, Enabled, and Dormant mirror MCPServerDiff for resources.
	Disabled []config.Resource
	Enabled  []config.Resource
	Dormant  []ResourceChange
}

ResourceDiff contains changes to resources.

type SecretObservation

type SecretObservation struct {
	Resolved   map[string]string
	ResolvedAt map[string]time.Time
	Unresolved map[string]error
	Seeded     bool
}

SecretObservation is the value-free view GET /api/secrets reads.

type Watcher

type Watcher struct {
	// contains filtered or unexported fields
}

Watcher monitors a stack file for changes and triggers reload.

func NewWatcher

func NewWatcher(path string, onChange func() error) *Watcher

NewWatcher creates a file watcher for the given stack path. onChange is called when the file changes (after debouncing).

func (*Watcher) AddBasename

func (w *Watcher) AddBasename(name string)

AddBasename watches another file in the same directory. A non-member basename is ignored. Used for stack.override.yaml beside the stack file.

func (*Watcher) SetDebounce

func (w *Watcher) SetDebounce(d time.Duration)

SetDebounce sets the debounce duration for file changes.

func (*Watcher) SetLogger

func (w *Watcher) SetLogger(logger *slog.Logger)

SetLogger sets the logger for watcher events.

func (*Watcher) Watch

func (w *Watcher) Watch(ctx context.Context) error

Watch starts watching the file for changes. Blocks until context is cancelled.

We watch the parent directory rather than the file directly because most editors use atomic saves (write to temp file, then rename). When a file is renamed over the watched file, fsnotify loses track of it. Watching the directory catches all events including renames.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL