Skip to documentation
Documentation navigation

Documentation navigation

Documentation / guides

Tools and Tool Limits

Describe Loop tools and their execution limits.

developer

Tools are declared as factories, not live instances:

func WithTools(defs ...tool.Definition) Option
func WithToolMiddlewares(middlewares ...tool.ToolMiddleware) Option
func WithToolLimits(limits ToolLimits) Option

type ToolLimits struct {
	Iterations int
	Calls      int
	Parallel   int
}

WithTools and WithToolMiddlewares append values and defensively copy their input slices. A tool.Definition must have a nonblank name. Every produced tool name is checked during Define and again after factories run during Bind; the exact structured-output control name is reserved. A nil factory, nil built tool, bad ToolInfo, duplicate produced name, or factory error is a typed binding failure.

Limits

Zero limits receive the fixed defaults shown below. Negative values are rejected by DefinitionInvalidToolLimits; a mode may override only positive fields.

FieldMeaningDefault
Iterationsmaximum tool-loop iterations in one turn25
Callsmaximum tool calls in one turn100
Parallelmaximum concurrent calls in one batch8

The base definition’s limits apply to the implicit base mode. For a declared mode, resolveLimits takes each positive mode field and otherwise keeps the base value. The returned BoundMode.ToolLimits is a value copy.

Binding and ownership

Definition.Bind(ctx, bindings) validates nonzero SessionID and LoopID before invoking any factory. It caches equal tool definitions within that one binding, so the same immutable factory used by the base and a mode builds once and its instances are reused. Different definitions that produce the same model-facing name are rejected. bindings.ExtraTools, when supplied by a composition root, is appended to the base and every mode and is subject to the same collision checks.

bound, err := definition.Bind(ctx, tool.Bindings{
	SessionID: sessionID,
	LoopID:    loopID,
	// Workspace and other binding fields are supplied by the Rig/session.
})
if err != nil {
	var bindErr *loop.BindError
	if errors.As(err, &bindErr) {
		log.Printf("bind refused: %s", bindErr.Kind)
	}
	return err
}
for _, candidate := range bound.Tools() {
	info, _ := candidate.Info(ctx)
	log.Println(info.Name)
}

Returned Tools, Modes, and middleware slices are defensive copies. The invokable tool instances themselves are the binding-owned live collaborators; the caller must not reuse them in another session.

Source and proof

← back to documentation