Help us shape the future of Go! Take the 2026 Go Developer Survey

tool

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2026 License: MIT Imports: 15 Imported by: 2

Documentation

Index

Constants

View Source
const AssemblyKind = "tool.Assembly"

AssemblyKind is the deployment resource kind of the standard tool assembly (registry + executor + optional dynamic injection).

View Source
const RegistryKind = "tool.Registry"

RegistryKind is the deployment resource kind of a tool registry.

View Source
const ToolName = "tool_search"

ToolName is the built-in tool_search tool name. The assembly registers it with ExposureAlways when dynamic injection is enabled, so the model always has a discovery path.

Variables

View Source
var DefaultBudget = Budget{
	MaxDefinitions: 32,
	MaxBytes:       16 * 1024,
}

DefaultBudget is the built-in budget: 32 tools or 16 KiB of definitions per turn, whichever is hit first.

Functions

func CallIDFromContext added in v0.1.30

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

CallIDFromContext returns the active tool call id carried by ctx. ok=false means the caller is outside a tool execution (or the id was empty); consumers should treat the zero value as "unknown" rather than an error.

func Register

func Register(r *resource.Registry) error

Register adds the memory registry factory to r.

func WithCallID added in v0.1.30

func WithCallID(ctx context.Context, id string) context.Context

WithCallID returns a derived context carrying id as the active tool call id.

func WithSession

func WithSession(ctx context.Context, s Session) context.Context

WithSession attaches the per-run session to ctx.

Types

type Assembly

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

Assembly is the standard tool resource value: the aggregated registry (read surface), the executor (execution surface), and — when dynamic injection is configured — the injection policy that session views are created from.

func NewAssembly

func NewAssembly(sources []Source, opts ...AssemblyOption) (*Assembly, error)

NewAssembly builds an assembly from sources. With dynamic enabled, the built-in tool_search tool is appended as a source so it is part of the registry from construction (no post-build mutation).

func (*Assembly) Catalog

func (a *Assembly) Catalog() Catalog

Catalog returns the aggregated read surface.

func (*Assembly) Close

func (a *Assembly) Close() error

Close releases the registry (and any loaded deferred tools).

func (*Assembly) Dispatcher

func (a *Assembly) Dispatcher() Dispatcher

Dispatcher returns the executor. Assembly itself also implements Dispatcher by delegation, so callers can take either form.

func (*Assembly) Execute

func (a *Assembly) Execute(ctx context.Context, call message.ToolCall) message.ToolResult

Execute implements Dispatcher.

func (*Assembly) ExecuteAll

func (a *Assembly) ExecuteAll(ctx context.Context, calls []message.ToolCall) []message.ToolResult

ExecuteAll implements Dispatcher.

func (*Assembly) NewSession

func (a *Assembly) NewSession() Session

NewSession creates one per-run / per-conversation injection view. Without dynamic injection it returns a static session showing every tool.

type AssemblyFactory

type AssemblyFactory struct{}

AssemblyFactory builds the standard tool assembly resource without middleware: the executor runs a bare chain. Deployments that need middleware use the tool.Assembly/middleware impl.

func (AssemblyFactory) New

New implements resource.Factory.

func (AssemblyFactory) Spec

func (AssemblyFactory) Spec() resource.Spec

Spec implements resource.Factory.

type AssemblyOption

type AssemblyOption func(*assemblyOptions)

AssemblyOption configures NewAssembly.

func WithConflict

func WithConflict(p ConflictPolicy) AssemblyOption

WithConflict sets the duplicate-name policy for the underlying registry.

func WithDynamic

func WithDynamic(p Policy) AssemblyOption

WithDynamic enables dynamic tool injection with policy p. Zero fields fall back to DefaultPolicy; tool_search is registered with ExposureAlways automatically.

func WithMiddleware

func WithMiddleware(mws ...Middleware) AssemblyOption

WithMiddleware appends middleware to the executor chain, outermost first.

type AssemblySettings

type AssemblySettings struct {
	Dynamic *Policy `json:"dynamic,omitempty"`
}

AssemblySettings is the strict settings subtree of the memory assembly factory: the dynamic injection policy only. Middleware settings belong to the tool.Assembly/middleware impl (see core/tool/middleware); the memory impl rejects the "middlewares" key via strict decoding.

type Budget

type Budget struct {
	// MaxDefinitions caps the number of definitions. Zero falls back
	// to DefaultBudget.MaxDefinitions.
	MaxDefinitions int `json:"max_definitions,omitempty"`
	// MaxBytes caps the total serialized size of the definitions. Zero
	// falls back to DefaultBudget.MaxBytes.
	MaxBytes int64 `json:"max_bytes,omitempty"`
}

Budget caps how many definitions reach the model per turn.

type Catalog

type Catalog interface {
	// Get returns the tool registered under name.
	Get(name string) (Tool, bool)
	// Definitions returns the Definition of every tool in the catalog.
	Definitions() []message.ToolDefinition
}

Catalog is the read-side view of a tool collection: lookup plus model-facing definitions. A Registry implements it; read-side wrappers (e.g. the dynamic injection catalog) implement it over a base registry without being execution surfaces.

type ConflictPolicy

type ConflictPolicy int

ConflictPolicy decides what happens when two sources contribute a tool with the same name.

const (
	// ConflictError fails construction on any duplicate name. This is
	// the default: a silent overwrite usually means two sources
	// disagree about who owns a tool.
	ConflictError ConflictPolicy = iota
	// ConflictOverwrite lets a later source (in the explicit dep
	// order) replace an earlier tool with the same name. Use it when
	// a deployment intentionally overrides a base tool.
	ConflictOverwrite
)

type DiscoverOutcome added in v0.3.1

type DiscoverOutcome struct {
	// Results carries one entry per requested name, in input order.
	Results []DiscoverResult `json:"results"`
	// Evicted lists pool entries dropped by budget pressure (LRU)
	// while fitting the newly discovered names.
	Evicted []string `json:"evicted,omitempty"`
}

DiscoverOutcome summarizes one Discover call.

type DiscoverResult added in v0.3.1

type DiscoverResult struct {
	Name string `json:"name"`
	// Exposed reports whether the tool will appear in the next
	// Definitions call: pooled and within the per-round budget.
	Exposed bool `json:"exposed"`
	// Reason explains a name that is not exposed:
	// "unknown" | "not_discoverable" | "over_budget" | "visible_budget".
	Reason string `json:"reason,omitempty"`
}

DiscoverResult reports one tool's discovery outcome.

type DiscoveryPolicy added in v0.3.1

type DiscoveryPolicy struct {
	// MaxTools caps the number of pool entries. Zero falls back to
	// the assembly's effective Budget.MaxDefinitions, so the default
	// pool is never wider than the visible set; set it explicitly to
	// keep more tools loaded than one round can show.
	MaxTools int `json:"max_tools,omitempty"`
	// MaxBytes caps the total serialized definition size across pool
	// entries, measured with definitionBytes. Zero falls back to the
	// assembly's effective Budget.MaxBytes, so the default pool fits
	// the visible set; set it explicitly to keep a larger loaded cache.
	MaxBytes int64 `json:"max_bytes,omitempty"`
	// IdleRounds evicts an entry that has not been used or refreshed
	// within this many rounds. Zero falls back to 10.
	IdleRounds int `json:"idle_rounds,omitempty"`
}

DiscoveryPolicy bounds the session-level discovery pool: tools the model has found via tool_search or used, kept loaded across rounds. The pool budget is independent from the per-round Budget: Budget caps what reaches the model this turn, Discovery caps what the session remembers.

func DefaultDiscoveryPolicy added in v0.3.1

func DefaultDiscoveryPolicy() DiscoveryPolicy

DefaultDiscoveryPolicy returns the recommended discovery pool bounds for hosts that keep the default per-round budget: the same size as that budget, with a 10-round idle horizon. Discovery fields left unset are normalized against the assembly's effective Policy.Budget instead, so raising budget.* raises the pool with it.

type Dispatch

type Dispatch func(ctx context.Context, call message.ToolCall) message.ToolResult

Dispatch is the function form of "execute one tool call and return its result". Middleware operates on this signature.

Implementations should treat the input call as immutable and must always return a Result — errors are reported via Result.IsError, never returned out-of-band.

type Dispatcher

type Dispatcher interface {
	Execute(context.Context, message.ToolCall) message.ToolResult
	ExecuteAll(context.Context, []message.ToolCall) []message.ToolResult
}

Dispatcher is the execution contract consumers depend on: run one tool call, or several concurrently. *Executor is the canonical implementation, but tests and adapters may substitute their own.

type Executor

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

Executor dispatches tool calls against a Catalog through a middleware chain. The un-decorated core only looks the tool up and invokes it; every cross-cutting concern — recovery, telemetry, concurrency limits, timeouts, approval, audit — is middleware, declared at construction. The chain is immutable afterwards, so an Executor is safe for concurrent use by construction.

func NewExecutor

func NewExecutor(catalog Catalog, mws ...Middleware) *Executor

NewExecutor builds an Executor over catalog. Nil middleware values are skipped. catalog must be non-nil.

func (*Executor) Execute

func (e *Executor) Execute(ctx context.Context, call message.ToolCall) message.ToolResult

Execute runs one call through the middleware chain. All failures — unknown tool, tool error — surface as Result with IsError=true, never as a Go error.

func (*Executor) ExecuteAll

func (e *Executor) ExecuteAll(ctx context.Context, calls []message.ToolCall) []message.ToolResult

ExecuteAll runs every call concurrently through the chain and returns results in input order. Concurrency is unbounded by default; put a concurrency middleware in the chain to cap it.

type Exposure

type Exposure string

Exposure controls whether a tool appears in the model-visible Definitions. It is host policy, never part of message.ToolDefinition.

const (
	// ExposureAlways marks a tool that appears in Definitions every
	// turn. Keep this set small; it is the stable baseline.
	ExposureAlways Exposure = "always"
	// ExposureDirect marks a tool that appears only when it is
	// RequiredByName or sits in the discovery pool (found via
	// tool_search or used recently).
	ExposureDirect Exposure = "direct"
	// ExposureDeferred marks a tool that is hidden until tool_search
	// surfaces it; discovered tools stay visible while they are used
	// and expire after idle rounds or discovery-budget eviction.
	ExposureDeferred Exposure = "deferred"
	// ExposureHidden marks a tool that never appears in Definitions
	// (not even via search). RequiredByName can still surface it, and
	// the tool remains callable by exact name at all times.
	ExposureHidden Exposure = "hidden"
)

func (Exposure) Valid

func (e Exposure) Valid() bool

Valid reports whether e is one of the four exposure levels.

type LazyTool

type LazyTool struct {
	Name        string
	Placeholder message.ToolDefinition
	Load        func(ctx context.Context) (Tool, error)
}

LazyTool is a deferred tool descriptor contributed by a Source. Name must equal Placeholder.Name; Load performs any network or process work the tool needs.

func (LazyTool) Validate

func (lt LazyTool) Validate() error

Validate checks the descriptor invariants.

type Middleware

type Middleware func(next Dispatch) Dispatch

Middleware wraps a Dispatch. mws[0] is outermost (sees the call first and the result last), matching agent.ComposeHost.

type Option

type Option func(*options)

Option configures a Registry.

func WithConflictPolicy

func WithConflictPolicy(p ConflictPolicy) Option

WithConflictPolicy sets the duplicate-name policy. The default is ConflictError; pass ConflictOverwrite to let later sources win.

type Policy

type Policy struct {
	// Default is the exposure applied to registered tools that have no
	// explicit entry in Exposures. Empty means ExposureDeferred.
	Default Exposure `json:"default,omitempty"`
	// Exposures maps tool names to explicit exposure levels.
	Exposures map[string]Exposure `json:"exposures,omitempty"`
	// Budget caps the visible set. Zero uses DefaultBudget.
	Budget Budget `json:"budget,omitempty"`
	// SearchWithLoad makes tool_search load every deferred source
	// before ranking, so hits are computed over real metadata instead
	// of placeholder declarations. Default is false (lazy search).
	SearchWithLoad bool `json:"search_with_load,omitempty"`
	// Discovery bounds the persistent discovery pool. Zero fields fall
	// back to DefaultDiscoveryPolicy.
	Discovery DiscoveryPolicy `json:"discovery,omitempty"`

	// SelectedRetention is deprecated: when Discovery.IdleRounds is
	// unset it seeds the idle horizon, then it is ignored. Zero means
	// "unset".
	SelectedRetention int `json:"selected_retention,omitempty"`
	// RecentWindow is deprecated: when Discovery.IdleRounds is unset
	// and SelectedRetention is not set either, it seeds the idle
	// horizon. It no longer drives a separate recency window. Zero
	// means "unset".
	RecentWindow int `json:"recent_window,omitempty"`
}

Policy is the host-declared injection policy for one assembly. Zero values mean "use DefaultPolicy" for every field except Exposures, so a partial settings subtree is sufficient.

func DefaultPolicy

func DefaultPolicy() Policy

DefaultPolicy returns the recommended policy: everything deferred by default, the per-round budget, and the default discovery pool.

func (Policy) Validate

func (p Policy) Validate() error

Validate checks policy invariants. Call after normalizePolicy when the policy may come from settings.

type Registrar added in v0.1.8

type Registrar interface {
	// Add registers one tool. Duplicate names follow the registry's
	// ConflictPolicy; adding to a closed registry returns an error.
	Add(t Tool) error
	// Remove unregisters the named tool, if present.
	Remove(name string)
}

Registrar is the runtime mutation surface of a Registry. Sources that discover tools after construction — an MCP server that connects in the background, a refresh that re-lists remote tools — use it to publish tools as they become available.

type Registry

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

Registry aggregates tools from many Source values. It implements Catalog; the underlying execution surface for an Executor is the same value. The tool set is fixed at construction — sources are a build-time concern — and changes at runtime only through the Registrar surface (Add/Remove), which deferred sources use to publish tools discovered after construction.

A Registry is safe for concurrent use. Close releases every contributed tool that implements io.Closer (deferred proxies included), forbids further lazy loads, and detaches the sources that were attached at construction.

func NewRegistry

func NewRegistry(sources []Source, opts ...Option) (*Registry, error)

NewRegistry builds a registry from sources in order. With the default ConflictError policy a duplicate name is an error; with ConflictOverwrite the later source wins. Sources that implement RegistryAttacher receive the new registry's Registrar before NewRegistry returns, so tools they discover after construction (a background MCP connect, a refresh) still reach it; sources that implement RegistryDetacher are notified when the registry closes.

func (*Registry) Add added in v0.1.8

func (r *Registry) Add(t Tool) error

Add registers one tool at runtime. It follows the same duplicate policy as construction and fails after Close.

func (*Registry) Close

func (r *Registry) Close() error

Close releases every tool that implements io.Closer (in reverse registration order), marks the registry closed so deferred tools can no longer load, and notifies every attached RegistryDetacher. It is idempotent.

func (*Registry) Definitions

func (r *Registry) Definitions() []message.ToolDefinition

Definitions implements Catalog, sorted by name.

func (*Registry) Get

func (r *Registry) Get(name string) (Tool, bool)

Get implements Catalog. A deferred tool returns its proxy, which serves the placeholder definition until first Execute.

func (*Registry) Len

func (r *Registry) Len() int

Len returns the number of registered tools.

func (*Registry) Names

func (r *Registry) Names() []string

Names returns the registered tool names in sorted order.

func (*Registry) Remove added in v0.1.8

func (r *Registry) Remove(name string)

Remove unregisters the named tool at runtime. If the removed tool implements io.Closer, it is closed so runtime removals (e.g. a shrunk MCP tool projection) release whatever resources the tool holds; the close happens outside the registry lock and is best-effort. Unknown names are ignored; after Close it is a no-op.

type RegistryAttacher added in v0.1.8

type RegistryAttacher interface {
	Attach(r Registrar)
}

RegistryAttacher is an optional Source capability: NewRegistry hands the new Registry's Registrar to sources that need to publish tools after construction (background connects, refresh).

type RegistryDetacher added in v0.5.4

type RegistryDetacher interface {
	// Detach reports that r, the registry this source was attached to,
	// has closed and released its tools. A source attached to several
	// registries is told about each as it closes.
	Detach(r Registrar)
}

RegistryDetacher is an optional RegistryAttacher counterpart: the registry calls Detach when it closes, so the attacher can drop the registrar it was handed. Without it, a source that outlives the registries it is attached to (a host ToolSet attached once per runtime generation) keeps every closed registry, and the tools it holds, reachable from a process-lifetime object.

type RegistryFactory

type RegistryFactory struct{}

RegistryFactory builds a memory registry from its many tool.Source deps. The dep order (GetAll's sorted key order) is the explicit source order used by ConflictOverwrite.

func (RegistryFactory) New

New implements resource.Factory.

func (RegistryFactory) Spec

func (RegistryFactory) Spec() resource.Spec

Spec implements resource.Factory.

type SearchHit

type SearchHit struct {
	Name        string  `json:"name"`
	Description string  `json:"description"`
	Score       float64 `json:"score"`
}

SearchHit is one tool_search result.

type SearchTool

type SearchTool struct{}

SearchTool is the model-facing discovery tool. It resolves the session from context (see WithSession) so one instance can be registered on the shared registry for all sessions.

func NewSearchTool

func NewSearchTool() SearchTool

NewSearchTool returns the tool_search implementation.

func (SearchTool) Definition

func (SearchTool) Definition() message.ToolDefinition

func (SearchTool) Execute

func (SearchTool) Execute(ctx context.Context, arguments string) (message.Content, error)

Execute parses the query, ranks hits, and exposes the top results for the following rounds through the session discovery pool.

type Session

type Session interface {
	Catalog

	// Require adds names to the RequiredByName set. Idempotent.
	Require(names ...string)
	// Discover adds names to the discovery pool (or refreshes their
	// recency). The pool is bounded by Policy.Discovery: entries are
	// evicted least-recently-used when the pool is over budget and
	// swept after idle rounds. A discovered tool must be loaded before
	// the next Definitions call, otherwise the model would see its
	// placeholder schema — tool_search loads each name before
	// discovering it. Names are ranked: the first one wins when a
	// same-round tie is pruned. DiscoverResult.Exposed reports whether
	// the name will actually appear in the next Definitions call, so a
	// pooled tool that loses the per-round budget comes back with
	// Reason "visible_budget" instead of a false positive.
	Discover(names ...string) DiscoverOutcome
	// RecordCall records that the model called name this round,
	// refreshing its discovery-pool recency. Always and Hidden tools
	// are skipped: the former are always visible, the latter must
	// never surface through use.
	RecordCall(call message.ToolCall)
	// AdvanceTurn moves to the next round, expiring discovery entries
	// that went idle and dropping names no longer in the catalog. Call
	// it once per inference round.
	AdvanceTurn()
	// Search ranks searchable definitions against query with BM25.
	// Search never loads deferred tools.
	Search(ctx context.Context, query string, limit int) ([]SearchHit, error)
	// SearchWithLoad loads every deferred tool before ranking, so hits
	// are computed over real metadata. It is the explicit opt-in for
	// hosts that need the complete tool set; tool_search itself follows
	// Policy.SearchWithLoad instead.
	SearchWithLoad(ctx context.Context, query string, limit int) ([]SearchHit, error)
	// Load eagerly loads every deferred tool. One failing source does
	// not stop the others; errors are joined.
	Load(ctx context.Context) error
	// EnsureLoaded forces the deferred load of the named tools so the
	// next Definitions call sees their real schemas. Unknown or
	// already-concrete tools are skipped.
	EnsureLoaded(ctx context.Context, names ...string) error
}

Session is the per-run / per-conversation injection view over a Catalog. An Engine creates one at run start, feeds the model Definitions() each round, records calls, and advances turns.

When the assembly was built without dynamic injection, the session is static: Definitions() shows every tool, and the stateful operations (Require/Discover/RecordCall/AdvanceTurn) are no-ops while Search returns NotAvailable.

func SessionFromContext

func SessionFromContext(ctx context.Context) (Session, bool)

SessionFromContext returns the per-run session attached by the engine. Only tool_search and the session recorder read it; the session is explicit run state, not assembly wiring.

type Source

type Source interface {
	Tools() []Tool
	LazyTools() []LazyTool
}

Source is a multi-tool contributor to a Registry: builtin Go functions, a sandbox-backed exec set, an MCP server, a YAML document, a host-provided collection. Sources are resources in the deployment DAG; a registry aggregates them through a Many dep.

Tools() are eager — available immediately without I/O. LazyTools() are deferred: the registry stores a proxy serving LazyTool.Placeholder as its definition, and the real implementation is loaded on first Execute (never at build time).

type Tool

type Tool interface {
	Definition() message.ToolDefinition
	Execute(ctx context.Context, arguments string) (message.Content, error)
}

Tool is the interface that LLM-callable tools must implement.

Execute returns provider-neutral content, not a string: a text-only tool returns a single text part (message.NewTextContent), while a tool that produces images, audio, files, or structured data keeps those parts intact all the way to the wire. Errors are transport or execution failures; a failure the model should read and recover from is better expressed as an error result at the dispatch layer, which preserves the distinction for middleware and telemetry.

Returning zero parts with a nil error is allowed and means "nothing to report": dispatch normalizes it to one empty text part before the result is stored. Content that fails message.Content.Validate is a contract violation and comes back as an error result naming the tool.

func FuncTool

func FuncTool(def message.ToolDefinition, fn func(ctx context.Context, args string) (message.Content, error)) Tool

FuncTool wraps a plain function as a Tool.

func TextTool added in v0.4.0

func TextTool(def message.ToolDefinition, fn func(ctx context.Context, args string) (string, error)) Tool

TextTool wraps a plain string-returning function as a Tool. It is the convenience form of FuncTool for the common case where the result is a single text part.

type ToolMeta

type ToolMeta struct {
	// RateLimit is the maximum number of executions per second this
	// tool can sustain. Zero means "no claim" (no rate limit applied).
	// A negative value is treated as zero.
	RateLimit float64

	// MutatesState declares that this tool has side effects beyond
	// returning a result (writes, posts, sends mail, ...). Zero value
	// (false) is the conservative default in the opposite direction:
	// callers that don't know better should assume the tool MAY mutate
	// state.
	MutatesState bool

	// SelfTimeout declares that the tool already bounds its own
	// execution time, so the timeout middleware should not impose its
	// default deadline on top.
	SelfTimeout bool
}

ToolMeta carries optional, execution-relevant metadata about a Tool.

All fields are advisory; a zero ToolMeta means "no claims, treat conservatively" (no rate limit, assume the tool may mutate state so retries are unsafe).

func MetadataOf

func MetadataOf(t Tool) ToolMeta

MetadataOf returns the ToolMeta declared by t, or a zero ToolMeta when t does not implement ToolMetadata. Safe on nil values.

type ToolMetadata

type ToolMetadata interface {
	Metadata() ToolMeta
}

ToolMetadata is an optional interface a Tool may implement to declare execution-relevant metadata. Tools that do not implement it are treated as if they returned a zero ToolMeta.

Directories

Path Synopsis
Package mcp attaches Model Context Protocol servers to a [tool.Registry], turning every tool a server exposes into an ordinary [tool.Tool].
Package mcp attaches Model Context Protocol servers to a [tool.Registry], turning every tool a server exposes into an ordinary [tool.Tool].
Package tooltest provides reusable fixtures and contract suites for core/tool implementations.
Package tooltest provides reusable fixtures and contract suites for core/tool implementations.

Jump to

Keyboard shortcuts

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