Documentation / guides
Registering Tools With Harness
Compose standard Tools definitions with validated Harness bindings.
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:
| Definition | Built tools | Required bindings | Main dependency |
|---|---|---|---|
AskUserDefinition | AskUser | none | Harness loop user-input seam |
TaskDefinitions | TaskCreate, TaskUpdate, TaskGet, TaskList | none | one bundle-local graph |
ReadFileDefinition | ReadFile | workspace | loop.ReadGuard |
GlobDefinition | Glob | workspace | loop.ReadGuard |
GrepDefinition | Grep | workspace | loop.ReadGuard |
WriteFileDefinition | WriteFile | workspace | session coordinator injected by Build |
EditFileDefinition | EditFile | workspace | session coordinator injected by Build |
Bash | foreground Bash | workspace | optional command runner |
BashDefinition | supervised Bash | workspace and process services | AsyncProcessRunnerResolver |
FetchDefinition | Fetch | none | injected *http.Client |
WebSearchDefinition | WebSearch | none | injected SearchProvider |
ProcessOutputDefinition | ProcessOutput | process services | shared supervisor registry |
ProcessInputDefinition | ProcessInput | process services | shared supervisor registry |
ProcessStopDefinition | ProcessStop | process services | shared 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.