Documentation
¶
Overview ¶
Package agenttool is the tool contract for Go agents over Open Responses: what a tool is, how a typed Go function becomes one, how its JSON Schema is generated and validated, and how a batch of calls executes.
The package imports openresponses and the standard library only, so a tool written with New can be handed to any loop that speaks Open Responses. agentturn is one such loop and depends on this module; the reverse never holds, and a test enforces the boundary. The MCP adapters, mcpclient and mcpserver, are nested modules that map to and from this contract.
type ReadFileArgs struct {
Path string `json:"path" desc:"Absolute path to read"`
MaxBytes int `json:"max_bytes,omitempty" desc:"Stop after this many bytes"`
}
var ReadFile = agenttool.New("read_file", "Read a file from disk",
func(ctx context.Context, a ReadFileArgs) (string, error) { ... })
Errors returned from Execute become error outputs the model sees; a tool never encodes an error as normal content.
Index ¶
- Constants
- func ConfinedBy(ctx context.Context, t Tool, args json.RawMessage) (bool, string)
- func ContextWithRecorder(ctx context.Context, fn RecordFunc) context.Context
- func Decode[T any](raw json.RawMessage) (T, error)
- func Definition(t Tool) *openresponses.FunctionTool
- func IsSequential(t Tool) bool
- func IsStrict(t Tool) bool
- func Progress(ctx context.Context, r Result)
- func ResourceOf(t Tool) string
- func SchemaFor[T any](opts ...Option) (json.RawMessage, error)
- func SchemaOf(t reflect.Type, opts ...Option) (json.RawMessage, error)
- func WithCall(ctx context.Context, call Call) context.Context
- func WriteRecord(ctx context.Context, details Recordable) error
- type Annotated
- type Annotations
- type Call
- type Confined
- type Event
- type Executor
- type Job
- type NoArgs
- type Option
- type PanicError
- type ProgressInfo
- type Property
- type Record
- type RecordFunc
- type Recordable
- type Resource
- type Result
- type Schema
- type Schemer
- type Sequential
- type Set
- type Strict
- type Tool
- type ValidationError
Constants ¶
const DefaultMaxParallel = 8
DefaultMaxParallel bounds concurrent tool calls in a batch when the caller sets no limit.
Variables ¶
This section is empty.
Functions ¶
func ConfinedBy ¶ added in v0.0.6
ConfinedBy reports whether a call of t with args will run confined, and what confines it. A tool that does not implement Confined reports false and "", which says the tool does not claim a sandbox rather than that it has none: a policy treats both as unconfined and may not read a false as permission to skip a prompt it would otherwise raise.
func ContextWithRecorder ¶ added in v0.0.6
func ContextWithRecorder(ctx context.Context, fn RecordFunc) context.Context
ContextWithRecorder returns ctx carrying fn as the recorder that WriteRecord calls. A harness installs it around a tool call, per call, so the record it writes can be filed beside that call; a nil fn removes any recorder already on the context.
func Decode ¶
func Decode[T any](raw json.RawMessage) (T, error)
Decode unmarshals the raw arguments into T. Empty arguments decode as an empty object. Errors are phrased for the model.
func Definition ¶
func Definition(t Tool) *openresponses.FunctionTool
Definition builds the function tool that describes t on a request.
func IsSequential ¶
IsSequential reports whether t asks to run alone.
func Progress ¶
Progress reports progress on the call attached to ctx. It is a no-op when there is no call or the caller did not ask for updates.
func ResourceOf ¶ added in v0.0.6
ResourceOf returns the shared state t names, or "" when it names none. A Sequential tool takes the whole batch and reports "" here whatever it says, so a caller scheduling by resource does not have to check both.
func SchemaFor ¶
func SchemaFor[T any](opts ...Option) (json.RawMessage, error)
SchemaFor returns the JSON Schema for T. See SchemaOf.
func SchemaOf ¶
SchemaOf returns the JSON Schema object New would use for an argument type t: the one t supplies when it implements Schemer, otherwise the reflected schema of Reflect. Of the options only WithStrict applies, and not to a Schemer's own schema.
Exported fields become properties named by their json tag. A "desc" tag becomes the description and an "enum" tag, comma separated, becomes the enum. Embedded structs are flattened under encoding/json's rules: of several fields promoted under one name the shallowest wins, a tagged one wins among equals, and the rest of a tie is dropped as the encoder would drop it. Supported kinds are bool, the integer and float kinds, string, slices and arrays, maps with string keys, nested structs, pointers, time.Time (a date-time string), []byte (a string), json.RawMessage and interfaces (any value) and types implementing encoding.TextMarshaler (a string).
In strict mode every property is required, additionalProperties is false on every object, pointer fields are nullable, and maps are rejected because strict mode cannot express them.
Outside strict mode a field is optional when its tag says omitempty or omitzero or when it is a pointer.
func WithCall ¶
WithCall attaches the call to ctx so the tool function can find it with CallFrom. A New tool does this before calling its function.
func WriteRecord ¶ added in v0.0.6
func WriteRecord(ctx context.Context, details Recordable) error
WriteRecord writes details to the record now, through the recorder on ctx, and returns when the write is durable. It is what a tool calls during Execute for the handle to something it has just started: a process group after the fork, a temporary directory after the mkdir, a remote job ID once the server has answered. A tool that is killed mid-call leaves nothing behind otherwise, since Result.Details is read only when the call ends, and that is the one case where the handle is wanted.
It is a no-op returning nil when no recorder is installed, so a tool can call it unconditionally and tests need to install nothing. The namespace and JSON are RecordOf's, so a value written here and returned again as Result.Details is recorded under one namespace twice, the later write describing the call as it ended.
Types ¶
type Annotated ¶ added in v0.0.6
type Annotated interface {
Annotations() Annotations
}
Annotated is implemented by a tool that carries Annotations.
type Annotations ¶ added in v0.0.6
type Annotations struct {
// Title is a human-readable name for display.
Title string
// ReadOnly says the tool does not modify its environment.
ReadOnly bool
// Destructive says the tool may make destructive updates, rather
// than only additive ones. It is meaningful only when ReadOnly is
// false, and MCP's default for a tool that carries annotations
// without this one is true.
Destructive bool
// Idempotent says that calling the tool again with the same
// arguments has no further effect. It is meaningful only when
// ReadOnly is false.
Idempotent bool
// OpenWorld says the tool may interact with entities outside a
// closed domain, as a web search does and a memory tool does not.
// MCP's default for a tool that carries annotations without this one
// is true.
OpenWorld bool
}
Annotations are the behavioural hints a tool carries: what a policy layer keys on when it wants to treat a search differently from a delete. The fields are MCP's tool annotations, so a remote tool's hints survive the adapter in both directions, and a Go tool may set them with WithAnnotations.
They are hints and never authoritative. MCP's own specification says a client must not make tool-use decisions on the annotations of an untrusted server, and a Go tool's are only as good as its author, so a policy may use them to be stricter and must not use them alone to allow a call. The zero value is a tool that says nothing, which is not the same as a tool that says it is harmless: ReadOnly false means "unstated" as often as "writes".
func AnnotationsOf ¶ added in v0.0.6
func AnnotationsOf(t Tool) Annotations
AnnotationsOf returns t's annotations, or the zero value when it carries none, which says nothing about the tool rather than saying it is harmless.
type Call ¶
type Call struct {
// ID is the call_id of the function_call item.
ID string
// Args is the raw JSON arguments object as the model wrote it.
Args json.RawMessage
// OnUpdate, when set, receives progress before the final result. It
// may be called from the tool's goroutine; the caller serialises it.
OnUpdate func(Result)
}
Call is one invocation of a tool.
type Confined ¶ added in v0.0.6
Confined is implemented by a tool that knows whether a call will run inside a sandbox, and says so before it runs. A permission layer can then ask about the calls that leave the sandbox without knowing the product's own argument for leaving it: both reference agents put an OS sandbox under one shell tool and make the escape hatch an argument of that tool, so a shared preset that cannot see confinement either prompts for every harmless command or names one product's field and serves only that product.
Confined answers for the call args describe, with the context the call will run under, since confinement can depend on what the host put there. The string names what confines it, "seatbelt", "landlock+seccomp", "container:agent-sandbox", for the prompt and the record; it is empty when the answer is false.
A tool that runs a sandbox implements it. A tool that does not is not expected to, and ConfinedBy answers false for it, which a policy reads as unconfined, since the safe mistake is to ask. Nothing here enforces anything: this is what a tool reports, never what it runs.
type Event ¶
Event is one step of a batch: a progress update when Final is false, otherwise the completion of the job at Index. Completions arrive in completion order, not job order.
type Executor ¶
type Executor struct {
// MaxParallel bounds concurrency; zero means [DefaultMaxParallel].
MaxParallel int
// Sequential forces every batch to run one job at a time in order.
// A batch containing a [Sequential] tool runs that way regardless.
// Serialising one tool against itself is [Resource], which costs the
// rest of the batch nothing.
Sequential bool
// Recorder, when set, is installed on every job's context with
// [ContextWithRecorder], so a tool that calls [WriteRecord] while it
// runs reaches the host without the host threading a context per
// job. It is left unset by a caller that has no record to write, and
// a recorder already on the context passed to [Executor.Execute] is
// then used as it is.
Recorder RecordFunc
}
Executor runs batches of tool calls.
func (Executor) Execute ¶
Execute runs jobs and yields their events from the caller's goroutine, so a consumer never sees two events at once. Progress updates from a tool that calls Call.OnUpdate are forwarded as non-final events; the executor installs its own OnUpdate and chains to the one on the job, if any, from the yielding goroutine. A tool that panics completes with an error. Breaking out of the loop cancels the batch and waits for running tools to return. Each job's tool finds its Call on the context with CallFrom, and Executor.Recorder on it with RecorderFrom.
Jobs whose tools name the same Resource run one after the other in the model's order and alongside the rest of the batch; jobs that name none run in parallel up to MaxParallel. A serial batch, from Executor.Sequential or a Sequential tool, runs every job in the model's order and ignores resources, since it is already stricter.
type Option ¶
type Option func(*options)
Option configures a tool built by New or NewFunc, or a schema from Reflect, SchemaOf and SchemaFor. Each says which options apply to it.
func WithAnnotations ¶ added in v0.0.6
func WithAnnotations(a Annotations) Option
WithAnnotations sets the behavioural hints the tool carries; see Annotations, which a policy layer may read and must not trust alone.
func WithParameters ¶
func WithParameters(schema json.RawMessage) Option
WithParameters replaces the reflected schema of a New tool with schema. Arguments are then not validated before decoding, because the tool cannot know what the schema promises.
func WithResource ¶ added in v0.0.6
WithResource names the shared state a call of the tool touches, so Executor runs two calls of it one after the other while the rest of the batch runs alongside; see Resource. An empty name is no resource.
func WithStrict ¶
func WithStrict() Option
WithStrict generates the schema under the strict rules and sets the strict flag on the function tool.
func WithoutValidation ¶
func WithoutValidation() Option
WithoutValidation skips a New tool's argument check against the reflected schema, leaving the decoder as the only guard.
type PanicError ¶ added in v0.0.3
PanicError is the error a job completes with when its tool panicked. Error reports the tool and the panic value in one line, which is what the model sees; the stack is kept for hosts and subscribers that recover it with errors.As, and never reaches the conversation.
func (*PanicError) Error ¶ added in v0.0.3
func (e *PanicError) Error() string
type ProgressInfo ¶ added in v0.0.3
ProgressInfo, when set as the Details of a progress update, carries the numbers behind it: how far the tool is, out of how much, and a message. The fields mirror the MCP progress notification so the two adapters forward it in either direction; a tool with no numbers to report leaves Details unset and sends text alone.
type Record ¶ added in v0.0.5
type Record struct {
NS string
Data json.RawMessage
}
Record is the durable form of a Recordable Details value: the namespace and the JSON a recorder writes under it.
func RecordOf ¶ added in v0.0.5
RecordOf returns the record of details when it implements Recordable, and nil when it is nil or any other value, which is not an error: most Details are for subscribers in the same process. An empty namespace or a value that does not marshal is an error.
type RecordFunc ¶ added in v0.0.6
RecordFunc writes one record durably. A harness installs it with ContextWithRecorder so that a tool can put a handle on the record at the moment the side effect begins, rather than at the end of the call, which is where RecordOf reads Result.Details. It is called from the tool's goroutine, possibly more than once and possibly concurrently with another call's, so an implementation must be safe for concurrent use and must have written the record durably before it returns; that is the whole point of the seam. The error it returns reaches the tool, which decides whether a record it could not write fails the call.
The call the record belongs to is on the context: a recorder reads it with CallFrom, which Executor fills in for every tool it runs.
func RecorderFrom ¶ added in v0.0.6
func RecorderFrom(ctx context.Context) (RecordFunc, bool)
RecorderFrom returns the recorder on ctx, if any. A tool that would spend real work building its details can ask first; WriteRecord makes the same check.
type Recordable ¶ added in v0.0.5
type Recordable interface {
RecordNS() string
}
Recordable is implemented by a Details value that is meant to outlive the run. A recorder that does not know the type can still write it, as a namespaced JSON entry beside the call it came from, through RecordOf. RecordNS names the entry's namespace, "owner:kind" by convention, and must not be empty. The data is the value's JSON as json.Marshal produces it, so a type shapes it with MarshalJSON like anywhere else; a MarshalJSON on a pointer receiver applies only when Details holds the pointer. Details for in-process subscribers alone, such as ProgressInfo or a live handle, do not implement it and are not recorded.
type Resource ¶ added in v0.0.6
type Resource interface {
Resource() string
}
Resource is implemented by a tool that owns shared state, naming it, so that Executor runs two calls that touch the same state one after the other, in the model's order, while everything else in the batch runs alongside them. The name is free-form and "<kind>:<id>" by convention, "shell:session" or "container:47"; two tools that return the same name share the lock, which is how a shell tool and a tool that restarts that shell stay apart.
It is the answer for a tool that must not run twice at once, and Sequential is the answer for a tool that must not run while anything else does. A tool that reports both is sequential: the batch, not the resource, is what it claims. A tool that reports neither runs in parallel with everything, which is the default and stays the default.
Whether a second call waits or is refused stays the tool's choice: nothing here stops a tool from answering "the previous command is still running" instead of blocking, which is what a persistent shell usually wants the model to see.
type Result ¶
type Result struct {
// Output is what the model sees.
Output openresponses.FunctionCallOutputData
// Details is app-only data for subscribers and fronts. It is never
// sent to the model. A value that implements [Recordable] can also
// be written to a session by a recorder that does not know its
// type; see [RecordOf].
Details any
// Terminate hints that the loop should stop after this batch instead
// of calling the model again. The loop honours it only when every
// result in the batch sets it.
Terminate bool
}
Result is what a tool produced.
func ErrorResult ¶
ErrorResult builds the output the model sees when a tool fails. The text form is "Error: <message>" so the model can tell it apart from a normal result and retry.
func Parts ¶
func Parts(parts ...openresponses.Content) Result
Parts builds a result whose output is a list of content parts.
type Schema ¶
type Schema struct {
// Type is a JSON Schema type name, or empty for any value.
Type string
// Nullable adds "null" to the type, as strict mode requires for
// optional fields.
Nullable bool
Description string
Format string
Enum []any
Properties []Property
Required []string
// AdditionalProperties and NoAdditional share the JSON key
// "additionalProperties". NoAdditional emits false and wins when both
// are set, as strict mode requires; AdditionalProperties emits a
// schema for the values of a map. Neither set, the key is omitted and
// unknown properties are allowed.
AdditionalProperties *Schema
NoAdditional bool
Items *Schema
}
Schema is a JSON Schema fragment as the generator builds it. Keys are emitted in a fixed order and properties keep struct field order, so the output is stable across runs and readable in a request.
The type can be built by hand for Schema.Validate. A nil or empty slice is omitted from the JSON, so a hand-built object schema emits "properties" and "required" only when they are set, while the generator sets both to empty slices and always emits them. Type "" accepts any value.
func Reflect ¶
Reflect returns the schema tree that SchemaOf serialises, for callers that want to validate with it. t must be a struct or a pointer to one; Schemer is not consulted, since a Schemer supplies JSON, not a tree. Of the options only WithStrict applies.
func (Schema) MarshalJSON ¶
MarshalJSON emits the schema with a fixed key order. The receiver is a value so that a Schema value, on its own or inside another struct, marshals the same way as a pointer; the validate methods take a pointer because a nil Items or AdditionalProperties accepts anything.
func (*Schema) Validate ¶
Validate checks a decoded JSON value (maps, slices, strings, json.Number or float64, bools, nil) against s: its type, enum, required properties, additionalProperties when false, and its items and properties recursively. A schema with no Type accepts anything; one whose Type is not a JSON Schema type name accepts nothing, so a misspelt hand-built schema fails on its first use rather than silently passing everything.
func (*Schema) ValidateJSON ¶
func (s *Schema) ValidateJSON(raw json.RawMessage) error
ValidateJSON checks raw against s. Empty raw is an empty object.
type Schemer ¶
type Schemer interface {
JSONSchema() json.RawMessage
}
Schemer is implemented by argument types that supply their own JSON Schema instead of the reflected one.
type Sequential ¶
type Sequential interface {
Sequential() bool
}
Sequential is implemented by tools that must not run alongside other tools in the same batch. When any tool in a batch reports true, the whole batch runs one call at a time in the model's order.
type Set ¶
type Set []Tool
Set is a list of tools with lookup by name.
func (Set) Close ¶ added in v0.0.6
Close closes every tool in the set that implements io.Closer, in order, and returns their errors joined; a tool that implements it releases what it owns beyond one call, such as a container or a persistent shell. It is the host's to call, once, when the session that built the tools is over, and never a loop's: the loop's runs come and go while the tools stay. Closing a set twice is the tools' business, as is a call that arrives after.
A tool from mcpclient is not a closer; the remote's session is closed through mcpclient.Remote.Close, which serves every tool of that server.
func (Set) Definitions ¶
func (s Set) Definitions() openresponses.Tools
Definitions returns the function tools for a request, in order.
type Strict ¶
type Strict interface {
Strict() bool
}
Strict is implemented by tools whose schema was generated under the strict rules (every field required, additionalProperties false, optional fields nullable). The flag is set on the function tool.
type Tool ¶
type Tool interface {
Name() string
Description() string
// Parameters is the JSON Schema of the arguments object. nil means
// the tool takes no arguments.
Parameters() json.RawMessage
// Execute runs one call. On error the model sees the error and
// Result.Output is ignored; Result.Details may still be set for
// subscribers, as mcpclient does with the raw MCP result. The
// context is the call's, and its cancellation is the host's
// interrupt: a tool that owns a process stops it there and keeps
// anything the tool value owns across calls, which [io.Closer]
// releases.
Execute(ctx context.Context, call Call) (Result, error)
}
Tool is something the model can call. Name and Parameters become the function tool on the request; Execute runs one call.
func New ¶
func New[Args, Out any](name, description string, fn func(context.Context, Args) (Out, error), opts ...Option) Tool
New builds a Tool from a typed function. The schema is reflected from Args at registration time (see SchemaOf); a type that cannot be expressed panics here rather than at call time, like a bad regexp in regexp.MustCompile. Call.Args is validated against that schema, so a missing required property, a wrong type or a value outside an enum is returned as an error the model can retry on, then decoded into Args with the standard decoder. Properties the schema does not name are ignored, or rejected under WithStrict, whose schema says so.
Validation covers reflected schemas only. When Args implements Schemer or the schema comes from WithParameters, the tool cannot know what the schema promises, so arguments go straight to the decoder; WithoutValidation asks for the same on a reflected one.
Out maps to the output the model sees: a string passes through as text, openresponses.Contents goes out as parts, a Result or an openresponses.FunctionCallOutputData is used as is, and anything else is marshalled to JSON.
The function can reach its Call through CallFrom on the context, for the call ID or to report progress.
func NewFunc ¶ added in v0.0.3
func NewFunc(name, description string, parameters json.RawMessage, fn func(ctx context.Context, call Call) (Result, error), opts ...Option) Tool
NewFunc builds a Tool from plain values and a function that takes the raw call: the untyped counterpart of New for tools whose schema comes from elsewhere, such as a remote server. parameters is served verbatim and never validated; nil means the tool takes no arguments. WithStrict, WithSequential, WithResource and WithAnnotations apply; the options that shape a reflected schema do not. A nil fn panics here, like a bad schema in New.
type ValidationError ¶
type ValidationError struct {
// Path locates the offending value: "" for the root, otherwise a
// dotted property path with [i] for array elements.
Path string
Msg string
}
ValidationError reports arguments that do not satisfy a schema. Its message is phrased for the model, which sees it as the error output and can retry.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
Error returns "invalid arguments: <path>: <msg>".