Skip to documentation
Documentation navigation

Documentation navigation

Documentation / guides

Registering Tools With Harness

Compose standard Tools definitions with validated Harness bindings.

developer

The Tools root package is intentionally a composition layer, not an application registry. A product chooses which definitions to expose, supplies their narrow dependencies, and lets Harness build fresh invokable tools for each loop.

Choose the Definition That Matches the Runtime

The root constructors have distinct ownership boundaries:

DefinitionBuilt toolsRequired bindingsMain dependency
AskUserDefinitionAskUsernoneHarness loop user-input seam
TaskDefinitionsTaskCreate, TaskUpdate, TaskGet, TaskListnoneone bundle-local graph
ReadFileDefinitionReadFileworkspaceloop.ReadGuard
GlobDefinitionGlobworkspaceloop.ReadGuard
GrepDefinitionGrepworkspaceloop.ReadGuard
WriteFileDefinitionWriteFileworkspacesession coordinator injected by Build
EditFileDefinitionEditFileworkspacesession coordinator injected by Build
Bashforeground Bashworkspaceoptional command runner
BashDefinitionsupervised Bashworkspace and process servicesAsyncProcessRunnerResolver
FetchDefinitionFetchnoneinjected *http.Client
WebSearchDefinitionWebSearchnoneinjected SearchProvider
ProcessOutputDefinitionProcessOutputprocess servicesshared supervisor registry
ProcessInputDefinitionProcessInputprocess servicesshared supervisor registry
ProcessStopDefinitionProcessStopprocess servicesshared supervisor registry

Use Bash when a consumer explicitly wants the legacy foreground path. Use BashDefinition when background execution or yield_time_ms must be supervised. The latter resolves the runner at build time, before a call can be invoked, and never chooses a runner from invocation-time provenance.

Build With Validated Bindings

The build call should happen after Harness has validated the bindings. The definition’s requirement bitmask is the contract that tells Harness which binding groups must be present.

// The resolver receives the validated LoopID exactly once at Build.
supervised := standardtools.BashDefinition(func(ctx context.Context, loopID uuid.UUID) (tool.AsyncProcessRunner, error) {
	return runners.ForLoop(ctx, loopID)
})

built, err := supervised.Build(ctx, tool.Bindings{
	SessionID: sessionID,
	LoopID:    loopID,
	Workspace: workspaceBinding,
	Process:   processBinding,
})
if err != nil {
	return err
}

WriteFileDefinition and EditFileDefinition prepend the session-bound mutation coordinator to their options. Do not pass a caller-owned mutation coordinator through those definitions. The factory already owns the binding that enforces path permits and lease health.

The three process companion definitions are workspace-free. Their build closure obtains process.SupervisorResourceKey from bindings.Process.Registry, then binds the resulting supervisor to the current SessionID and LoopID. Build them against the same session registry to share one supervisor; separate registries represent separate sessions.

Add Tools to a Turn

After Build, register the resulting tool.InvokableTool values with the Harness loop that will execute model tool calls. The tool list belongs in the model request, while tool-call and tool-result content belongs in the call lifecycle. Inference documents the tool request format, model selection, and tool-use blocks. Harness documents the model request step and tool-call/result step.

Keep this registration boundary explicit. The Tools package does not discover a model, construct a network client, choose a sandbox profile, or create a process runner. Those choices remain in the product composition root.

Source

Proof

← back to documentation