Documentation / guides
Tool Definitions, Preparation, and Results
Understand the common lifecycle shared by Looprig Tools.
The Tools package separates construction from invocation. This is the central rule for composing an effectful operation safely: a consumer builds a tool from a definition, prepares one call, lets the gate evaluate the resulting request, and invokes only the approved artifact. Mutating tools such as WriteFile, EditFile, and ProcessInput are subject to the same prepare-before-effect boundary.
Definitions Are Construction Blueprints
tool.Definition is the root package’s composition seam. A definition has a stable name, a bitmask of requirements such as tool.RequiresWorkspace or tool.RequiresProcessServices, and a build function. Build receives tool.Bindings, which carry the session and loop identities plus the workspace or process services declared by that definition.
The root builders keep dependencies explicit:
func ReadFileDefinition(
readGuard loop.ReadGuard,
options ...readfile.ReadFileOption,
) tool.Definition
func FetchDefinition(client *http.Client) tool.Definition
func BashDefinition(
resolver AsyncProcessRunnerResolver,
options ...bash.BashOption,
) tool.Definition
ReadFileDefinition and the other workspace definitions reject a missing read guard at build time. FetchDefinition rejects a missing HTTP client. BashDefinition resolves its async runner once with the validated bindings.LoopID, then builds a tool that requires both workspace and process services. These failures are DefinitionBuildError values rather than partially built tools.
TaskDefinitions is a bundle definition. One build produces four tools, TaskCreate, TaskUpdate, TaskGet, and TaskList, over one loop-local store. AskUserDefinition is pure and needs no workspace binding.
Preparation Happens Before Effect
Every standard effectful tool is a tool.CallPreparer. PrepareCall owns the untrusted argument boundary:
- Decode and validate JSON once.
- Normalize the target, command, URL, skill identity, or process handle.
- Build a
tool.Requestwith the exact requirements and reusable candidates. - Freeze all execution inputs into a typed
tool.PreparedArtifact.
Preparation is not a grant. A Bash access declaration requests a filesystem or network delta, but an omitted delta remains subject to the sandbox at execution. A direct file or network tool carries an empty grant pair because the tool itself enforces the approved target. In either case, the gate decides before the effect runs.
At invocation, InvokableRun reads the prepared call from context. It does not trust a second parse of argsJSON. The tests deliberately prepare an approved path or command, then invoke with changed raw JSON, and verify that the changed value is ignored. Missing or wrong artifacts produce a tool-result error and no effect.
// Prepare freezes the command and its request before any runner call.
request, artifact, err := bashTool.PrepareCall(ctx, executionID, `{"command":"printf prepared"}`)
if err != nil {
panic(err)
}
prepared := loop.WithPreparedCall(ctx, tool.PreparedCall{
ExecutionID: executionID,
Request: request,
Artifact: artifact,
})
// The raw JSON says "changed", but the prepared artifact still runs
// "printf prepared".
result, err := bashTool.InvokableRun(prepared, `{"command":"printf changed"}`)
The full runnable version is the prepared Bash example. Connect this lifecycle to Harness’s tool calls and results and Inference’s tool-result content.
Result Shapes
The common return type is *tool.ToolResult. Individual tools choose a bounded shape that keeps useful recovery information while avoiding secrets and host details:
Bashreturns combined output and an exit code. A non-zero exit is still a normal result.ReadFilereturns line-numbered text and a truncation notice when the guard cap is exceeded.WriteFilereturns a short success message, whileEditFilereturns a compact diff preview.Fetchreturns status, a bounded header summary, and a capped body.GlobandGrepreturn sorted or line-oriented matches with explicit truncation notices.ProcessOutput,ProcessInput, andProcessStopreturn JSON objects with opaque process IDs, cursors, status, and stable error codes.
Errors from execution are model-facing strings or fields. They do not expose file contents, request bodies, full URLs, process paths, OS PIDs, or another owner’s process details.
Fresh Instances and Shared State
Definition builds are fresh. Concurrent builds must not return the same mutable tool instance. The exception is deliberate shared state inside a bundle or session resource: the four task tools share one task graph, and supervised process definitions resolve one session-scoped SupervisorResourceKey entry. Read the registration guide before adding a definition to a composition root.