assemble

package
v1.17.3 Latest Latest
Warning

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

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

Documentation

Overview

Package assemble is the public SDK facade over Harbor's internal/runtime/assemble package — the ONE composition fan-out that turns a validated config into a running headless stack (RFC §3.6, §6.4). Alias-based re-exports only: no behavior lives here. Blank-import sdk/drivers/prod alongside this package so every Open the assembly performs resolves the production driver set; see docs/recipes/embed-harbor-headless.md for the full embedding walkthrough.

runtyped.go — the facade's SECOND documented generic-func carve-out: Go has no generic function values, so RunTyped cannot be expressed as a `var = internal.RunTyped` forward the way every other re-export in this tree is. The wrapper body is exactly one return statement and adds no behavior — the IDENTICAL rationale and shape as the facade's first carve-out, sdk/tools/inproc.RegisterFunc. The no-behavior smoke's func allow-list is the enumerated set of exactly these two.

Example

Example shows the golden one-call path: Assemble composes a headless runtime from a validated config, and Stack.RunOnce turns a goal plus the mandatory (tenant, user, session) identity triple into a terminal answer envelope in a single blocking call.

package main

import (
	"context"
	"fmt"
	"log"

	// Production driver aggregator — the SAME sdk-facade blank import a
	// headless embedder adds (see docs/recipes/embed-harbor-headless.md)
	// so every Open the assembly performs resolves the production driver
	// set. Copyable as-is: it stays inside the sdk facade.

	// Dev-only mock LLM (D-089): explicit opt-in, never in the aggregator,
	// and deliberately has no sdk facade. It is imported here ONLY to keep
	// the example deterministic and offline; a real deployment configures a
	// real provider in harbor.yaml and drops this import.

	"github.com/hurtener/Harbor/sdk/assemble"
	"github.com/hurtener/Harbor/sdk/config"
	_ "github.com/hurtener/Harbor/sdk/drivers/prod"
	"github.com/hurtener/Harbor/sdk/identity"

	_ "github.com/hurtener/Harbor/internal/llm/mock"
)

// mockRunnableConfig builds the canonical baseline configuration and
// points the LLM seam at the deterministic, offline mock driver. It
// keeps the example bodies focused on the one-call run surface.
func mockRunnableConfig() *config.Config {
	cfg := config.Defaults()
	cfg.Telemetry.LogLevel = "error"
	cfg.LLM.Driver = "mock"
	cfg.LLM.Model = "mock/echo"
	cfg.LLM.ModelProfiles = map[string]config.LLMModelProfileConfig{
		"mock/echo": {ContextWindowTokens: 100000, TokenEstimator: "chars_div_4"},
	}
	return cfg
}

func main() {
	ctx := context.Background()

	cfg := mockRunnableConfig()
	stack, err := assemble.Assemble(ctx, cfg, assemble.Options{})
	if err != nil {
		log.Fatalf("assemble: %v", err)
	}
	defer func() { _ = stack.Close(ctx) }()

	id := identity.Identity{TenantID: "acme", UserID: "u-1", SessionID: "s-1"}
	env, err := stack.RunOnce(ctx, "Summarise the deployment status.", id)
	if err != nil {
		fmt.Println("run:", err)
		return
	}

	fmt.Println(env.FinishReason)
}
Output:
goal
Example (RunTyped)

Example_runTyped shows the generic typed embed binding: RunTyped derives a JSON Schema from a Go type (the same reflection-based derivation sdk/tools/inproc.RegisterFunc uses for tool registration), drives a schema-constrained run, and returns the validated answer already unmarshaled into that type — replacing the WithOutputSchema + json.Unmarshal two-liner with one generic call.

package main

import (
	"context"
	"fmt"
	"log"

	// Production driver aggregator — the SAME sdk-facade blank import a
	// headless embedder adds (see docs/recipes/embed-harbor-headless.md)
	// so every Open the assembly performs resolves the production driver
	// set. Copyable as-is: it stays inside the sdk facade.

	// Dev-only mock LLM (D-089): explicit opt-in, never in the aggregator,
	// and deliberately has no sdk facade. It is imported here ONLY to keep
	// the example deterministic and offline; a real deployment configures a
	// real provider in harbor.yaml and drops this import.

	"github.com/hurtener/Harbor/sdk/assemble"
	"github.com/hurtener/Harbor/sdk/config"
	_ "github.com/hurtener/Harbor/sdk/drivers/prod"
	"github.com/hurtener/Harbor/sdk/identity"
	"github.com/hurtener/Harbor/sdk/planner"

	_ "github.com/hurtener/Harbor/internal/llm/mock"
)

// mockRunnableConfig builds the canonical baseline configuration and
// points the LLM seam at the deterministic, offline mock driver. It
// keeps the example bodies focused on the one-call run surface.
func mockRunnableConfig() *config.Config {
	cfg := config.Defaults()
	cfg.Telemetry.LogLevel = "error"
	cfg.LLM.Driver = "mock"
	cfg.LLM.Model = "mock/echo"
	cfg.LLM.ModelProfiles = map[string]config.LLMModelProfileConfig{
		"mock/echo": {ContextWindowTokens: 100000, TokenEstimator: "chars_div_4"},
	}
	return cfg
}

// sentimentReport is the RunTyped target type for Example_runTyped — a
// plain Go struct, no hand-authored JSON Schema required.
type sentimentReport struct {
	Sentiment  string  `json:"sentiment"`
	Confidence float64 `json:"confidence,omitempty"`
}

// fixedFinishPlanner is a deterministic planner.Planner concrete used
// ONLY to keep this example offline and reproducible: it returns a
// fixed terminal Finish payload with no LLM call at all, so
// Example_runTyped needs no network and no scripted-provider ceremony.
// A production RunTyped call rides the SAME schema-derivation +
// validation path against a real generation-steering planner — see
// docs/recipes/embed-harbor-headless.md and examples/embed-runonce/.
type fixedFinishPlanner struct{ payload any }

func (p fixedFinishPlanner) Next(_ context.Context, _ planner.RunContext) (planner.Decision, error) {
	return planner.Finish{Reason: planner.FinishGoal, Payload: p.payload}, nil
}

func main() {
	ctx := context.Background()

	cfg := mockRunnableConfig()
	stack, err := assemble.Assemble(ctx, cfg, assemble.Options{
		PlannerOverride: fixedFinishPlanner{
			payload: map[string]any{"sentiment": "positive", "confidence": 0.9},
		},
	})
	if err != nil {
		log.Fatalf("assemble: %v", err)
	}
	defer func() { _ = stack.Close(ctx) }()

	id := identity.Identity{TenantID: "acme", UserID: "u-1", SessionID: "s-1"}
	report, env, err := assemble.RunTyped[sentimentReport](ctx, stack, "Classify the sentiment.", id)
	if err != nil {
		fmt.Println("run:", err)
		return
	}

	fmt.Println(report.Sentiment, env.FinishReason)
}
Output:
positive goal
Example (Streaming)

Example_streaming shows RunOnce driving the same run while a WithStream sink observes its StreamEvents — token deltas, planner-step boundaries, and tool dispatches — as they occur. RunOnce still blocks and returns the same terminal envelope; the sink fires synchronously on the run goroutine, so every StreamEvent arrives before RunOnce returns. Here the sink reassembles the streamed content tokens, which reproduce the final answer exactly.

package main

import (
	"context"
	"fmt"
	"log"
	"strings"

	// Production driver aggregator — the SAME sdk-facade blank import a
	// headless embedder adds (see docs/recipes/embed-harbor-headless.md)
	// so every Open the assembly performs resolves the production driver
	// set. Copyable as-is: it stays inside the sdk facade.

	// Dev-only mock LLM (D-089): explicit opt-in, never in the aggregator,
	// and deliberately has no sdk facade. It is imported here ONLY to keep
	// the example deterministic and offline; a real deployment configures a
	// real provider in harbor.yaml and drops this import.

	"github.com/hurtener/Harbor/sdk/assemble"
	"github.com/hurtener/Harbor/sdk/config"
	_ "github.com/hurtener/Harbor/sdk/drivers/prod"
	"github.com/hurtener/Harbor/sdk/identity"

	_ "github.com/hurtener/Harbor/internal/llm/mock"
)

// mockRunnableConfig builds the canonical baseline configuration and
// points the LLM seam at the deterministic, offline mock driver. It
// keeps the example bodies focused on the one-call run surface.
func mockRunnableConfig() *config.Config {
	cfg := config.Defaults()
	cfg.Telemetry.LogLevel = "error"
	cfg.LLM.Driver = "mock"
	cfg.LLM.Model = "mock/echo"
	cfg.LLM.ModelProfiles = map[string]config.LLMModelProfileConfig{
		"mock/echo": {ContextWindowTokens: 100000, TokenEstimator: "chars_div_4"},
	}
	return cfg
}

func main() {
	ctx := context.Background()

	cfg := mockRunnableConfig()
	stack, err := assemble.Assemble(ctx, cfg, assemble.Options{})
	if err != nil {
		log.Fatalf("assemble: %v", err)
	}
	defer func() { _ = stack.Close(ctx) }()

	var streamed strings.Builder
	sink := func(e assemble.StreamEvent) {
		// Content deltas only — skip reasoning tokens and non-token events.
		if e.Kind == assemble.StreamToken && !e.Reasoning {
			streamed.WriteString(e.Text)
		}
	}

	id := identity.Identity{TenantID: "acme", UserID: "u-1", SessionID: "s-1"}
	env, err := stack.RunOnce(ctx, "Ping.", id, assemble.WithStream(sink))
	if err != nil {
		fmt.Println("run:", err)
		return
	}

	fmt.Println(streamed.String() == env.Answer)
}
Output:
true

Index

Examples

Constants

View Source
const (
	StreamToken          = internal.StreamToken
	StreamToolDispatched = internal.StreamToolDispatched
	StreamStep           = internal.StreamStep
)

Streaming-sink event kinds: StreamToken (incremental output delta), StreamToolDispatched (a tool was dispatched), StreamStep (a planner-step boundary).

Variables

View Source
var (
	// ErrRunTypedSchemaConflict — a WithOutputSchema option was
	// supplied alongside RunTyped; RunTyped derives its own schema
	// from T.
	ErrRunTypedSchemaConflict = internal.ErrRunTypedSchemaConflict
	// ErrRunTypedUnmarshal — the validated answer payload failed to
	// unmarshal into the target type T.
	ErrRunTypedUnmarshal = internal.ErrRunTypedUnmarshal
)

Re-exported sentinel errors callers compare via errors.Is.

View Source
var Assemble = internal.Assemble

Assemble composes the full dependency-ordered runtime from a validated config, with partial-failure cleanup (a failed Assemble returns the partial Stack so the caller can drain it via Close).

View Source
var DefaultMCPIdentity = internal.DefaultMCPIdentity

DefaultMCPIdentity is the fallback transport-event identity for attached MCP servers.

View Source
var ErrNotRunnable = internal.ErrNotRunnable

ErrNotRunnable is returned by Stack.RunOnce when the stack was assembled without a planner/run loop (no LLM driver, or SkipSteering/SkipRunLoop). Compare via errors.Is.

View Source
var WithInputArtifacts = internal.WithInputArtifacts

WithInputArtifacts pre-resolves operator-uploaded artifact IDs into the run's first-turn multimodal inputs.

View Source
var WithOutputSchema = internal.WithOutputSchema

WithOutputSchema opts a RunOnce call into run-level structured output: the terminal answer is validated against the supplied JSON Schema and delivered as the envelope's additive AnswerPayload key (raw validated JSON). A nil/empty schema is a loud config error. On a schema-invalid answer after the correction budget the runner returns planner.ErrOutputInvalid — never a silent fallback to unvalidated text. Composing with WithStream: assistant-content token deltas are suppressed; the validated answer arrives once, in the envelope.

View Source
var WithRunID = internal.WithRunID

WithRunID pins the run's RunID instead of synthesising a fresh ULID.

View Source
var WithStream = internal.WithStream

WithStream registers a per-run streaming sink on a RunOnce invocation. RunOnce still blocks and returns the terminal answer envelope; the sink receives StreamEvents synchronously on the run goroutine, so every event arrives before RunOnce returns.

Functions

func RunTyped added in v1.9.0

func RunTyped[T any](
	ctx context.Context,
	s *Stack,
	goal string,
	id identity.Identity,
	opts ...RunOption,
) (T, planner.AnswerEnvelope, error)

RunTyped derives a JSON Schema from T (via Harbor's shared Go-type derivation — the same machinery sdk/tools/inproc.RegisterFunc uses for tool registration), drives goal through Stack.RunOnce with that schema applied, and unmarshals the validated answer payload into T.

An unsupported T (a non-empty interface, a channel or function field, a cyclic structure) fails loud at call time, before any run starts. A caller-supplied WithOutputSchema alongside RunTyped is a loud conflict (ErrRunTypedSchemaConflict) — RunTyped owns the schema for the call it drives. A schema-invalid terminal answer surfaces planner.ErrOutputInvalid, exactly as it would from a hand-rolled RunOnce + WithOutputSchema call. A payload that validated against the schema but still failed to unmarshal into T is ErrRunTypedUnmarshal — RunTyped never returns a zero value alongside a nil error.

Thin generic forward over the internal runner; no behavior is added here (CLAUDE.md §13).

Types

type Options

type Options = internal.Options

Options carries Assemble's injection points (logger, LLM snapshot override, planner override, skip knobs).

type RunOption added in v1.8.0

type RunOption = internal.RunOption

RunOption configures a Stack.RunOnce invocation. The functional-option shape keeps RunOnce's signature stable as new per-run knobs land.

type Stack

type Stack = internal.Stack

Stack is the composed runtime bundle Assemble returns (stores, bus, LLM, memory, skills, tasks, catalog, executor, planner, run loop) with reverse-order Close.

type StreamEvent added in v1.8.0

type StreamEvent = internal.StreamEvent

StreamEvent is one observation a WithStream sink receives while RunOnce drives a run — a token delta, a planner-step boundary, or a tool dispatch. Minimal by design: progress signals only, never raw tool arguments or results.

type StreamEventKind added in v1.8.0

type StreamEventKind = internal.StreamEventKind

StreamEventKind is the sealed enum discriminating a StreamEvent (token / tool_dispatched / step).

Jump to

Keyboard shortcuts

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