tools

package
v1.21.1 Latest Latest
Warning

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

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

Documentation

Overview

agent_provenance.go — the ctx-carried acting-agent provenance seam.

Southbound tool transports (the MCP driver today; the interactive-OAuth and HTTP paths later) stamp the acting agent's registration id alongside the isolation triple so a shared downstream server can attribute a call per agent. This file is the single carrier: the run loop stamps the run's agent-config registration id at run start, and the transport reads it at dispatch.

PROVENANCE, NEVER AN ISOLATION PRINCIPAL. The isolation boundary is and stays (tenant, user, session) — the agent id is attribution metadata only. Nothing Harbor-side keys storage, event filters, memory/state drivers, or token caches by it, and a downstream server MUST NOT treat it as an isolation filter. Absence is valid: a bare embedder run (no agent-config registration) carries no agent id, and the transport simply omits the provenance stamp.

Package tools defines Harbor's unified tool catalog surface — the single planner-addressable concept that hides whether a tool is an in-process Go function, an HTTP endpoint, an MCP server, an A2A remote agent, or a Flow (a typed DAG of Nodes registered as one Tool — see internal/runtime/flow).

The unification is at the **type level** (RFC §6.4): every Tool is the same struct regardless of Source / Transport; the dispatch is one switch in one place. Adding a new transport (HTTP, MCP, A2A) is a ToolProvider driver; nothing else changes.

Identity is mandatory. CatalogFilter keys on the (tenant, user, session) triple plus GrantedScopes; every ToolDescriptor.Invoke reads identity from ctx and stamps it in audit-emitted events (RFC §4).

Reliability shell. Every tool invocation (regardless of Transport) is wrapped in the ToolPolicy shell — timeout, retry-with- exponential-backoff, validation. Defaults fire on a zero-valued ToolPolicy so a plain `tools.RegisterFunc(name, fn)` is production-resilient with no ceremony (RFC §6.4).

Concurrent reuse contract. A constructed *catalog is safe to share across N concurrent goroutines: descriptors are immutable after Register; storage is RWMutex-guarded; per-invocation state lives in the call's ctx + ToolResult, never on the catalog or the Tool itself.

Index

Constants

View Source
const (
	// EventTypeToolInvoked — emitted by the descriptor-wrap shell at the
	// start of every tool invocation (before the wrapped Invoke's
	// validation + policy shell runs). Carries identity + tool name +
	// transport.
	EventTypeToolInvoked events.EventType = "tool.invoked"
	// EventTypeToolCompleted — emitted when the wrapped Invoke returns a
	// ToolResult with no error. Carries identity + tool name + transport
	// + attempts + duration.
	EventTypeToolCompleted events.EventType = "tool.completed"
	// EventTypeToolFailed — emitted on a terminal invocation
	// failure (policy retries exhausted or a permanent class
	// error). Carries identity + tool name + transport + last
	// error class + attempts taken.
	EventTypeToolFailed events.EventType = "tool.failed"
	// EventTypeToolInvalidArgs — emitted when argument validation
	// fails at the catalog edge. NOT a tool error; the planner is
	// expected to reformulate. Carries identity + tool name + the
	// validation error detail.
	EventTypeToolInvalidArgs events.EventType = "tool.invalid_args"
	// EventTypeToolPolicyExhausted — emitted when the policy's
	// retry budget exhausts. (A distinct shape from tool.failed so
	// operators can quantify "tool was unhealthy" vs "tool was
	// permanently broken".)
	EventTypeToolPolicyExhausted events.EventType = "tool.policy_exhausted"
)

tool-side event types. Registered via init() so the canonical events registry stays the single source of truth (see internal/events/events.go).

All five are produced by ONE universal lifecycle-emitting shell the catalog wraps around every registered descriptor's Invoke (see wrapDescriptorLifecycle) — never per-transport-driver — so every dispatch path and every transport (in-process, MCP, HTTP, A2A, flow) produces them by construction, each carrying the full run quadruple on the envelope so the event is turn-attributable. Payloads are content-free: tool NAME + transport + status + attempts + duration only, never args or results (CLAUDE.md §7 rule 7).

Variables

View Source
var (
	// ErrToolNotFound — Resolve returned (_, false); typically the
	// planner asked for a tool name the catalog never registered.
	ErrToolNotFound = errors.New("tools: tool not found")
	// ErrToolInvalidArgs — argument validation failed at the
	// catalog edge. The planner is expected to reformulate via LLM
	// retry feedback; this is NOT a tool error.
	ErrToolInvalidArgs = errors.New("tools: invalid arguments")
	// ErrToolPolicyExhausted — the policy's retry budget was
	// exhausted; the wrapped cause carries the last attempt's
	// failure.
	ErrToolPolicyExhausted = errors.New("tools: policy retries exhausted")
	// ErrToolDuplicateName — Register called with a Name already
	// in the catalog.
	ErrToolDuplicateName = errors.New("tools: duplicate tool name")
)

Sentinel errors. Callers compare via errors.Is.

View Source
var ErrToolExampleInvalid = errors.New("tools: invalid tool example")

ErrToolExampleInvalid — a ToolExample registered on a Tool references an argument key that is not declared in the tool's Tool.ArgsSchema `properties`. The catalog rejects the registration loudly (AGENTS.md §5 fail-loudly): a passing example is a working example, so an example whose `Args` cannot match the schema would teach the planner a shape the catalog edge would then reject.

Functions

func InvokingAgentFrom added in v1.10.0

func InvokingAgentFrom(ctx context.Context) (string, bool)

InvokingAgentFrom returns the acting agent's registration id carried on ctx and a presence bool. The bool is false (and the string empty) when no agent provenance was stamped — the valid, common case for a bare-embedder run. Callers stamp the value as attribution metadata only; it is never an isolation filter (see the package-level note).

func VisibleNames added in v1.3.0

func VisibleNames(cat ToolCatalog, filter CatalogFilter) []string

VisibleNames returns the sorted names of every tool visible under `filter` — both `LoadingAlways` and `LoadingDeferred` modes, so the result is the run's FULL reachable tool set (the prompt-time set plus everything `tool_search` can surface).

this is the ONE producer of the "allowed tools" capability input the skills surface consumes — the builtin `skill_search` / `skill_get` / `skill_list` delegations and the run loop's `skills.Directory.View` call both derive their `CapabilityContext.AllowedTools` / `DirectoryCapability.AllowedTools` from it. Centralised here so the three call sites cannot drift (the SDK friction audit's triple-duplication finding, read forward).

The supplied filter's LoadingModes is OVERRIDDEN to the full two-mode set; every other predicate (identity triple, GrantedScopes, NameRegex) applies as given. A nil catalog returns nil — callers on catalog-less stacks get the empty (default-deny) capability set.

Concurrent reuse: pure function over the catalog's concurrent-safe List; no shared state.

func WithCatalog

func WithCatalog(ctx context.Context, cat ToolCatalog) context.Context

WithCatalog attaches cat to ctx so downstream handlers can recover it via MustCatalog / Catalog.

func WithInvokingAgent added in v1.10.0

func WithInvokingAgent(ctx context.Context, agentID string) context.Context

WithInvokingAgent returns a child ctx carrying agentID as the acting agent's registration id (RFC §6.16 registration identity — provenance, not an isolation principal). An empty agentID is a no-op: the returned ctx carries no provenance, so a bare-embedder run (no agent-config id) never stamps an empty agent id downstream.

Types

type CatalogFilter

type CatalogFilter struct {
	TenantID, UserID, SessionID string
	GrantedScopes               []string
	LoadingModes                []LoadingMode
	NameRegex                   *regexp.Regexp
}

CatalogFilter is the server-enforced subscription predicate on ToolCatalog.List. Keys on the (tenant, user, session) triple plus GrantedScopes — every component participates in visibility:

  • TenantID / UserID / SessionID: typically mandatory at the dispatch boundary, but List is tolerant of empty fields so callers can build admin-view filters (an empty triple matches every tool — see HasFullTriple). Production wiring (later phases Protocol surface) MUST gate the empty-triple case behind an elevated scope.

  • GrantedScopes: a Tool is visible only if every entry in its AuthScopes is contained in GrantedScopes. Tools with no AuthScopes (the in-process default) are always visible.

  • LoadingModes: defaults to LoadingAlways when empty (the prompt-time view); pass [LoadingAlways, LoadingDeferred] for a full-discovery view.

  • NameRegex: optional final filter for operator queries; nil matches every tool.

func (CatalogFilter) HasFullTriple

func (f CatalogFilter) HasFullTriple() bool

HasFullTriple reports whether the filter has all three identity components. The Protocol surface uses this to reject empty- triple non-admin reads.

type CatalogOption

type CatalogOption func(*catalogConfig)

CatalogOption configures a catalog at construction.

func WithCatalogBus added in v1.13.0

func WithCatalogBus(bus events.EventBus) CatalogOption

WithCatalogBus wires the canonical event bus into the catalog so Register wraps every registered descriptor's Invoke in the universal tool-lifecycle-emitting shell. This is the single seam where tool lifecycle events (tool.invoked / .completed / .failed / .invalid_args / .policy_exhausted) are produced — every dispatch path that resolves and invokes a descriptor inherits them by construction, for every transport. A nil bus is the same as not supplying the option (no shell). Production wiring supplies the runtime's bus.

func WithSearchCache added in v1.2.0

func WithSearchCache(sc ToolSearchCache) CatalogOption

WithSearchCache attaches a search index to the catalog ( ).

type CatalogReplacer

type CatalogReplacer interface {
	// Replace atomically swaps each named descriptor in `wrapped`
	// with its wrapped version. Returns ErrToolNotFound (wrapped)
	// when any name does not resolve.
	Replace(wrapped []ToolDescriptor) error
}

CatalogReplacer is the optional surface a ToolCatalog exposes when it supports atomic per-tool descriptor replacement at boot. The catalog wiring (internal/tools/catalog.Builder) uses this to install wrapped descriptors over previously-registered names without a Deregister+Register round trip (which would race concurrent Resolve calls).

Replacement semantics:

  • Each descriptor in `wrapped` MUST name an already-registered Tool (the builder enforces this via Resolve BEFORE calling Replace). Otherwise Replace returns ErrToolNotFound naming the offending tool.
  • Replacement is atomic from the catalog's external view: a concurrent Resolve sees EITHER every old descriptor OR every new descriptor, never a half-applied mix.
  • Descriptors NOT named in `wrapped` are untouched.

A catalog implementation that does not support replacement skips this interface. Callers branch on the type assertion; the absence is the signal.

type CatalogSourceDeregisterer added in v1.11.0

type CatalogSourceDeregisterer interface {
	// DeregisterSource removes every tool whose Source == source and returns
	// the number removed.
	DeregisterSource(source ToolSourceID) int
}

CatalogSourceDeregisterer is the optional surface a ToolCatalog exposes when it supports removing every tool registered under one source id. The detach-on-reconcile run-start step (agent_config.remove_mcp_connection and rollback-past-add) uses it to prune a no-longer-declared MCP server's tools from the catalog so the next run's projected catalog excludes them — the physical inverse of the boot-time per-source Register loop.

Removal semantics:

  • DeregisterSource removes every descriptor whose Tool.Source equals source and returns the count removed.
  • Removal is atomic from the catalog's external view: a concurrent Resolve / List sees either the full set or the pruned set.
  • A source with no registered tools removes nothing (returns 0) and is a no-op — idempotent across repeated deregisters of the same source.

A catalog implementation that does not support source deregistration skips this interface; callers branch on the type assertion (the absence is the signal), matching CatalogReplacer.

type DescriptorOption

type DescriptorOption func(*descriptorConfig)

DescriptorOption configures a ToolDescriptor at registration. Drivers (in-process, HTTP / MCP / A2A) accept `opts ...DescriptorOption` so the same option surface is reused across transports.

func WithAuthScopes

func WithAuthScopes(scopes ...string) DescriptorOption

WithAuthScopes adds required auth scopes — the CatalogFilter MUST grant every scope listed here for the Tool to be visible.

func WithCostHint

func WithCostHint(s string) DescriptorOption

WithCostHint sets a free-form cost annotation.

func WithDescription

func WithDescription(s string) DescriptorOption

WithDescription overrides the Tool's planner-facing description. Default is the function name when registering via RegisterFunc.

func WithExamples

func WithExamples(examples ...ToolExample) DescriptorOption

WithExamples attaches canonical argument-shape examples surfaced to the planner.

func WithLatencyHint

func WithLatencyHint(d time.Duration) DescriptorOption

WithLatencyHint sets a non-authoritative latency annotation.

func WithLoading

func WithLoading(m LoadingMode) DescriptorOption

WithLoading overrides LoadingMode (default: LoadingAlways).

func WithPolicy

func WithPolicy(p ToolPolicy) DescriptorOption

WithPolicy overrides the ToolPolicy applied to the registered Tool. Pass tools.ToolPolicy{} (the zero value) to opt back into DefaultPolicy().

func WithSafetyNotes

func WithSafetyNotes(s string) DescriptorOption

WithSafetyNotes attaches operator-supplied safety text surfaced to the planner.

func WithSideEffect

func WithSideEffect(s SideEffect) DescriptorOption

WithSideEffect declares the tool's side-effect class.

func WithSource

func WithSource(id ToolSourceID) DescriptorOption

WithSource overrides the descriptor's ToolSourceID (for provider-driven registrations).

func WithTags

func WithTags(tags ...string) DescriptorOption

WithTags adds operator-facing tags.

type ErrInsufficientScope added in v1.16.0

type ErrInsufficientScope struct {
	// Source is the tool source (MCP connection) that answered the challenge.
	Source ToolSourceID
	// ToolName is the server-side tool name the call targeted.
	ToolName string
	// DownstreamResource is the origin/host the challenge came from.
	DownstreamResource string
	// RequiredScopes is the parsed `scope` challenge parameter — the scopes
	// the downstream demands.
	RequiredScopes []string
	// GrantedScopes is the binding's most-recently-granted scope set (from
	// the resolved token). Empty when unknown.
	GrantedScopes []string
	// WWWAuthenticate is the verbatim challenge header value.
	WWWAuthenticate string
	// Origin is the scheme://host[:port] the challenge was observed on.
	Origin string
}

ErrInsufficientScope is the typed, structured signal for a downstream step-up scope shortfall: a `403` answered with a `WWW-Authenticate` challenge carrying `error="insufficient_scope"` (RFC 6750 §3.1). It is constructed ONLY when the challenge is explicitly marked insufficient_scope — an unmarked 403 stays an opaque transport error, so no false-positive structured signal is raised on a server that just returns bare 403s.

Report-not-act: constructing this value never escalates, re-consents, or widens a binding. It is inert data for the coordinator to act on via the existing operator-driven discovery-allowance path — the runtime never auto-follows a shortfall. Classification treats it as permanent (ClassifyError), so the reliability shell stops retrying a shortfall retrying can never fix.

func (*ErrInsufficientScope) Error added in v1.16.0

func (e *ErrInsufficientScope) Error() string

Error implements error.

func (*ErrInsufficientScope) ShortfallDetail added in v1.16.0

func (e *ErrInsufficientScope) ShortfallDetail() ScopeShortfallDetail

ShortfallDetail projects the structured shortfall onto the wire-safe ScopeShortfallDetail carried on tool.failed. It never carries token bytes.

type ErrorClass

type ErrorClass string

ErrorClass categorises a tool invocation failure. The policy's RetryOn allowlist references these classes; the shell's classifier maps Go errors to a class on each failed attempt.

const (
	// ErrClassTransient covers retryable infrastructure issues —
	// network blips, "connection reset", "EOF", etc. The shell's
	// classifier maps common error strings here.
	ErrClassTransient ErrorClass = "transient"
	// ErrClassTimeout — the per-attempt context.DeadlineExceeded
	// or the tool's own timeout signal.
	ErrClassTimeout ErrorClass = "timeout"
	// ErrClass5xx — HTTP / RPC server-side 5xx (transient-side
	// failures distinct from network errors).
	ErrClass5xx ErrorClass = "5xx"
	// ErrClassPermanent — non-retryable. A tool that returns a
	// 4xx, ErrToolInvalidArgs, or a context.Canceled gets this
	// class; the shell stops retrying immediately.
	ErrClassPermanent ErrorClass = "permanent"
)

func ClassifyError

func ClassifyError(err error, perAttemptTimeout bool) ErrorClass

ClassifyError maps a Go error to an ErrorClass. Used by the shell to decide retryability. Public so drivers can return a pre-classified error if they want to bypass the default heuristics.

Heuristics (V1):

  • context.DeadlineExceeded + perAttemptTimeout: ErrClassTimeout (per-attempt timeout fired).
  • context.DeadlineExceeded without perAttemptTimeout: parent ctx died → ErrClassPermanent (caller-driven).
  • context.Canceled: ErrClassPermanent.
  • ErrToolInvalidArgs / ErrToolNotFound wrapped: ErrClassPermanent.
  • HTTP-status-shaped error strings ("status 5xx", "500", "503"): ErrClass5xx.
  • "timeout" / "deadline exceeded" / "context canceled" string match in error: ErrClassTimeout.
  • everything else: ErrClassTransient (the conservative default so tools opt OUT of retry rather than opting in).

type ExclusionView added in v1.5.0

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

ExclusionView wraps a PlannerCatalogView, hiding any tool whose Source is a paused MCP server or whose Name is individually disabled — the run-start projection of the agent-config tool-exposure desired state.

Exclusion is projection-time only and applies NEXT-turn: the live MCP transport stays WARM while a server is paused (resume is a flag flip, not a re-dial). A tool hidden here is absent from BOTH List (the `<available_tools>` prompt section) and Resolve (so the planner cannot re-invoke a remembered name), making the exclusion the planner's view of the run's snapshot.

Concurrent reuse: ExclusionView is a value type with read-only fields (the wrapped view + two immutable lookup sets). Each run constructs its own via NewExclusionView; DO NOT cache across runs (the exclusion sets derive from the run's resolved active revision).

func NewExclusionView added in v1.5.0

func NewExclusionView(inner PlannerCatalogView, pausedSources, disabledNames []string) ExclusionView

NewExclusionView wraps inner so tools whose Source is in pausedSources or whose Name is in disabledNames are hidden. The two slices are copied into lookup sets so the caller's backing arrays cannot mutate the view after construction. A view with both sets empty still wraps inner faithfully (it just hides nothing) — the projection only wraps when at least one set is non-empty, but the constructor is robust either way.

func (ExclusionView) List added in v1.5.0

func (v ExclusionView) List() []Tool

List implements PlannerCatalogView. Returns the inner view's tools minus the excluded ones; the slice is always freshly allocated.

func (ExclusionView) Resolve added in v1.5.0

func (v ExclusionView) Resolve(name string) (Tool, bool)

Resolve implements PlannerCatalogView. An excluded tool resolves as absent (found=false), so the planner cannot re-invoke a paused/disabled name from memory.

type InvokeHooks

type InvokeHooks struct {
	// OnAttempt fires after each invoke attempt with the attempt
	// index (0-based: 0 is the initial call, 1 is the first
	// retry) and the attempt's error (nil on success).
	OnAttempt func(attempt int, err error)
}

InvokeHooks lets drivers observe per-attempt outcomes (e.g. to emit per-attempt audit events) without coupling the policy shell to the events package.

type LoadingMode

type LoadingMode string

LoadingMode controls when a Tool appears in the planner's prompt- time catalog. Always-loaded tools live in every planner step; Deferred tools are loaded on demand (lazy resolution).

const (
	LoadingAlways   LoadingMode = "always"
	LoadingDeferred LoadingMode = "deferred"
)

The LoadingMode values.

type LoadingOverrideView added in v1.10.0

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

LoadingOverrideView wraps a PlannerCatalogView, rewriting each tool's LoadingMode from a per-run resolved effective-mode map and re-applying the prompt-time loading predicate against that EFFECTIVE mode — the run-start projection of the agent-config loading-mode override. Sibling of ExclusionView: the two compose independently — LoadingOverrideView changes prompt PRESENCE only, ExclusionView removes CAPABILITY.

List() keeps only tools whose effective mode is in the constructor's visibleModes set (defaulting to LoadingAlways, mirroring CatalogFilter's default), and rewrites Tool.Loading on every returned tool. Resolve() NEVER filters on loading — matching PlannerView.Resolve — so a tool overridden to LoadingDeferred stays reachable through the two-turn tool_search discovery cycle; it still rewrites the returned Tool's Loading to the effective mode.

Concurrent reuse: LoadingOverrideView is a value type with read-only fields (the wrapped view + two immutable lookup maps built at construction). Each run constructs its own via NewLoadingOverrideView; DO NOT cache across runs — the effective map derives from the run's resolved active config revision (the concurrent-reuse contract).

func NewLoadingOverrideView added in v1.10.0

func NewLoadingOverrideView(inner PlannerCatalogView, effective map[string]LoadingMode, visibleModes []LoadingMode) LoadingOverrideView

NewLoadingOverrideView wraps inner, rewriting each resolved Tool's Loading via effective (keyed by the tool's full catalog Name). A name absent from effective keeps the Loading value inner reports (the boot-effective mode) — callers pass an effective map containing ONLY the names an override actually changes, not every catalog entry. List() filters to visibleModes (an empty slice defaults to LoadingAlways, mirroring CatalogFilter's default prompt-time view). Both effective and visibleModes are copied so the caller's backing storage cannot mutate the view after construction.

inner is expected to expose BOTH loading modes (its own CatalogFilter should carry LoadingModes: [LoadingAlways, LoadingDeferred]) — this view performs the loading-based filtering itself, against the EFFECTIVE mode, not inner's boot-effective one.

func (LoadingOverrideView) List added in v1.10.0

func (v LoadingOverrideView) List() []Tool

List implements PlannerCatalogView. Returns inner's tools rewritten to their effective mode, filtered to the constructor's visibleModes set; the slice is always freshly allocated.

func (LoadingOverrideView) Resolve added in v1.10.0

func (v LoadingOverrideView) Resolve(name string) (Tool, bool)

Resolve implements PlannerCatalogView. Never filters on loading (matching PlannerView.Resolve) — the two-turn tool_search discovery cycle depends on a deferred-overridden tool staying resolvable by name. Rewrites the returned Tool's Loading to its effective mode.

type PlannerCatalogView added in v1.5.0

type PlannerCatalogView interface {
	// Resolve returns the schema-only Tool by name and a presence bool.
	Resolve(name string) (Tool, bool)
	// List returns every Tool visible to the run.
	List() []Tool
}

PlannerCatalogView is the planner-facing, schema-only catalog view contract (Resolve + List over Tool). Both PlannerView and ExclusionView satisfy it; the run-loop driver assigns the value it builds to the planner's structurally-identical `ToolCatalogView`. The interface lives here (not in `internal/planner`) so the agent-config run-start projection can compose views without importing `planner`.

type PlannerView added in v1.3.0

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

PlannerView is the planner-facing, schema-only projection of a ToolCatalog under one run's identity scope ( originally as a `cmd/harbor` adapter).

`planner.RunContext.Catalog` is the surface that renders into the `<available_tools>` prompt section. PlannerView wraps the production catalog + a per-run visibility filter (keyed on the run's identity triple + any GrantedScopes the operator declared). The planner sees the FILTERED set; the catalog's internal store is immutable from the planner's perspective (the planner only reads), and Resolve returns the schema-only Tool value — never the dispatch-side ToolDescriptor.

PlannerView satisfies `planner.ToolCatalogView` STRUCTURALLY (Resolve(name) (Tool, bool) + List() []Tool). This package cannot name that interface — `internal/planner` imports `internal/tools` (ToolCatalogView's methods return Tool) — so the compile-time assertion lives in `internal/planner`'s tests, where the import is legal.

Per-run construction discipline: the view is a value type with two read-only fields. Each run constructs its own view via NewPlannerView — the filter depends on the run's identity. Sharing one view across runs would cross-contaminate visibility across tenants / users / sessions — DO NOT cache.

func NewPlannerView added in v1.3.0

func NewPlannerView(cat ToolCatalog, filter CatalogFilter) PlannerView

NewPlannerView constructs the per-run view over `cat` with `filter`'s visibility predicate. The filter's GrantedScopes is the operator-configured `tools.granted_scopes` list (a scoped ): tools whose declared AuthScopes are entirely contained in the granted set are visible; tools that require a missing scope are filtered out. An empty / nil GrantedScopes keeps the "no scopes granted" default — tools with AuthScopes are invisible to the planner; tools without AuthScopes are always visible (the standard CatalogFilter rule). The GrantedScopes slice is copied so the caller's backing array cannot mutate the view after construction.

func (PlannerView) List added in v1.3.0

func (v PlannerView) List() []Tool

List implements the planner's ToolCatalogView contract. Returns the filtered slice of Tools visible to the run's identity.

func (PlannerView) Resolve added in v1.3.0

func (v PlannerView) Resolve(name string) (Tool, bool)

Resolve implements the planner's ToolCatalogView contract. Returns the schema-only Tool value the planner uses to build a CallTool decision — never the dispatch-side ToolDescriptor.

type ResolvedDescriptorConfig

type ResolvedDescriptorConfig struct {
	Policy      ToolPolicy
	Description string
	Tags        []string
	AuthScopes  []string
	Examples    []ToolExample
	SideEffect  SideEffect
	Loading     LoadingMode
	CostHint    string
	LatencyHint time.Duration
	SafetyNotes string
	Source      ToolSourceID
}

ResolvedDescriptorConfig is the resolved option set after applying DescriptorOptions. Drivers consume this when building their ToolDescriptor — they read what the operator declared at registration time + the package-set defaults.

Exported so transports (HTTP, MCP, the A2A) can reuse the option-resolution pipeline without re-implementing it. The fields mirror the unexported descriptorConfig in this file.

func ResolveOptions

func ResolveOptions(opts ...DescriptorOption) ResolvedDescriptorConfig

ResolveOptions applies opts to a fresh ResolvedDescriptorConfig + sets per-driver defaults the inproc registrar (and future drivers) consume.

type ScopeShortfallDetail added in v1.16.0

type ScopeShortfallDetail struct {
	DownstreamResource string
	RequiredScopes     []string
	GrantedScopes      []string
	WWWAuthenticate    string
	Origin             string
}

ScopeShortfallDetail is the wire-safe (SafePayload) projection of ErrInsufficientScope carried on ToolFailedPayload. Every field is operator/server-supplied metadata — never a token or an arg value.

type SideEffect

type SideEffect string

SideEffect is the declared side-effect class. Operators reason about which tools are safe to retry from this field; the policy's retry behaviour is orthogonal (a tool with SideEffectWrite may still have RetryOn = []ErrorClass{ErrClassTransient} if the caller knows the write is idempotent).

const (
	SideEffectPure     SideEffect = "pure"
	SideEffectRead     SideEffect = "read"
	SideEffectWrite    SideEffect = "write"
	SideEffectExternal SideEffect = "external"
	SideEffectStateful SideEffect = "stateful"
)

The SideEffect classes, ordered from safest to most consequential.

type Tool

type Tool struct {
	// Name is the unique catalog key. Two descriptors with the same
	// Name return ErrToolDuplicateName from Register.
	Name string
	// Description is the planner-facing summary. Surfaced in the
	// prompt-time catalog so the LLM can choose between tools.
	Description string
	// ArgsSchema is the JSON-Schema (object) describing the tool's
	// argument shape. Catalogs validate against this before dispatch
	// — failures yield ErrToolInvalidArgs.
	ArgsSchema json.RawMessage
	// OutSchema is the JSON-Schema (object) describing the tool's
	// result shape. Used for output validation when policy enables it.
	OutSchema json.RawMessage
	// SideEffects classifies the tool's effect domain — operators
	// gate which classes are safe to retry / parallelize.
	SideEffects SideEffect
	// Tags allow operators to filter / categorise tools.
	Tags []string
	// AuthScopes lists the scopes a planner step's identity MUST
	// carry (via CatalogFilter.GrantedScopes) for the Tool to be
	// visible. Empty = no scope requirement.
	AuthScopes []string
	// CostHint is free-form (cheap / normal / expensive) and
	// non-authoritative — Governance owns real cost gates.
	CostHint string
	// LatencyHint is non-authoritative; surfaced for prompt ordering.
	LatencyHint time.Duration
	// SafetyNotes is free-form text the planner sees alongside
	// Description; used for "this tool writes to production" hints.
	SafetyNotes string
	// Loading mode: Always (visible in every planner step) or
	// Deferred (loaded on demand).
	Loading LoadingMode
	// Examples surface canonical argument shapes to the planner.
	Examples []ToolExample
	// Source identifies the provider (empty for in-process tools).
	Source ToolSourceID
	// Transport discriminates the tool's source. Determines which
	// driver's Invoke implementation runs.
	Transport TransportKind
	// Policy is the reliability shell wrapping every invocation.
	// Zero value → DefaultPolicy is applied at dispatch time.
	Policy ToolPolicy
	// HandlesMIME declares the MIME types this tool can consume from
	// the artifact store. Used by the runtime's
	// multimodal materializer to populate `ArtifactStub.Fetch.Tool`
	// when an operator-uploaded input artifact's MIME matches — giving
	// the planner an explicit "use this tool for this ref" hint
	// instead of forcing the LLM to discover the binding from the
	// catalog. Wildcards are supported (`image/*`, `audio/*`); empty
	// means the tool advertises no MIME affinity (the LLM still picks
	// it from the catalog by description).
	HandlesMIME []string
	// Form classifies the descriptor shape (tool / resource / prompt).
	// Additive, classification-only (never a dispatch or isolation input);
	// zero value is ToolFormTool. Stamped by the MCP driver at build time
	// for its resource/prompt wrappers; every other driver leaves it
	// zero-valued. See ToolForm.
	Form ToolForm
}

Tool is Harbor's planner-addressable unit. Same struct regardless of Transport. The planner reasons about this; the dispatcher uses the matching ToolDescriptor.

Concurrent reuse: Tool is a value type; all fields are read-only after Register. The Tool itself never carries per-invocation state.

func (Tool) MatchesMIME

func (t Tool) MatchesMIME(mime string) bool

MatchesMIME reports whether the tool's HandlesMIME declaration covers the supplied MIME type. Wildcards on the type half are supported (`image/*` matches `image/png`, `image/webp`, etc.). used by the runtime materializer when populating `ArtifactStub.Fetch.Tool`.

type ToolCatalog

type ToolCatalog interface {
	// Register adds a descriptor. Returns ErrToolDuplicateName when
	// the Tool's Name is already registered.
	Register(d ToolDescriptor) error
	// Resolve returns the descriptor for name. found=false when
	// absent; the caller compares against the boolean (no error).
	Resolve(name string) (ToolDescriptor, bool)
	// List returns Tool *views* (never ToolDescriptors) matching
	// filter. The slice is owned by the caller; mutations on it do
	// not affect the catalog.
	List(filter CatalogFilter) []Tool
	// Search runs a full-text + tag-filter scan
	// over the catalog's search index. Returns an empty slice when no
	// SearchCache is attached (honest "discovery unavailable").
	Search(ctx context.Context, query string, tags []string, limit int) []Tool
}

ToolCatalog is Harbor's planner-addressable registry. Three methods. Concrete V1 implementation is the in-memory catalog (catalog.go); future drivers (remote-catalog, persistent-catalog) plug in behind the interface.

Concurrent reuse: implementations MUST be safe for N concurrent goroutines. The in-memory catalog uses a single RWMutex; descriptors are immutable after Register.

func Catalog

func Catalog(ctx context.Context) (ToolCatalog, bool)

Catalog returns the ToolCatalog in ctx and a presence bool. Use when absence is recoverable.

func MustCatalog

func MustCatalog(ctx context.Context) ToolCatalog

MustCatalog returns the ToolCatalog in ctx. Panics with ErrToolNotFound (used as the sentinel for "no catalog configured") when absent. Use in handler/runtime paths where a catalog is mandatory.

func NewCatalog

func NewCatalog(opts ...CatalogOption) ToolCatalog

NewCatalog constructs the canonical in-memory ToolCatalog. Safe for N concurrent goroutines after construction. The catalog is empty; callers register descriptors via Register.

`opts` is reserved for future configuration; Harbor ships without options but the variadic surface is stable so future fields land without breaking signatures.

type ToolCompletedPayload

type ToolCompletedPayload struct {
	events.SafeSealed
	Identity   identity.Quadruple
	ToolName   string
	Transport  TransportKind
	Attempts   int
	DurationMS int64
}

ToolCompletedPayload is the typed payload for EventTypeToolCompleted. SafePayload.

type ToolDescriptor

type ToolDescriptor struct {
	Tool     Tool
	Invoke   func(ctx context.Context, args json.RawMessage) (ToolResult, error)
	Validate func(args json.RawMessage) error
}

ToolDescriptor is the callable binding produced by a driver. The planner sees Tool; the dispatcher uses ToolDescriptor.

Invoke is wrapped by the ToolPolicy shell at dispatch time — the descriptor's Invoke is the inner-most function, called once per retry attempt by the shell. Drivers populate Invoke with the transport-specific code; the shell handles timeout / retry / backoff / validation uniformly.

Validate is the cached compiled JSON-Schema validator. Drivers build it once at Register; the shell calls it once before the first Invoke attempt (validation BEFORE retry — failing args don't get retried).

type ToolExample

type ToolExample struct {
	Args        map[string]any
	Description string
	Tags        []string
}

ToolExample is a usage example surfaced to the planner. The `Args` map is JSON-marshalled into the prompt.

type ToolFailedPayload

type ToolFailedPayload struct {
	events.SafeSealed
	Identity     identity.Quadruple
	ToolName     string
	Transport    TransportKind
	Attempts     int
	ErrorClass   ErrorClass
	ErrorMessage string
	// ScopeShortfall carries the structured downstream insufficient-scope
	// step-up when the terminal failure was an *ErrInsufficientScope. Nil for
	// every other failure. Additive: it enriches the SAME tool.failed event
	// every transport already emits, never a new event type. SafePayload —
	// server/operator-supplied challenge metadata, never a token.
	ScopeShortfall *ScopeShortfallDetail `json:",omitempty"`
}

ToolFailedPayload is the typed payload for EventTypeToolFailed. SafePayload: ErrorClass is enum-shaped; ErrorMessage is operator-controlled (or wraps a sentinel).

type ToolForm added in v1.10.0

type ToolForm string

ToolForm classifies a Tool's descriptor shape — a callable TOOL versus a driver-wrapped resource/prompt. Additive; classification only — NEVER a dispatch or isolation input. It exists so cross-cutting policy (the agent-config per-server loading-mode override) can distinguish callable tools from wrapped MCP resources/prompts without sniffing a driver's internal name conventions across the §4.4 seam.

const (
	// ToolFormTool is a callable tool descriptor (the zero value / default).
	ToolFormTool ToolForm = ""
	// ToolFormResource is a driver-wrapped resource (e.g. the MCP driver's
	// one-shot `read_resource`-style descriptor).
	ToolFormResource ToolForm = "resource"
	// ToolFormPrompt is a driver-wrapped prompt (e.g. the MCP driver's
	// one-shot `get_prompt`-style descriptor).
	ToolFormPrompt ToolForm = "prompt"
)

The ToolForm values. ToolFormTool is the zero value — every driver that never stamps Form (in-process, HTTP, A2A, and the MCP driver's own tool-form descriptors) classifies as a callable tool by default.

type ToolInvalidArgsPayload

type ToolInvalidArgsPayload struct {
	events.SafeSealed
	Identity        identity.Quadruple
	ToolName        string
	Transport       TransportKind
	ValidationError string
}

ToolInvalidArgsPayload is the typed payload for EventTypeToolInvalidArgs. SafePayload: the validation error describes the schema mismatch (e.g. "expected string, got int"), not the offending arg value. Producers MUST NOT include raw arg bytes here — those go through the audit redactor.

type ToolInvokedPayload

type ToolInvokedPayload struct {
	events.SafeSealed
	Identity  identity.Quadruple
	ToolName  string
	Transport TransportKind
	StartedAt time.Time
}

ToolInvokedPayload is the typed payload for EventTypeToolInvoked. SafePayload: carries no secret-shaped data (the tool name and transport are operator-supplied identifiers, not user input).

type ToolPolicy

type ToolPolicy struct {
	// TimeoutMS is the per-attempt deadline in milliseconds. 0
	// means "use DefaultPolicy.TimeoutMS (30000)". When the policy
	// is overridden via WithPolicy, 0 means "no per-attempt
	// timeout" (the ctx's deadline still applies).
	TimeoutMS int
	// MaxRetries is the count of retry attempts AFTER the initial
	// invocation. Total invocations = MaxRetries + 1. 0 (zero
	// value) means "use DefaultPolicy.MaxRetries (3)".
	MaxRetries int
	// BackoffBase is the first-retry sleep before retry attempt 1.
	// Subsequent retries multiply by BackoffMult, capped at
	// BackoffMax. 0 means "use DefaultPolicy.BackoffBase (100ms)".
	BackoffBase time.Duration
	// BackoffMult is the multiplier between successive retry
	// sleeps. 0 means "use DefaultPolicy.BackoffMult (2)".
	BackoffMult float64
	// BackoffMax caps the per-retry sleep regardless of mult. 0
	// means "use DefaultPolicy.BackoffMax (30s)".
	BackoffMax time.Duration
	// RetryOn lists which ErrorClasses are retryable. Empty (zero
	// value) means "use DefaultPolicy.RetryOn ([transient, timeout,
	// 5xx])". An empty non-nil slice (explicitly set to []) means
	// "retry on nothing" (one attempt only).
	RetryOn []ErrorClass
	// Validate selects which side(s) of the invocation the shell
	// validates. Zero value means "use DefaultPolicy.Validate
	// (ValidateBoth)".
	Validate ValidateMode
}

ToolPolicy is the per-tool reliability shell. Every invocation regardless of Transport wraps in ToolPolicy (RFC §6.4). Zero value is "use defaults": DefaultPolicy fires on a fresh Tool.Policy at dispatch time.

Mirrors engine.NodePolicy (RFC §6.1): same backoff math, same validation modes. A flow registered as a tool gets BOTH layers — the outer ToolPolicy wraps the whole flow invocation; the per-node NodePolicy wraps each step inside the flow's engine. No double-wrapping at any single layer.

func DefaultPolicy

func DefaultPolicy() ToolPolicy

DefaultPolicy returns the policy applied when ToolPolicy is zero-valued. 3 retries / 100ms→30s exponential backoff (mult=2) / 30s timeout / Validate=ValidateBoth / RetryOn=[transient, timeout, 5xx]. Per acceptance criteria.

type ToolPolicyExhaustedPayload

type ToolPolicyExhaustedPayload struct {
	events.SafeSealed
	Identity  identity.Quadruple
	ToolName  string
	Transport TransportKind
	Attempts  int
	LastClass ErrorClass
	LastError string
}

ToolPolicyExhaustedPayload is the typed payload for EventTypeToolPolicyExhausted. SafePayload.

type ToolProvider

type ToolProvider interface {
	// Connect is called once at provider attach. Drivers establish
	// long-lived connections, authenticate, etc.
	Connect(ctx context.Context) error
	// Discover returns the current set of descriptors. May be
	// called periodically by the catalog manager.
	Discover(ctx context.Context) ([]ToolDescriptor, error)
	// Close releases provider resources. Must be idempotent.
	Close(ctx context.Context) error
	// SourceID is the stable identifier for this provider.
	SourceID() ToolSourceID
}

ToolProvider is the seam for external tool sources. later phases drivers (HTTP, MCP, A2A) implement Connect / Discover / Close to pull in remote tools as ToolDescriptors. The in-process registrar does not need a provider lifecycle (it's a thin wrapper around ToolCatalog.Register), so Harbor ships the interface shape without a default driver.

Identity-mandatory: Connect / Discover propagate identity via ctx so transports can scope their authentication.

type ToolResult

type ToolResult struct {
	Value any
	Meta  map[string]any
}

ToolResult is the canonical result type returned by every ToolDescriptor.Invoke. Heavy outputs route through the ArtifactStore upstream; ToolResult.Value carries either typed Go values or ArtifactRef-shaped placeholders.

func RunWithPolicy

func RunWithPolicy(
	ctx context.Context,
	args json.RawMessage,
	invoke func(ctx context.Context, args json.RawMessage) (ToolResult, error),
	validateIn func(args json.RawMessage) error,
	validateOut func(result ToolResult) error,
	policy ToolPolicy,
) (ToolResult, error)

RunWithPolicy is the externally-visible executor — drivers (in-process, HTTP, MCP, A2A, Flow) call this from their Invoke closures so every tool invocation regardless of Transport wraps in the same reliability shell.

`validateIn` / `validateOut` may be nil; the shell uses the resolved policy's Validate mode to decide whether to call them. `policy` is the Tool's policy (per-Tool); zero-valued → defaults.

Concurrent reuse: RunWithPolicy is stateless — no shared mutable state across calls. Safe for N concurrent invocations.

func RunWithPolicyHooked

func RunWithPolicyHooked(
	ctx context.Context,
	args json.RawMessage,
	invoke func(ctx context.Context, args json.RawMessage) (ToolResult, error),
	validateIn func(args json.RawMessage) error,
	validateOut func(result ToolResult) error,
	policy ToolPolicy,
	hooks ...InvokeHooks,
) (ToolResult, error)

RunWithPolicyHooked is the externally-visible executor with observability hooks. Equivalent to RunWithPolicy with an InvokeHooks pointer. Drivers (esp. those that publish per-attempt events) consume this surface.

type ToolSearchCache added in v1.2.0

type ToolSearchCache interface {
	Search(ctx context.Context, query string, tags []string, limit int) ([]Tool, error)
	Sync(ctx context.Context, tools []Tool) error
	Close() error
}

ToolSearchCache is the tool search index surface the catalog delegates to. Defined here to avoid import cycles between internal/tools and internal/tools/drivers/searchcache.

type ToolSourceID

type ToolSourceID string

ToolSourceID identifies the provider that produced a Tool. Empty for in-process tools (no provider lifecycle to track); populated for HTTP / MCP / A2A providers.

type TransportKind

type TransportKind string

TransportKind discriminates a Tool's source. Same Tool struct regardless of Transport; the value lets observability + audit filter or annotate without re-shaping the Tool.

const (
	// TransportInProcess — a Go function registered via
	// drivers/inproc.RegisterFunc.
	TransportInProcess TransportKind = "inprocess"
	// TransportHTTP — (UTCP-style HTTP tool).
	TransportHTTP TransportKind = "http"
	// TransportMCP — (MCP southbound).
	TransportMCP TransportKind = "mcp"
	// TransportA2A — (A2A southbound).
	TransportA2A TransportKind = "a2a"
	// TransportFlow — (a typed DAG of Nodes registered as
	// a Tool via internal/runtime/flow.RegisterAsTool).
	TransportFlow TransportKind = "flow"
)

type ValidateMode

type ValidateMode string

ValidateMode selects which side of an invocation the policy validates: input, output, both, or none. Mirrors engine.NodePolicy (RFC §6.1) so a developer who learned NodePolicy already knows ToolPolicy.

const (
	// ValidateNone disables validation. Escape hatch for hot paths.
	ValidateNone ValidateMode = ""
	// ValidateBoth runs validation on input AND output. Default for
	// production tools.
	ValidateBoth ValidateMode = "both"
	// ValidateIn runs validation on the input only.
	ValidateIn ValidateMode = "in"
	// ValidateOut runs validation on the output only.
	ValidateOut ValidateMode = "out"
)

Directories

Path Synopsis
Package annotate assembles the production per-tool Annotator the Tools catalog projector reads OAuth / approval / metrics / content-stats / last-used / display-mode annotations through.
Package annotate assembles the production per-tool Annotator the Tools catalog projector reads OAuth / approval / metrics / content-stats / last-used / display-mode annotations through.
Package approval ships Harbor's tool-side synchronous approval-gate subsystem — the second consumer of the unified pause/resume primitive, layered on the same Coordinator + bus + steering inbox seams the tool-side OAuth work built.
Package approval ships Harbor's tool-side synchronous approval-gate subsystem — the second consumer of the unified pause/resume primitive, layered on the same Coordinator + bus + steering inbox seams the tool-side OAuth work built.
Package auth ships Harbor's tool-side OAuth subsystem — the TokenStore + OAuthProvider seam every HTTP / MCP / A2A southbound driver consults when a tool call needs a bearer token.
Package auth ships Harbor's tool-side OAuth subsystem — the TokenStore + OAuthProvider seam every HTTP / MCP / A2A southbound driver consults when a tool call needs a bearer token.
conformancetest
Package conformancetest exposes the shared TokenStore + Sealer conformance suite Harbor ships.
Package conformancetest exposes the shared TokenStore + Sealer conformance suite Harbor ships.
credsource
Package credsource is the §4.4 seam through which an OAuth provider's OWN client credential (`client_id` / `client_secret`) resolves.
Package credsource is the §4.4 seam through which an OAuth provider's OWN client credential (`client_id` / `client_secret`) resolves.
credsource/credsourcetest
Package credsourcetest ships the committed REFERENCE implementation of the coordinator credential-fetch contract: an httptest server that serves an OAuth provider's client credential to the `remote` credential source.
Package credsourcetest ships the committed REFERENCE implementation of the coordinator credential-fetch contract: an httptest server that serves an OAuth provider's client credential to the `remote` credential source.
credsource/drivers/env
Package env ships the default credential source: the OAuth provider's client credential is read from the process environment ONCE at boot.
Package env ships the default credential source: the OAuth provider's client credential is read from the process environment ONCE at boot.
credsource/drivers/remote
Package remote ships the coordinator-served credential source: the runtime PULLS an OAuth provider's client credential (`client_id` / `client_secret`) from an operator-configured endpoint at first need, authenticated by the runtime's own service token.
Package remote ships the coordinator-served credential source: the runtime PULLS an OAuth provider's client credential (`client_id` / `client_secret`) from an operator-configured endpoint at first need, authenticated by the runtime's own service token.
drivers/oauth2
Package oauth2 ships Harbor's V1 default OAuth provider driver (closes issue #116 and the deferred construction gap).
Package oauth2 ships Harbor's V1 default OAuth provider driver (closes issue #116 and the deferred construction gap).
drivers/tokenexchange
Package tokenexchange ships Harbor's pull-based, non-interactive external-credential acquisition strategy: a driver on the OAuth flow-strategy registry that obtains a downstream tool credential (a user's Microsoft 365 token, a Google Workspace token — foreign-IdP tokens used to call third-party tools) from an operator-configured external credential broker (a fleet orchestrator, an enterprise token vault, an STS) via an RFC-8693-shaped token exchange, instead of Harbor's interactive authorization-code flow.
Package tokenexchange ships Harbor's pull-based, non-interactive external-credential acquisition strategy: a driver on the OAuth flow-strategy registry that obtains a downstream tool credential (a user's Microsoft 365 token, a Google Workspace token — foreign-IdP tokens used to call third-party tools) from an operator-configured external credential broker (a fleet orchestrator, an enterprise token vault, an STS) via an RFC-8693-shaped token exchange, instead of Harbor's interactive authorization-code flow.
Package builtin ships the small set of opt-in tools that travel with the Harbor binary.
Package builtin ships the small set of opt-in tools that travel with the Harbor binary.
Package catalog ships Harbor's operator-config-driven tool-catalog wiring.
Package catalog ships Harbor's operator-config-driven tool-catalog wiring.
Package conformancetest is Harbor's shared conformance suite for any ToolCatalog implementation.
Package conformancetest is Harbor's shared conformance suite for any ToolCatalog implementation.
drivers
a2a
Package a2a is Harbor's southbound A2A integration with the tool catalog.
Package a2a is Harbor's southbound A2A integration with the tool catalog.
http
Package http is Harbor's HTTP transport driver for the unified tool catalog.
Package http is Harbor's HTTP transport driver for the unified tool catalog.
inproc
Package inproc is Harbor's in-process tool driver.
Package inproc is Harbor's in-process tool driver.
mcp
attach.go — the exported boot-time MCP server attachment helper (absorbs cmd/harbor's attachDevMCPServer from INCLUDING the config→ToolPolicy projection that the devstack mirror had silently dropped).
attach.go — the exported boot-time MCP server attachment helper (absorbs cmd/harbor's attachDevMCPServer from INCLUDING the config→ToolPolicy projection that the devstack mirror had silently dropped).
searchcache
Package searchcache is Harbor's SQLite FTS5-backed tool search cache.
Package searchcache is Harbor's SQLite FTS5-backed tool search cache.
Package protocol implements the seven `tools.*` Protocol methods the Console Tools page consumes:
Package protocol implements the seven `tools.*` Protocol methods the Console Tools page consumes:
Package schema is Harbor's neutral Go-type -> JSON-Schema derivation home.
Package schema is Harbor's neutral Go-type -> JSON-Schema derivation home.

Jump to

Keyboard shortcuts

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