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; D-204/D-197). 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 (D-273, amending D-205 item 1): 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 ¶
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 ¶
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.
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).
var DefaultMCPIdentity = internal.DefaultMCPIdentity
DefaultMCPIdentity is the fallback transport-event identity for attached MCP servers.
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.
var WithInputArtifacts = internal.WithInputArtifacts
WithInputArtifacts pre-resolves operator-uploaded artifact IDs into the run's first-turn multimodal inputs.
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.
var WithRunID = internal.WithRunID
WithRunID pins the run's RunID instead of synthesising a fresh ULID.
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, D-273).
Types ¶
type Options ¶
Options carries Assemble's injection points (logger, LLM snapshot override, planner override, skip knobs).
type RunOption ¶ added in v1.8.0
RunOption configures a Stack.RunOnce invocation. The functional-option shape keeps RunOnce's signature stable as new per-run knobs land.
type 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).