Documentation
¶
Index ¶
- Variables
- func AllowUnauthenticated(stack *config.Stack, override bool) bool
- func EffectiveBind(stack *config.Stack, override string) string
- func NewSecurityPreflight(startup *config.Stack, bind string, insecure bool) func(*config.Stack) []string
- type ConfigDiff
- type DirWatcher
- type Handler
- func (h *Handler) CurrentConfig() *config.Stack
- func (h *Handler) HasUnresolved() bool
- func (h *Handler) Initialize(ctx context.Context, stackPath string) (*ReloadResult, error)
- func (h *Handler) OverridePath() string
- func (h *Handler) Plan(ctx context.Context) (*config.PlanDiff, error)
- func (h *Handler) Reload(ctx context.Context) (*ReloadResult, error)
- func (h *Handler) RetrySecrets(ctx context.Context) (recovered []string, still []string)
- func (h *Handler) SecretObservation() SecretObservation
- func (h *Handler) SeedResolved(values map[string]string, unresolved map[string]error)
- func (h *Handler) SeedResolvedStack(stack *config.Stack)
- func (h *Handler) SetLogger(logger *slog.Logger)
- func (h *Handler) SetNoExpand(noExpand bool)
- func (h *Handler) SetOnConfigApplied(fn func(*config.Stack))
- func (h *Handler) SetOnResolvedApplied(fn func(*config.Stack))
- func (h *Handler) SetOverride(path string, noOverride bool)
- func (h *Handler) SetRegisterServerFunc(...)
- func (h *Handler) SetSecretResolverFactory(factory func(*config.Stack) secrets.StackResolver)
- func (h *Handler) SetSecurityPreflight(fn func(*config.Stack) []string)
- func (h *Handler) ValidateCandidate(ctx context.Context, data []byte) error
- type MCPServerChange
- type MCPServerDiff
- type ReloadResult
- type ReplicaRuntime
- type ResourceChange
- type ResourceDiff
- type SecretObservation
- type Watcher
Constants ¶
This section is empty.
Variables ¶
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 ¶
AllowUnauthenticated applies the explicit CLI or stack insecure override.
func EffectiveBind ¶
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 ¶
CurrentConfig returns the current stack configuration.
func (*Handler) HasUnresolved ¶
HasUnresolved reports whether a secret failure is waiting to be retried.
func (*Handler) Initialize ¶
Initialize cold-loads a stack into a running stackless daemon. Candidate preparation and preflight preserve accepted state on rejection.
func (*Handler) OverridePath ¶
OverridePath returns the selected override path, which may not exist yet.
func (*Handler) Plan ¶
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 ¶
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 ¶
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 ¶
SeedResolvedStack records the startup resolved stack so a later recovery can merge a server into it instead of replacing the running definitions.
func (*Handler) SetNoExpand ¶
SetNoExpand sets whether to skip OpenAPI env var expansion.
func (*Handler) SetOnConfigApplied ¶
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 ¶
SetOnResolvedApplied receives the resolved stack after a successful pass. It must not rebuild that stack from the literal configuration.
func (*Handler) SetOverride ¶
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 ¶
SetSecurityPreflight installs the startup-owned static security comparison.
type MCPServerChange ¶
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 ¶
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 ¶
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 ¶
NewWatcher creates a file watcher for the given stack path. onChange is called when the file changes (after debouncing).
func (*Watcher) AddBasename ¶
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 ¶
SetDebounce sets the debounce duration for file changes.
func (*Watcher) Watch ¶
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.