serve

package
v1.17.2 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: Apache-2.0 Imports: 65 Imported by: 0

Documentation

Overview

Package serve is the importable serve band: the config→listener composition that turns a loaded configuration into a running Harbor Protocol server.

Distinct from internal/server

internal/server is the Protocol server's request-handling core (scope authorizers, bootstrap handler, search-scope adapters). This package sits ABOVE it: it assembles the runtime (via internal/runtime/assemble), mounts the Protocol surfaces (via BuildMux), binds the listener, and owns the serve/close lifecycle. In short — internal/server answers requests; this package composes the process that listens for them.

One constructor, production-shaped by construction

Boot REQUIRES a non-nil auth-validator factory: identity is mandatory, so a nil factory is a loud construction error, never an unauthenticated listener. Boot mounts ONLY the surfaces every caller shares. Dev-only surfaces (the bootstrap-token endpoint, draft scaffolding, the token-rotate surface, the Console static build, fixture seeding, the mock-LLM snapshot override) are composed CALLER-SIDE through the injection seams on Options — the extra pre-CORS routes, the auth-surface builder, the LLM-snapshot builder, and the post-boot hook. The dev signer never reaches this package.

Lifecycle

Boot returns a Handle whose Serve binds the listener and runs until ctx cancels, and whose Close drains every subsystem in reverse dependency order. On any boot error every already-opened subsystem is Closed before returning.

cmd/harbor/session_ensurer.go — adapts the concrete sessions.Registry to the protocol.SessionEnsurer seam.

The Protocol ControlSurface owns the create-on-first-use behaviour on `start` but must not import the sessions package (it depends only on the error-only `protocol.SessionEnsurer` interface). This adapter lives at the cmd/harbor wiring boundary — the one place allowed to know both the concrete registry and the Protocol surface — and translates the registry's lifecycle sentinels into the protocol-side sentinels the surface's error mapper understands.

Index

Constants

This section is empty.

Variables

View Source
var ErrAuthValidatorFactoryRequired = errors.New("serve: auth-validator factory is required (identity is mandatory; nil is never an unauthenticated listener)")

ErrAuthValidatorFactoryRequired is returned by Boot when Options carries a nil AuthValidatorFactory. Identity is mandatory: the serve band never stands up an unauthenticated listener. Callers compare via errors.Is.

View Source
var ErrRunLoopDriverMisconfigured = errors.New("dev: per-task RunLoop driver missing a mandatory dependency")

ErrRunLoopDriverMisconfigured fires when NewRunLoopDriver is called with a nil bus / RunLoop / planner. Driver invariant: all three are mandatory.

View Source
var ErrRuntimeAddOwnerMissing = errors.New("serve: runtime-added mcp connection is missing its (tenant, agent) owner")

ErrRuntimeAddOwnerMissing is returned by MCPConnectionAttacher.Attach when a runtime-added connection reaches the attach path without a full (tenant, agent) owner. It is a fail-closed guard: a runtime-added entry with no owner tag would be an unreconcilable orphan, so the attach is rejected loudly rather than registering an untagged runtime-add.

Functions

func BootDeclaredMCPServerNames

func BootDeclaredMCPServerNames(cfg *config.Config) []string

BootDeclaredMCPServerNames returns the names of every MCP server declared in the boot yaml (`tools.mcp_servers[].name`) — the set the remove verb rejects and the run-start reconcile never detaches (boot-declared servers are not revisioned state).

func BootDeclaredMCPServerSet

func BootDeclaredMCPServerSet(cfg *config.Config) map[string]struct{}

BootDeclaredMCPServerSet returns the boot-declared MCP server names as a set for the run-start reconcile's O(1) skip check.

func BootDeclaredOAuthProviderNames added in v1.14.0

func BootDeclaredOAuthProviderNames(cfg *config.Config) []string

BootDeclaredOAuthProviderNames returns the names of every OAuth provider declared in the boot yaml (`tools.oauth_providers[].name`) — the set the set/remove_oauth_provider verbs reject (boot wins; edit yaml + restart).

func InstanceID

func InstanceID(prefix string) string

InstanceID mints a stable-per-process instance identifier of the form "<prefix>-<hostname>" (bare prefix when the hostname is unavailable). A Console attached to multiple Runtimes keys each attachment by it. Shared by every production serve caller so the id shape stays uniform.

func MCPAddStdioAllowlist

func MCPAddStdioAllowlist(cfg *config.Config) []string

MCPAddStdioAllowlist projects the operator's stdio allowlist out of config for the runtime add-connection gate. A nil block yields a nil allowlist (fail-closed — every stdio add is rejected).

Types

type ApprovalChecker added in v1.14.0

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

ApprovalChecker is the production tasks.list approval-gate seam — it answers "does this task's run hold an open HITL / tool-approval gate?" by reading the pause coordinator, scoped to the task's own identity + run (the projection-completeness gate). It satisfies the tasks/protocol.ApprovalChecker interface structurally, keeping the pause package out of tasks/protocol's import graph (§4.4 seam).

safe for concurrent reuse: immutable after construction — it holds only the coordinator reference (itself safe for concurrent reuse) and reads all per-call state from its arguments.

func NewApprovalChecker added in v1.14.0

func NewApprovalChecker(pauses pauseresume.Coordinator) *ApprovalChecker

NewApprovalChecker builds the coordinator-backed approval checker. A nil coordinator returns nil so the caller leaves the seam unwired (the row's has_pending_approval stays at its honest false — the projection gate's Half-B prod-wiring test proves production wires a non-nil coordinator).

func (*ApprovalChecker) HasPendingApproval added in v1.14.0

func (c *ApprovalChecker) HasPendingApproval(ctx context.Context, taskIdentity identity.Identity, runID string) bool

HasPendingApproval reports whether the task's run holds at least one PAUSED pause record with the ApprovalRequired reason, scoped to the task's own (tenant, user, session) [+ run]. The read is identity-scoped so session A's open gate never bleeds into session B's row. A read error or an incomplete triple returns false (honest — the row shows no gate rather than a fabricated one; the failure is logged nowhere hotter than the coordinator's own diagnostics).

type AuthSurfaceBuilder

type AuthSurfaceBuilder func(red audit.Redactor, bus events.EventBus, logger *slog.Logger) (*auth.RotateSurface, error)

AuthSurfaceBuilder builds the optional token-rotate surface after the runtime is assembled (it needs the redactor + bus the assembly produces). The dev caller injects a builder that closes over its dev signer; the production caller leaves it nil (no in-runtime token issuer).

type AuthValidatorFactory

type AuthValidatorFactory func(ctx context.Context, cfg *config.Config, red audit.Redactor, bus events.EventBus, logger *slog.Logger) (auth.Validator, error)

AuthValidatorFactory builds the Protocol auth validator from the loaded config plus the assembled redactor / bus / logger. The production caller injects a JWKS-backed factory; the dev caller injects one built from its ephemeral dev signer. It is REQUIRED — a nil factory fails Boot loud.

func NewJWKSAuthValidatorFactory

func NewJWKSAuthValidatorFactory() AuthValidatorFactory

NewJWKSAuthValidatorFactory returns THE production auth-validator factory: it projects the operator's identity config onto a JWKS-backed Validator (URL or file source), wiring the assembled redactor / bus / logger. The initial JWKS fetch runs synchronously inside the projection, so a bad source fails the boot loud. Every production serve caller (the serve subcommand and the external-serving facade) injects this one factory — a second hand-rolled copy is the drift this shared constructor exists to prevent.

type BuiltMux

type BuiltMux struct {
	// Mux is the transports mux the caller mounts under /v1/.
	Mux *http.ServeMux
	// FlowRegistry is the empty registry the flows surface serves. Nil when
	// the flows surface was not mounted (no artifact store / task registry).
	FlowRegistry *flow.Registry
}

BuiltMux is the result of BuildMux: the mounted Protocol mux plus the flow registry the flows surface serves (the caller seeds the same registry).

func BuildMux

func BuildMux(in MuxInput) (*BuiltMux, error)

BuildMux constructs every Protocol surface the non-nil handles in MuxInput imply and returns the mounted transports mux. It is the single source of the handle→transport-option mapping shared by cmd/harbor and the test kit.

type Enricher

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

Enricher is the production tasks.get Enricher for the dev stack. It provides parent-session / planner-snapshot / trajectory enrichment from in-memory runtime state.

safe for concurrent reuse: the enricher is immutable after construction — the trajectory accessor is a pure function, the session lister is itself safe for concurrent reuse, and no mutable receiver state is added.

func NewEnricher

func NewEnricher(trajectoryFn func(tasks.TaskID) *planner.Trajectory, opts ...EnricherOption) *Enricher

NewEnricher builds the tasks.get Enricher over a trajectory accessor. The accessor is a pure function (the run-loop driver's TrajectoryByTaskID), so the returned Enricher is safe for concurrent reuse.

func (*Enricher) Cost

Cost returns a zero-valued cost rollup. Per-task cost aggregation from the `llm.cost.recorded` event stream is DEFERRED to the tools annotator follow-up (a documented risk-note deferral): the sessions cost helper is session-scoped and not extractable for a task-scoped rollup without a divergent second reader, so the un-stub lands with the tools annotator work rather than forking the cost path. This is display-only (no facet/sort operates over the task cost rollup), so the honest zero is gate-clean.

func (*Enricher) ParentSession

ParentSession returns the parent-session card for the task — the session's lifecycle status + start/last-activity timestamps, read from the session lister scoped to the caller's own identity (the task's session is the caller's session; GetTask already scoped the lookup).

AgentName is left EMPTY on purpose: no single-valued session→agent binding exists in V1 (a session may run several agents over its life), so its absence is representable (omitted) rather than a fabricated value — the same honest-omission the sessions projection takes for its agent fields (the silent-absence class rule). The projector overlays only the non-zero fields, so the SessionID baseline it set is preserved.

func (*Enricher) PlannerSnapshot

PlannerSnapshot returns nil — planner-checkpoint references are deferred to the checkpoint store.

func (*Enricher) Trajectory

Trajectory projects the planner's in-memory reasoning trace onto the Protocol wire. Steps with empty ReasoningTrace are filtered out. Returns nil when the task's trajectory is unavailable (evicted or the run-loop didn't store one).

type EnricherOption added in v1.14.0

type EnricherOption func(*Enricher)

EnricherOption configures NewEnricher.

func WithSessionLister added in v1.14.0

func WithSessionLister(l sessions.SessionLister) EnricherOption

WithSessionLister wires the session lister the parent-session card reads the parent session's status + timestamps from. A nil lister is treated as "not supplied".

type ExtraRoutesFunc

type ExtraRoutesFunc func(ctx context.Context, m RouteMount) ([]func(context.Context) error, error)

ExtraRoutesFunc mounts caller-side pre-CORS routes and returns any closers they registered. Called after the boot's validator and bind address are known and before the CORS wrap. On error, the closers accumulated up to the failing step MUST still be returned — Boot appends them to its rollback chain before inspecting the error, so a partially-mounted seam never leaks an already-constructed subsystem.

type Handle

type Handle struct {
	// Cfg / Bus are exported so a supervising caller (the dev hot-reload
	// loop) can read the reload policy and publish reload lifecycle events.
	Cfg *config.Config
	Bus events.EventBus
	// contains filtered or unexported fields
}

Handle is the running serve band a successful Boot returns. Serve binds the listener and runs until ctx cancels; Close drains every subsystem in reverse dependency order.

func Boot

func Boot(ctx context.Context, opts Options) (*Handle, error)

func (*Handle) BindAddr

func (h *Handle) BindAddr() string

BindAddr reports the address the listener is (or will be) bound to. After Serve binds an ephemeral port it reflects the OS-assigned address. Internally synchronized — safe to call while Serve runs.

func (*Handle) Close

func (h *Handle) Close(ctx context.Context)

Close runs every subsystem's Close in reverse dependency order. Idempotent: the closer slice is drained exactly once; a second Close is a no-op. Close also fires a "closed before bind" readiness signal so a WaitReady waiter on a handle whose Serve never bound (or was cancelled before binding) returns promptly instead of blocking until its own ctx expires.

func (*Handle) Handler

func (h *Handle) Handler() http.Handler

Handler returns the composed CORS-wrapped router the listener serves. It lets a caller drive the Protocol surface (and any caller-mounted pre-CORS route) through httptest without binding a port — the posture split between the dev and serve compositions is asserted this way.

func (*Handle) Serve

func (h *Handle) Serve(ctx context.Context) error

Serve binds the listener and runs the http.Server until ctx cancels.

func (*Handle) WaitReady added in v1.15.0

func (h *Handle) WaitReady(ctx context.Context) (string, error)

WaitReady blocks until the listener binds (returning the actual OS-assigned address) or until the bind fails / ctx cancels (returning the error). It is the race-safe one-shot readiness contract a co-launched client (the TUI, an operator script, an embedder) waits on before dialing the server — no polling, no second listener lifecycle.

Safe to call from multiple goroutines before, during, or after Serve binds: the first successful bind delivers the address to every waiter through the buffered channel; a late caller after a successful bind returns immediately. If Serve was never called or was cancelled before binding, WaitReady blocks until ctx cancels (callers SHOULD scope their wait with a deadline). Close fires a "closed before bind" signal so a waiter on an abandoned handle returns promptly.

type LLMProviderInstaller added in v1.17.0

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

LLMProviderInstaller manages the runtime-level set of Protocol-installed inference provider bindings. Because a provider KEY is a runtime-level credential (not identity-scoped), the set is keyed by binding NAME, not by owner tuple — the install/uninstall verbs feed the one shared LiveKey.

Concurrent reuse: the immutable config is set once at construction; the managed-source map is guarded by mu. Per-run identity is never read from the installer.

func NewLLMProviderInstaller added in v1.17.0

func NewLLMProviderInstaller(liveKey *llm.LiveKey, primaryProvider string, brokers []config.InferenceBrokerConfig, bus events.EventBus, redactor audit.Redactor, principal string, logger *slog.Logger) *LLMProviderInstaller

NewLLMProviderInstaller builds the production installer. liveKey is mandatory (the shared holder the bifrost Account reads); a nil returns a nil installer so the caller leaves the verb unwired (→ 501 at the wire edge) rather than nil-panicking. primaryProvider is the runtime's configured provider — a binding for any other provider is rejected (V1 is single-provider). principal is the per-runtime service-principal id for the pull audit event.

func (*LLMProviderInstaller) BootConnectPrimary added in v1.17.0

func (i *LLMProviderInstaller) BootConnectPrimary(ctx context.Context, brokerName string) error

BootConnectPrimary connects a config-declared brokered PRIMARY (`llm.credential_source: remote`) SYNCHRONOUSLY and fails loud on broker-unreachable — the boot exits non-zero naming the broker + missing config (never a silent empty LiveKey). It then starts the refresh scheduler. Called once at boot when the primary is brokered.

func (*LLMProviderInstaller) BrokerNames added in v1.17.0

func (i *LLMProviderInstaller) BrokerNames() []string

BrokerNames returns the boot-declared inference-broker names (for the agent-config service's WithInferenceBrokers unknown-broker gate).

func (*LLMProviderInstaller) Close added in v1.17.0

Close stops every managed binding's refresh goroutine + closes its source (zeroing the LiveKey). Joined on runtime shutdown.

func (*LLMProviderInstaller) InstallLLMProvider added in v1.17.0

func (i *LLMProviderInstaller) InstallLLMProvider(_ context.Context, _, _ string, desc agentcfg.LLMProviderDescriptor) error

InstallLLMProvider installs (upserts) / rotates the named binding: it builds the broker-pull source over the shared LiveKey and starts an async first pull + refresh scheduler. A re-install by the same name replaces (closes) the prior binding (the rotate path). The pull is best-effort at install time and fail-loud in the loop (parity with the OAuth lazy credential resolution) — the binding is installed immediately.

func (*LLMProviderInstaller) UninstallLLMProvider added in v1.17.0

func (i *LLMProviderInstaller) UninstallLLMProvider(_ context.Context, name string) error

UninstallLLMProvider removes the named binding and CLOSES its source — the live key is zeroed so a subsequently-bound call fails loud rather than serving the old key. A missing name is an idempotent no-op.

type LLMSnapshotBuilder

type LLMSnapshotBuilder func(cfg *config.Config) (*llm.ConfigSnapshot, error)

LLMSnapshotBuilder projects the loaded config onto the LLM ConfigSnapshot the runtime opens the client with. The dev caller injects a builder that runs the mock-LLM gate and overrides the driver when the escape hatch fired; a nil builder uses the default llm.SnapshotFromConfig projection.

type MCPConnectionAttacher

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

MCPConnectionAttacher implements agentcfgprotocol.ConnectionAttacher by reusing the boot-time mcpdrv.Attach lifecycle (dial → initialize → discover → register) against the LIVE catalog + registry + bus. It owns a mutex-guarded closer chain so a runtime-added server's transport drains on stack teardown.

Concurrent reuse: the collaborators (catalog / registry / bus / logger / defaultIdentity) are set once at construction; the only mutable state is the closers slice, guarded by mu (documented internally-synchronised).

func NewMCPConnectionAttacher

func NewMCPConnectionAttacher(catalog tools.ToolCatalog, registry *mcpdrv.Registry, bus events.EventBus, logger *slog.Logger, defaultIdentity identity.Identity, oauthProviders map[string]toolauth.OAuthProvider, oauthProviderSet toolauth.ProviderSet) *MCPConnectionAttacher

NewMCPConnectionAttacher builds the production attacher. catalog, registry, and bus are mandatory (mcpdrv.Attach validates them too). oauthProviders may be nil when no provider is declared; oauthProviderSet, when non-nil, is the runtime set (boot seed + installs) resolution consults first.

func (*MCPConnectionAttacher) Attach

Attach drives the real MCP attach for one runtime-added connection. The operator-supplied auth Headers flow to the live transport ONLY (the agent-config service never persists them). A partial-attach failure is drained by mcpdrv.Attach's own closer-chain handling; this method merges the per-add closers into its master chain under the lock so teardown drains the subprocess. An auth-required condition is surfaced by wrapping agentcfgprotocol.ErrAuthRequired so the service parks on the unified pause/resume primitive.

func (*MCPConnectionAttacher) Close

Close drains every runtime-added server's transport in reverse order. Wired into the boot closer chain so a runtime-added subprocess does not outlive the stack.

func (*MCPConnectionAttacher) SetOAuthDiscoveryOrigins added in v1.14.0

func (a *MCPConnectionAttacher) SetOAuthDiscoveryOrigins(ctx context.Context, name string, origins []string) (prev []string, err error)

SetOAuthDiscoveryOrigins applies a runtime-added connection's OAuth-discovery cross-origin allow-list to the LIVE MCP registry (the live half of `agent_config.set_mcp_discovery_origins`), returning the prior set. It satisfies the agent-config service's DiscoveryOriginApplier seam by delegating to the process-global bare-name registry (identity-mandatory for authorization; the registry is never re-keyed by identity). The revoke-prune of any recorded requirement lives in the registry mutator.

type MCPConnectionDetacher

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

MCPConnectionDetacher implements projection.ConnectionDetacher against the LIVE catalog + registry. Concurrent reuse: the collaborators are set once at construction; the type holds no mutable state (the registry + catalog own their own synchronisation).

func NewMCPConnectionDetacher

func NewMCPConnectionDetacher(catalog tools.ToolCatalog, registry *mcpdrv.Registry, logger *slog.Logger) *MCPConnectionDetacher

NewMCPConnectionDetacher builds the production detacher. catalog and registry are mandatory (the reconcile is a no-op without them).

func (*MCPConnectionDetacher) AttachedSources

func (d *MCPConnectionDetacher) AttachedSources(_ context.Context, owner toolauth.Owner) []string

AttachedSources returns the reconciling OWNER's runtime-added source ids — the owner-scoped reconcile VIEW (Registry.RuntimeAddedSources), NOT the process-global Registry.SourceIDs enumeration. Boot-declared servers stay untagged and every OTHER owner's runtime-adds carry a different owner tag, so neither appears in the view: one owner's run-start reconcile can never detach a boot server or another owner's connection. The registry + catalog stay process-global and deployment-shared (resolution + dispatch by bare name); only the reconcile VIEW is owner-scoped.

func (*MCPConnectionDetacher) Detach

func (d *MCPConnectionDetacher) Detach(ctx context.Context, source string) error

Detach deregisters the source's tools from the catalog (when the catalog supports source deregistration — the optional CatalogSourceDeregisterer companion) and from the MCP registry, closing its transport gracefully. An already-gone source is a no-op (ErrServerNotFound is swallowed — idempotent).

func (*MCPConnectionDetacher) SetOAuthDiscoveryOrigins added in v1.14.0

func (d *MCPConnectionDetacher) SetOAuthDiscoveryOrigins(ctx context.Context, name string, origins []string) (prev []string, err error)

SetOAuthDiscoveryOrigins re-applies a runtime-added connection's OAuth-discovery cross-origin allow-list to the live registry — the run-start allowance-reconcile leg's live effect (the rollback path for the discovery-allowance write). It delegates to the process-global bare-name registry (identity-mandatory for authorization). Satisfies projection.DiscoveryOriginReconciler alongside AttachedSources.

type MuxInput

type MuxInput struct {
	Cfg     *config.Config
	Surface *protocol.ControlSurface
	Bus     events.EventBus
	// Redactor / Logger / Metrics feed every surface's audit + posture legs.
	Redactor audit.Redactor
	Logger   *slog.Logger
	Metrics  *telemetry.MetricsRegistry
	// LLMSnapshot is the resolved snapshot the posture provider projects.
	LLMSnapshot llm.ConfigSnapshot

	// Core subsystem handles.
	Tasks          tasks.TaskRegistry
	Sessions       *sessions.Registry
	Agents         *agentregistry.Registry
	Artifacts      artifacts.ArtifactStore
	Memory         memory.MemoryStore
	Catalog        tools.ToolCatalog
	Coordinator    pauseresume.Coordinator
	MCPRegistry    *mcpdrv.Registry
	MCPToolContext *mcpconsole.ToolContextStore
	State          state.StateStore
	Skills         skills.SkillStore

	// Control-plane handles.
	AgentConfig    agentcfg.Registry
	AgentConfigID  string
	SessionOverlay sessionoverlay.Store
	RunsStore      *runsprotocol.Store
	// RunLoopDriver backs the tasks.get trajectory enricher. Nil leaves
	// tasks reads un-enriched (a stack without a run loop).
	RunLoopDriver  *RunLoopDriver
	OAuthProviders map[string]toolauth.OAuthProvider

	// Governance handles. TenantOverridePolicy backs governance.set/get
	// tenant overrides; KeyRotator backs governance.rotate_key. Both are
	// built caller-side (the policy is shared with the run-loop driver; the
	// rotator's lifecycle is the assembly's).
	TenantOverridePolicy *governance.TenantOverridePolicy
	// SetPosturePolicy backs governance.set_posture — the admin identity-tier
	// policy WRITE surface (the StateStore-backed effective-policy record the
	// posture read layers over). Built caller-side; nil leaves the
	// set_posture route at 501 (the partial-build convention).
	SetPosturePolicy *governance.SetPosturePolicy
	KeyRotator       *llm.ProviderKeyRotator
	ValidModels      []string

	// MCPAttacher backs agent_config.add_mcp_connection. Built caller-side so
	// its Close joins the caller's closer chain; nil leaves the add verb
	// unwired.
	MCPAttacher       agentcfgprotocol.ConnectionAttacher
	MCPStdioAllowlist []string
	BootDeclaredMCP   []string

	// OAuthProviderInstaller backs agent_config.set_oauth_provider /
	// remove_oauth_provider (the Protocol-installed, zero-URL broker-pull
	// provider). Built caller-side; nil leaves the install verbs unwired (→ 501).
	OAuthProviderInstaller agentcfgprotocol.ProviderInstaller
	// BootDeclaredOAuth is the set of boot-declared OAuth provider names
	// (tools.oauth_providers[].name); an install/uninstall of one of these is
	// refused (boot wins).
	BootDeclaredOAuth []string

	// LLMProviderInstaller backs agent_config.set_llm_provider (the
	// Protocol-installed, zero-URL broker-pull inference provider). Built
	// caller-side; nil leaves the verb unwired (→ 501).
	LLMProviderInstaller agentcfgprotocol.LLMProviderInstaller
	// InferenceBrokers is the set of boot-declared inference-broker names
	// (llm.inference_brokers[].name); a set_llm_provider naming a broker outside
	// this set is refused (→ 400 — no admin-writable field determines a sink).
	InferenceBrokers []string

	// Auth. Validator nil mounts the transports WithoutValidator (the
	// explicit test-kit opt-out); AuthSurface nil leaves auth.rotate_token
	// un-mounted (the production posture — no in-runtime token issuer).
	Validator   auth.Validator
	AuthSurface *auth.RotateSurface

	// Posture stamps.
	DisplayName  string
	InstanceID   string
	BuildVersion string
	BuildCommit  string
	// TopologyAvailable advertises the topology.snapshot capability (true
	// only when the caller wired a topology accessor onto the surface).
	TopologyAvailable bool
}

MuxInput carries the assembled subsystem handles and the caller-side posture stamps BuildMux mounts the Protocol surface over. Every handle is optional except the ControlSurface and the bus: a nil handle leaves the corresponding surface un-mounted (the route then 404s), exactly as the two callers already behave under their skip/absence paths.

type OAuthProviderInstaller added in v1.14.0

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

OAuthProviderInstaller installs / uninstalls Protocol-installed providers by building them from the boot credential broker (auth.ProviderBuilder) and installing them owner-tagged into the shared auth.ProviderSet.

Concurrent reuse: builder + set are set once at construction; both are internally synchronised. The installer holds no mutable state.

func NewOAuthProviderInstaller added in v1.14.0

func NewOAuthProviderInstaller(builder *toolauth.ProviderBuilder, set toolauth.ProviderSet) *OAuthProviderInstaller

NewOAuthProviderInstaller builds the production installer. Both the builder and the set are mandatory; a nil either returns a nil installer so the caller leaves the install verbs unwired (→ 501 at the wire edge) rather than nil-panicking.

func (*OAuthProviderInstaller) InstallProvider added in v1.14.0

func (i *OAuthProviderInstaller) InstallProvider(ctx context.Context, tenant, agentID string, desc agentcfg.OAuthProviderDescriptor) error

InstallProvider resolves the descriptor's credential_broker against the boot broker set, builds the broker-pull provider, and installs it owner-tagged. Auth-package errors are wrapped into the agent-config service's client-error sentinels so the wire handler classifies them as 400s (an unknown broker / name collision is a bad request, not a server fault).

func (*OAuthProviderInstaller) InstalledFor added in v1.14.0

func (i *OAuthProviderInstaller) InstalledFor(_ context.Context, owner toolauth.Owner) []string

InstalledFor returns the owner's installed provider names — the owner-scoped reconcile view.

func (*OAuthProviderInstaller) UninstallProvider added in v1.14.0

func (i *OAuthProviderInstaller) UninstallProvider(ctx context.Context, tenant, agentID, name string) error

UninstallProvider removes the named provider from the owner-tagged set and CLOSES it (a still-bound connection's next call then fails loud). The (tenant, agentID) pair is the caller's resolved owner; the set refuses a cross-owner drop at its own boundary (ErrProviderOwnerCollision) — defense in depth independent of the handler's caller-side owner resolution.

type Options

type Options struct {
	ConfigPath string

	// Config, when non-nil, is used directly instead of loading and
	// validating ConfigPath — the entry point for a caller that already
	// holds a validated (or programmatically-built) configuration.
	// Boot re-runs the full-binary Validate on it so a hand-built config
	// cannot bypass validation. When both Config and ConfigPath are set
	// Config wins; when Config is nil ConfigPath is loaded.
	Config *config.Config

	Port            int
	BindAddr        string
	Logger          *slog.Logger
	Stderr          io.Writer
	SubcommandLabel string

	// PreferConfigBindAddr opts into honoring the operator-configured
	// `server.bind_addr` (which may name a non-loopback interface) when no
	// explicit BindAddr override is given. ONLY the production serve caller
	// sets it. The dev/console callers leave it false so they stay
	// loopback-only on 127.0.0.1:<Port> — a dev boot against a serve-shaped
	// yaml must never expose the dev-token stack off-box.
	PreferConfigBindAddr bool

	// AuthValidatorFactory is REQUIRED (nil fails Boot loud).
	AuthValidatorFactory AuthValidatorFactory

	// MCPDefaultIdentity is the identity a boot-time / runtime-added MCP
	// connection dials under.
	MCPDefaultIdentity identity.Identity

	// DisplayName / InstanceID stamp the posture surface's runtime.info.
	DisplayName string
	InstanceID  string
	// BuildVersion / BuildCommit stamp runtime.info's build identity.
	BuildVersion string
	BuildCommit  string
	// DevAllowMock stamps the Serve start log's dev_allow_mock attribute —
	// the dev caller sets it when the mock escape hatch fired so an operator
	// reading the boot line sees the dev-only posture. A stamp only; the mock
	// gate itself is caller policy inside BuildLLMSnapshot.
	DevAllowMock bool

	// RegisterCatalog forwards a compiled-tool registrar onto the
	// assembly's pre-policy catalog seam (assemble.Options.RegisterCatalog):
	// a tool it registers is wrapped with its declared approval / OAuth /
	// policy shell before the run loop can dispatch it. Nil is a no-op —
	// the stock serve caller passes nil; an external serving binary passes
	// its agent's RegisterTools.
	RegisterCatalog func(catalog tools.ToolCatalog) error

	// Caller-side injection seams (all optional).
	BuildLLMSnapshot LLMSnapshotBuilder
	BuildAuthSurface AuthSurfaceBuilder
	ExtraRoutes      ExtraRoutesFunc
	PostBoot         PostBootFunc
}

Options bundles the inputs Boot consumes. ConfigPath (not a pre-loaded config) is carried so a hot-reload supervisor re-reads the file on each reboot by re-calling Boot with the same Options.

type PostBootFunc

type PostBootFunc func(ctx context.Context, h PostBootHandles) error

PostBootFunc runs after the listener + surfaces are composed but before Serve. The dev caller uses it to seed fixture entities.

type PostBootHandles

type PostBootHandles struct {
	Sessions  *sessions.Registry
	Agents    *agentregistry.Registry
	Tasks     tasks.TaskRegistry
	Artifacts artifacts.ArtifactStore
	Memory    memory.MemoryStore
	Tools     tools.ToolCatalog
	Flows     *flow.Registry
	Bus       events.EventBus
	Logger    *slog.Logger
}

PostBootHandles carries the assembled subsystem handles the post-boot hook receives (the dev caller's fixture seeder writes through them).

type RouteMount

type RouteMount struct {
	Router    *http.ServeMux
	Validator auth.Validator
	Bus       events.EventBus
	Redactor  audit.Redactor
	Logger    *slog.Logger
	// BindAddr is the resolved listen address (the bootstrap handler puts it
	// in its connection envelope).
	BindAddr string
}

RouteMount is handed to the ExtraRoutes seam so a caller can mount pre-CORS routes (draft scaffolding, the bootstrap-token endpoint, the Console static build) with access to the boot's validator, bus, and bound address. The caller returns any closers its routes registered (a draft store's Close).

type RunLoopDriver

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

RunLoopDriver subscribes to `task.spawned` and drives a RunLoop per spawned foreground task. The driver is constructed by bootDevStack and Closed during stack teardown.

func NewRunLoopDriver

func NewRunLoopDriver(opts RunLoopDriverOptions) (*RunLoopDriver, error)

NewRunLoopDriver validates the opts and returns a stopped driver. Call Start before serving; call Close to drain.

func (*RunLoopDriver) Close

func (d *RunLoopDriver) Close(_ context.Context) error

Close cancels the subscription, waits for the subscribe-loop to drain, then waits for every in-flight RunLoop goroutine to return. Idempotent: a second Close walks no-ops. The supplied ctx is accepted for the closer-signature compatibility (closeFns takes a ctx); the driver's drain has its own bounded shape (every RunLoop observes d.subCtx cancellation and returns within one drain boundary). A pathological RunLoop that holds ctx-cancellation indefinitely would block Close; the dev cmd's serve loop applies the Server.ShutdownGracePeriod ceiling at the http boundary, so a blocked Close eventually surfaces as a graceless exit.

func (*RunLoopDriver) SessionOverridesStore

func (d *RunLoopDriver) SessionOverridesStore() *runsprotocol.Store

SessionOverridesStore returns the pending-override Store the driver consumes at run start. It is the seam a caller wires the runs.set_overrides service to so a set reaches the run; exposed so a caller can assert the two share one Store.

func (*RunLoopDriver) Start

func (d *RunLoopDriver) Start(ctx context.Context) error

Start opens the admin-scoped subscription and launches the subscribe-loop goroutine. Idempotent: a second Start is a no-op. The supplied ctx anchors the subscription's lifetime — when ctx cancels (e.g. boot was aborted before Close), the subscription cancels along with it.

func (*RunLoopDriver) TrajectoryByTaskID

func (d *RunLoopDriver) TrajectoryByTaskID(taskID tasks.TaskID) *planner.Trajectory

TrajectoryByTaskID returns a defensive SNAPSHOT of a run's trajectory (its Query + a copy of the append-only Steps slice), or nil when the task's trajectory has been evicted or never existed. Safe for an out-of-band Protocol reader (the Enricher on a tasks.get) to call while the run is IN-FLIGHT: the snapshot is taken under the run's per-run trajMu — the SAME lock the steering RunLoop holds around each per-step append — so the returned Steps slice never observes a mid-append slice header (the data race the Enricher would otherwise hit against the run loop). Only the fields the Enricher reads (Query + Steps) are copied; the trajectory's shared maps are deliberately NOT aliased into the snapshot.

type RunLoopDriverOptions

type RunLoopDriverOptions struct {
	Logger   *slog.Logger
	Bus      events.EventBus
	RunLoop  *steering.RunLoop
	Planner  planner.Planner
	Tasks    tasks.TaskRegistry // mandatory: the FSM the driver advances on Run exit
	TaskKind tasks.TaskKind     // KindForeground at V1; the driver spawns RunLoops for this kind

	// driveBackground widens the driver to ALSO
	// drive KindBackground tasks — the ones a planner-emitted SpawnTask
	// creates. False (the default / legacy test path) keeps the
	// foreground-only behaviour. Recursion is bounded at the spawn site
	// by the dev executor's absolute_max_spawn_depth cap, not here.
	DriveBackground bool

	// RunContext consumer wiring. All three of
	// memory / skillsDirectory / planningHints are OPTIONAL: a dev
	// stack that did not open the respective subsystem hands nil; the
	// driver projects the corresponding RunContext field to nil and
	// the planner omits the wrapper. The skills
	// surface is the `skills.Directory` — the bounded,
	// pinned-then-recent, capability-filtered `<skills_context>`
	// producer (the directory carries its own MaxEntries cap; the
	// pre-111d SkillStore.Search + skillsContextMax pair is deleted).
	Memory          memory.MemoryStore
	MemoryRecall    memory.RecallSettings
	SkillsDirectory *skills.Directory
	PlanningHints   *planner.PlanningHints

	// tool dispatch + Catalog projection +
	// Trajectory. The tool catalog is the shared catalog the rest of
	// the dev stack already populated (in-process tools, MCP-discovered
	// tools, etc.). MaxStepsRunLoop caps the runloop's outer step
	// counter (separate from the planner-internal cap that goes onto
	// react via PlannerConfig.MaxSteps).
	Catalog         tools.ToolCatalog
	Executor        steering.ToolExecutor
	MaxStepsRunLoop int

	// operator-declared GrantedScopes
	// threaded into the per-run catalog view's CatalogFilter. Tools
	// whose AuthScopes exceed this set are invisible to the planner.
	// Nil / empty list means no scopes granted (the existing latent
	// default before the plumb-through).
	GrantedScopes []string

	// the artifact store the multimodal
	// materializer reads from. Required only when `task.InputArtifactIDs`
	// is non-empty (text-only tasks never touch the store). A nil
	// store with input artifacts on the task degrades gracefully —
	// the materializer emits text-stub-only references the LLM
	// routes via the catalog.
	ArtifactStore artifacts.ArtifactStore

	// trajectory compression. `tokenBudget`
	// projects onto RunSpec.Base.Budget.TokenBudget (the per-run
	// runtime budget — a run option, never
	// planner state); `compression` is the assembly-built
	// planner.CompressionRunner the runloop invokes at each step
	// boundary when the budget is non-zero. Both zero/nil (the
	// default) = compression off, byte-identical behaviour.
	TokenBudget int
	Compression *planner.CompressionRunner

	// the per-agent attachment disposition policy
	// decoded from `multimodal.disposition` (the middle precedence
	// layer of the disposition resolution). Zero value = no agent
	// policy; the runtime default applies.
	DispositionPolicy planner.DispositionPolicy

	// tenantOverrides resolves an admin-set tenant default for the run's
	// LLM parameters at run start (model / extra-instructions /
	// temperature / max-tokens / reasoning-effort). OPTIONAL — a nil
	// resolver means "no tenant defaults to apply" (the run uses the
	// agent/config defaults). When supplied, the driver reads it ONCE per
	// run and pins the resolved snapshot into the run's RunContext so the
	// swap lands on this run (next-turn relative to the admin's set), never
	// mid-flight.
	TenantOverrides TenantOverrideResolver

	// sessionOverrides is the in-process pending-override Store the
	// `runs.set_overrides` Service writes into. The driver Consumes the
	// session's pending override at run start (one-shot) and composes it
	// OVER the tenant default (session › tenant › config). OPTIONAL — a
	// nil Store means "no session overrides" (the run uses tenant/config
	// only). It is the SAME Store handed to the runs Service so a set and
	// the consume meet.
	SessionOverrides *runsprotocol.Store

	// agentConfig resolves the agent's active config revision at run start
	// (the agent-config control plane). OPTIONAL — a nil registry means
	// "no agent-config projection" (the run uses every directory-visible
	// skill). When supplied with a non-empty agentConfigID, the run reads
	// the active revision ONCE at run start and pins the resolved
	// skills-set into the per-run projection so a config edit applies on
	// the NEXT run (next-turn-only), never mid-flight.
	AgentConfig   agentcfg.Registry
	AgentConfigID string

	// sessionOverlay resolves the SESSION-scoped safe-subset overlay (the
	// non-admin lower tier) at run start: the session's user prompt layer,
	// narrow-only source/tool disables (UNIONED into the admin exclusion set
	// — can only narrow, never widen), and ephemeral personal skills. Keyed
	// by the REAL (tenant, user, session) triple, so it is session-isolated.
	// OPTIONAL — nil means "no session overlay" (the run uses the admin agent
	// config only). The SAME store the session-safe Protocol verbs write into.
	SessionOverlay sessionoverlay.Store

	// runCompletionHook is the static `runtime.hooks.run_completion`
	// projection (nil when unset). At run start the driver resolves the
	// effective hook via the shared projection (agent-config over this yaml
	// over none) and pins it into the RunSpec, so an edit lands next-run.
	RunCompletionHook *steering.CompletionHookSpec

	// connectionDetacher drives the DETACH leg of run-start reconciliation:
	// at run start the driver detaches every MCP server that is attached but
	// no longer declared by the agent's active config revision (a removed
	// connection, or a rollback past an add), so the next run's projected
	// catalog excludes it. OPTIONAL — nil means no reconcile (the
	// backward-compatible path). Injected at the cmd/harbor boundary (it
	// imports the concrete MCP driver; this driver stays driver-agnostic).
	ConnectionDetacher projection.ConnectionDetacher

	// bootDeclaredMCP is the set of boot-declared (yaml) MCP server names the
	// reconcile MUST NEVER detach (they are not revisioned state). Nil/empty
	// when no yaml server is declared.
	BootDeclaredMCP map[string]struct{}

	// OAuthProviderReconciler drives the run-start provider-reconcile leg: it
	// makes the reconciling owner's installed OAuth providers match the current
	// active revision (a rollback past an install uninstalls+closes; a rollback
	// of a removal re-installs). OPTIONAL — nil means no provider reconcile.
	// Owner-scoped exactly like the connection reconcile.
	OAuthProviderReconciler projection.OAuthProviderReconciler

	// namingDefault is the static `runtime.naming` fleet-default auto-naming
	// policy; the driver resolves the effective policy per run (agent-config
	// over this yaml over off). Opt-in, default off.
	NamingDefault config.RuntimeNamingConfig

	// sessionTitler is the session-registry seam the auto-naming trigger
	// writes/reads through (RecordCompletedTurn / AutoNamingState /
	// SetTitleAuto). The same *sessions.Registry the sessions Protocol routes
	// project over. Nil ⇒ no auto-naming trigger even when a policy resolves.
	SessionTitler steering.SessionTitler

	// namingLLM is the run's wrapped LLM client the ONE naming Complete call
	// flows through (governance/safety via ctx identity). Nil ⇒ no auto-naming
	// trigger.
	NamingLLM steering.NamingCompleter
}

RunLoopDriverOptions bundles the dependencies the driver consumes. Bus + RunLoop + Planner + TaskRegistry are all mandatory; a nil any of them returns ErrRunLoopDriverMisconfigured from NewRunLoopDriver. The TaskRegistry is what the driver calls MarkRunning / MarkComplete / MarkFailed on to advance the FSM (closes issue #123).

type SessionEnsurerAdapter

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

SessionEnsurerAdapter wraps a *sessions.Registry as a protocol.SessionEnsurer. Immutable after construction; the wrapped registry is itself concurrency-safe, so the adapter is too.

func NewSessionEnsurerAdapter

func NewSessionEnsurerAdapter(reg *sessions.Registry) *SessionEnsurerAdapter

NewSessionEnsurerAdapter builds the adapter. A nil registry would be a wiring bug; the caller (cmd_dev bootDevStack) always passes the constructed registry.

func (*SessionEnsurerAdapter) EnsureSession

func (a *SessionEnsurerAdapter) EnsureSession(ctx context.Context, ident identity.Identity) error

EnsureSession implements protocol.SessionEnsurer. It calls the registry's create-on-first-use EnsureOpen and translates the registry's sentinels into the protocol-side sentinels so the ControlSurface's mapSessionEnsureError reaches a stable Protocol code. An unclassified error is wrapped (not swallowed) so the surface's catch-all maps it to CodeRuntimeError (CLAUDE.md §5 — fail loud).

type TenantOverrideResolver

type TenantOverrideResolver interface {
	Get(ctx context.Context, tenant string) (governance.TenantOverrideSpec, bool, error)
}

TenantOverrideResolver is the narrow read seam the run loop uses to resolve an admin-set tenant default at run start. The `*governance.TenantOverridePolicy` concrete satisfies it.

Directories

Path Synopsis
Package external is the production-only entry point behind the public sdk/server facade: it turns a validated configuration into a running Harbor Protocol server that an external Go binary can host, with its compiled in-process tools wrapped at the pre-policy catalog seam.
Package external is the production-only entry point behind the public sdk/server facade: it turns a validated configuration into a running Harbor Protocol server that an external Go binary can host, with its compiled in-process tools wrapped at the pre-policy catalog seam.

Jump to

Keyboard shortcuts

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