Recipe: define an in-process tool
Register a plain Go function as a Harbor tool. The in-process driver derives JSON Schemas from your typed input/output structs by reflection and wraps the function in the ToolPolicy reliability shell (timeout + retry + validation) — a plain registration is production-resilient by construction.
The full runnable version of this recipe is examples/tools/weather/.
Steps
Declare typed input and output structs. JSON Schema is derived from the
jsonstruct tags:gotype LookupArgs struct { City string `json:"city"` } type LookupResult struct { City string `json:"city"` TempC float64 `json:"temp_c"` Summary string `json:"summary"` }Write the tool body. It MUST be safe to invoke concurrently (CLAUDE.md §5, D-025) — hold no mutable state, read only from the arguments and
ctx. Honour context cancellation:gofunc lookup(ctx context.Context, in LookupArgs) (LookupResult, error) { if err := ctx.Err(); err != nil { return LookupResult{}, fmt.Errorf("weather.lookup: %w", err) } if in.City == "" { return LookupResult{}, fmt.Errorf("weather.lookup: city is required") } return LookupResult{City: in.City, TempC: 21.0, Summary: "clear skies"}, nil }Register it against a catalog with
inproc.RegisterFunc. The import paths are the publicsdk/facade (RFC §3.6) — they work identically from inside this module and from an external Go module:goimport ( "github.com/hurtener/Harbor/sdk/tools" "github.com/hurtener/Harbor/sdk/tools/inproc" ) cat := tools.NewCatalog() err := inproc.RegisterFunc[LookupArgs, LookupResult]( cat, "weather.lookup", lookup, tools.WithDescription("Look up current weather conditions by city name."), tools.WithTags("example", "weather"), tools.WithSideEffect(tools.SideEffectExternal), )Common
DescriptorOptions (see thesdk/toolsgodoc):WithDescription,WithTags,WithAuthScopes,WithSideEffect,WithExamples,WithCostHint,WithLatencyHint,WithPolicy.Resolve and invoke (this is what a planner does internally):
godesc, ok := cat.Resolve("weather.lookup") // desc.Validate(args) runs the derived schema validator; // desc.Invoke(ctx, args) runs the policy-wrapped function.
Notes
RegisterFuncfails loudly on a duplicate name or an unrepresentable type — never silently. Check the returned error.- Heavy outputs (≥ the configured
heavy_output_threshold_bytes) route through the ArtifactStore automatically (D-022, D-026); your tool returns typed values, not blobs. - HTTP, MCP, and A2A tools use different transport drivers but the same catalog surface; they are wired declaratively from the
toolsblock inharbor.yaml(no Go call site) — seeexamples/harbor.yaml.