assemble

package
v1.3.1 Latest Latest
Warning

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

Go to latest
Published: Jun 11, 2026 License: Apache-2.0 Imports: 34 Imported by: 0

Documentation

Overview

Package assemble is Harbor's assembly entry point (Phase 110d, D-197): the ONE exported, error-returning config→stack fan-out that turns a validated *config.Config into a running runtime stack.

What this package replaces

Before 110d the dependency-ordered composition — stores → bus → llm → memory → skills → tasks → catalog (builtins + OAuth + approval + MCP attach) → sessions → planner → run loop, with reverse-order closers and partial-failure cleanup — existed in exactly two places: `cmd/harbor/cmd_dev.go::bootDevStack` (package main) and `harbortest/devstack`'s unexported `tryAssemble` (gated behind a `*testing.T` wrapper). The two copies had already drifted (the devstack MCP attach silently dropped the Phase 26b ToolPolicy projection; the devstack assembly never constructed cfg-declared OAuth providers). Both callers are now thin wrappers over `Assemble`; there is no second ordering left to drift (brief 01 §5 — one model, no legacy "before" mode).

Scope

Assemble ends where the network surface begins. Protocol surfaces, transports, auth validators, listeners, CORS, the Console, draft stores, and the per-task run-loop *driver* (the `task.spawned` subscriber) stay with the caller — `cmd/harbor` keeps its production driver, `harbortest/devstack` its test-kit mirror, and a headless embedder drives `Stack.RunLoop.Run` directly (see docs/recipes/embed-harbor-headless.md).

Lifecycle contract

On error, Assemble returns the PARTIAL *Stack alongside the error so the caller's deferred Close drains every subsystem that opened before the failure. On success the caller owns the stack and MUST call Close (reverse dependency order, idempotent).

Concurrent reuse (D-025)

The returned *Stack is a compiled artifact: every field is set once during Assemble and never mutated afterwards (Close flips an internal once). The composed subsystems carry their own D-025 concurrent-reuse guarantees; per-run state lives in ctx + planner.RunContext, never on the stack.

Index

Constants

This section is empty.

Variables

View Source
var DefaultMCPIdentity = identity.Identity{TenantID: "dev", UserID: "dev", SessionID: "dev"}

DefaultMCPIdentity is the fallback identity stamped on MCP server-pushed events that arrive without an inflight call when the caller supplies no Options.MCPDefaultIdentity. It matches the `harbor dev` single-operator triple.

Functions

This section is empty.

Types

type Options

type Options struct {
	// Logger receives the pre-telemetry bootstrap warnings (the window
	// before the redactor + bus exist). Once the telemetry Logger is
	// constructed, the assembly threads its Slog() bridge into every
	// subsequently-built subsystem, so subsystem log lines flow
	// through the canonical pipeline (Wave C checkpoint audit).
	// Defaults to slog.Default().
	Logger *slog.Logger

	// LLMSnapshot, when non-nil, overrides the snapshot Assemble would
	// otherwise project from cfg.LLM via llm.SnapshotFromConfig (Phase
	// 110c, D-196). `harbor dev` uses this for the D-089 mock-LLM
	// escape hatch; tests flip the driver without rewriting yaml.
	LLMSnapshot *llm.ConfigSnapshot

	// PlannerOverride, when non-nil, replaces the registry-resolved
	// planner concrete (D-103). Tests inject stub / scripted / pausing
	// planners; production never sets it.
	PlannerOverride planner.Planner

	// SkillStore, when non-nil, wins over the cfg-opened store. The
	// caller owns its lifecycle (it is NOT added to the closer chain).
	SkillStore skills.SkillStore

	// OAuthProviders pre-populates / overrides entries in the provider
	// map the catalog Builder consults. Entries here win over
	// same-named cfg-built providers; the caller owns their lifecycle.
	OAuthProviders map[string]toolauth.OAuthProvider

	// PreRegisterTools is registered on the catalog BEFORE the builtin
	// registration and the Phase 64a Builder apply, so operator config
	// in cfg.Tools.Entries can wrap in-process fixtures.
	PreRegisterTools []tools.ToolDescriptor

	// MCPDefaultIdentity is the transport-event fallback identity for
	// attached MCP servers (Phase 83m Item 1, D-156). Zero value falls
	// back to DefaultMCPIdentity.
	MCPDefaultIdentity identity.Identity

	// MetricsOptions is threaded into telemetry.NewMetricsRegistry.
	// The devstack injects an in-process manual reader so the kit does
	// not require a metrics-exporter driver per test binary.
	MetricsOptions []telemetry.MetricsOption

	// TelemetryOptions is threaded into telemetry.New (Phase 111f,
	// D-203). Tests inject telemetry.WithWriter to observe emitted
	// records; production callers pass nothing (stdout handler).
	TelemetryOptions []telemetry.Option

	// TracerOptions is threaded into telemetry.NewTracer (Phase 111f,
	// D-203). Tests inject telemetry.WithSpanExporter with an
	// in-memory recorder so derived spans are observable without a
	// collector; production callers pass nothing (exporter selection
	// follows cfg.Telemetry.OTelEndpoint — noop without a collector).
	TracerOptions []telemetry.TracerOption

	// ApprovalAuthorizer overrides the resolve-privilege seam threaded
	// into every catalog-built ApprovalGate (Phase 111f, D-203). Nil
	// defaults to the runtime-vocabulary
	// `approval.NewIdentityAuthorizer()` (originating identity /
	// control scope). The serving binary and the devstack inject the
	// Protocol-side `server.NewProtocolScopeAuthorizer` so wire-driven
	// resolution keeps today\'s admin / console:fleet acceptance.
	ApprovalAuthorizer toolapproval.ResolveAuthorizer

	// SkipCatalog disables the tool-catalog band (search cache,
	// builtins, pause Coordinator, OAuth providers, approval gates, MCP
	// attach, executor). Catalog / Coordinator / Gates / OAuthProviders
	// / MCPRegistry / Executor are nil. Test-kit convenience; the
	// production binary never sets it.
	SkipCatalog bool

	// SkipSteering disables the steering Registry + planner + RunLoop
	// band. Steering / Planner / RunLoop are nil. Test-kit convenience.
	SkipSteering bool

	// SkipRunLoop disables only the RunLoop construction (the steering
	// Registry and planner still build). Test-kit convenience.
	SkipRunLoop bool
}

Options carries the injection points the two existing callers need (CLAUDE.md §4.3 / the 110d plan's options-surface rule: the union of what cmd/harbor and harbortest/devstack consume today, not a speculative embedder wishlist).

type Stack

type Stack struct {
	// Cfg is the *config.Config the stack was assembled from.
	Cfg *config.Config

	// Redactor / State / Bus / Metrics / Artifacts / Tasks / Sessions /
	// Agents are always non-nil after a successful Assemble — the
	// runtime's load-bearing core.
	Redactor  audit.Redactor
	State     state.StateStore
	Bus       events.EventBus
	Metrics   *telemetry.MetricsRegistry
	Artifacts artifacts.ArtifactStore
	Tasks     tasks.TaskRegistry
	Sessions  *sessions.Registry
	Agents    *agentregistry.Registry

	// Telemetry is the canonical redactor-mandatory structured Logger
	// (RFC §6.14), constructed with the bus-paired emitter so
	// Logger.Error emits the slog record AND a `runtime.error` bus
	// event. Always non-nil after a successful Assemble (Phase 111f,
	// D-203). The Options.Logger slog logger remains the BOOT logger
	// for the pre-redactor bootstrap window only; the subsystems the
	// assembly constructs after this Logger exists receive its Slog()
	// bridge (Wave C checkpoint audit), and request-scoped emission
	// goes through this Logger directly.
	Telemetry *telemetry.Logger

	// Tracer is the canonical OTel tracer (RFC §6.14). Spans are a
	// derivation of the event bus: the assembly starts
	// telemetry.BridgeBusToTracer alongside the metrics bridge.
	// Always non-nil after a successful Assemble; with no collector
	// configured the noop exporter drops the spans (in-process
	// propagation still works).
	Tracer *telemetry.Tracer

	// RunErrorHandler is the production engine run-error handler
	// (Phase 111f, D-203): it routes the structured engine.RunError
	// through Telemetry.Error so a terminal node failure emits the
	// paired `runtime.error` bus event. Flow composition forwards it
	// via `flow.WithRunErrorHandler(stack.RunErrorHandler)`.
	RunErrorHandler engine.RunErrorHandler

	// LLM / LLMSnapshot are populated when cfg.LLM.Driver (or
	// Options.LLMSnapshot) names a driver. LLMSnapshot is the resolved
	// snapshot the client was opened with — posture surfaces project it.
	LLM         llm.LLMClient
	LLMSnapshot llm.ConfigSnapshot

	// Memory / Skills follow cfg.Memory.Driver / cfg.Skills.Driver
	// (Options.SkillStore wins for Skills).
	Memory memory.MemoryStore
	Skills skills.SkillStore

	// Catalog band (nil under SkipCatalog).
	Catalog        tools.ToolCatalog
	Coordinator    pauseresume.Coordinator
	Gates          map[string]*toolapproval.ApprovalGate
	OAuthProviders map[string]toolauth.OAuthProvider
	MCPRegistry    *mcpdrv.Registry
	Executor       steering.ToolExecutor

	// Steering band (nil under SkipSteering; RunLoop additionally nil
	// under SkipRunLoop or when no planner could be resolved).
	Steering *steering.Registry
	Planner  planner.Planner
	RunLoop  *steering.RunLoop

	// Compression is the trajectory-compression runner (Phase 111e —
	// D-202): planner.NewCompressionRunner over the LLM-backed
	// summarizer.NewTrajectorySummariser. Non-nil only when
	// cfg.Planner.TokenBudget > 0 (and the steering band ran) — the
	// per-task run-loop drivers project it onto RunSpec.Compression
	// alongside Base.Budget.TokenBudget. Nil = compression off.
	Compression *planner.CompressionRunner
	// contains filtered or unexported fields
}

Stack is the composed runtime bundle Assemble returns. Fields are nil when the corresponding layer was skipped via Options or not implied by the cfg (LLM / Memory / Skills follow their cfg blocks).

func Assemble

func Assemble(ctx context.Context, cfg *config.Config, opts Options) (*Stack, error)

Assemble composes the runtime stack from a validated *config.Config. See the package doc for scope, the lifecycle contract (partial stack on error), and what stays with the caller.

func (*Stack) Close

func (s *Stack) Close(ctx context.Context) error

Close runs every subsystem's Close in reverse dependency order and joins the errors. Idempotent: a second Close is a no-op. Safe on a partial stack returned alongside an Assemble error.

Jump to

Keyboard shortcuts

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