Skip to content

toolexec User Journey

Overview

This guide walks through using toolexec to execute tools, from simple single-tool calls to complex orchestrated workflows.

1. Installation

go get github.com/jonwraymond/toolexec@latest

The exec package provides a unified facade combining discovery and execution:

import (
    "github.com/jonwraymond/toolexec/exec"
    "github.com/jonwraymond/tooldiscovery/index"
    "github.com/jonwraymond/tooldiscovery/tooldoc"
)

// Setup discovery infrastructure
idx := index.NewInMemoryIndex()
docs := tooldoc.NewInMemoryStore(tooldoc.StoreOptions{Index: idx})

// Create executor with local handlers
executor, err := exec.New(exec.Options{
    Index: idx,
    Docs:  docs,
    LocalHandlers: map[string]exec.Handler{
        "calculator-add": func(ctx context.Context, args map[string]any) (any, error) {
            a, b := args["a"].(float64), args["b"].(float64)
            return a + b, nil
        },
    },
})

// Execute a tool
result, err := executor.RunTool(ctx, "math:add", map[string]any{"a": 5, "b": 3})
fmt.Println(result.Value) // 8

// Search for tools
results, _ := executor.SearchTools(ctx, "calculator", 10)

// Get tool documentation
doc, _ := executor.GetToolDoc(ctx, "math:add", tooldoc.DetailFull)

3. Execute a Single Tool

result, err := executor.RunTool(ctx, "github:create_issue", map[string]any{
    "owner": "jonwraymond",
    "repo":  "toolexec",
    "title": "Bug report",
    "body":  "Description here",
})

if err != nil {
    log.Fatalf("Execution failed: %v", err)
}

fmt.Printf("Created issue: %v\n", result.Value)
fmt.Printf("Duration: %v\n", result.Duration)

4. Chain Tool Calls

Use UsePrevious to pass results between steps:

result, steps, err := executor.RunChain(ctx, []exec.Step{
    {ToolID: "github:create_issue", Args: map[string]any{
        "title": "Bug report",
    }},
    {ToolID: "github:add_labels", Args: map[string]any{
        "labels": []string{"bug"},
    }, UsePrevious: true}, // Injects previous result as "previous" arg
})

fmt.Printf("Final result: %v\n", result.Value)
for i, step := range steps {
    fmt.Printf("Step %d: %s → %v\n", i+1, step.ToolID, step.Value)
}

5. Register Local Tools

Register tools using the backend/local package:

import "github.com/jonwraymond/toolexec/backend/local"

backend := local.New("calculator")
backend.RegisterHandler("add", local.ToolDef{
    Name:        "add",
    Description: "Adds two numbers",
    InputSchema: map[string]any{
        "type": "object",
        "properties": map[string]any{
            "a": map[string]any{"type": "number"},
            "b": map[string]any{"type": "number"},
        },
    },
    Handler: func(ctx context.Context, args map[string]any) (any, error) {
        a, b := args["a"].(float64), args["b"].(float64)
        return a + b, nil
    },
})

6. Using the Run Package Directly (Advanced)

For more control, use the run package directly:

import "github.com/jonwraymond/toolexec/run"

runner := run.NewRunner(
    run.WithIndex(idx),
    run.WithLocalRegistry(localReg),
    run.WithValidation(true, true),
)

result, err := runner.Run(ctx, "ns:tool", args)

7. Code Orchestration (Sandboxed Execution)

The code package enables executing user-provided code that can call tools:

import "github.com/jonwraymond/toolexec/code"

// Create code executor with limits
codeExec := code.NewExecutor(code.Config{
    Index:        idx,
    Docs:         docs,
    Run:          runner,
    MaxToolCalls: 50,
    Timeout:      30 * time.Second,
})

result, err := codeExec.Execute(ctx, code.ExecuteParams{
    Language: "go",
    Code: `
        // Access tools via the tools interface
        result, _ := tools.RunTool(ctx, "math:add", map[string]any{"a": 1, "b": 2})
        return result.Structured
    `,
})

8. Runtime Isolation (Security Profiles)

toolexec supports three security profiles for different isolation levels:

Profile Isolation Use Case
ProfileDev None Development/testing
ProfileStandard Container Production
ProfileHardened VM/gVisor Untrusted code
import (
    "github.com/jonwraymond/toolexec/runtime"
    "github.com/jonwraymond/toolexec/runtime/backend/unsafe"
    "github.com/jonwraymond/toolexec/runtime/gateway/direct"
)

// Gateway exposes tool discovery + execution to sandboxed code
gateway := direct.New(direct.Config{
    Index:  idx,
    Docs:   docs,
    Runner: runner,
})

// Runtime with security profile selection
rt := runtime.NewDefaultRuntime(runtime.RuntimeConfig{
    Backends: map[runtime.SecurityProfile]runtime.Backend{
        runtime.ProfileDev: unsafe.New(unsafe.Config{RequireOptIn: true}),
    },
    DefaultProfile: runtime.ProfileDev,
})

// Execute code in the runtime
result, err := rt.Execute(ctx, runtime.ExecuteRequest{
    Language: "go",
    Code:     `__out = 1 + 1`,
    Profile:  runtime.ProfileDev,
    Gateway:  gateway,
    Limits: runtime.Limits{
        MaxToolCalls:   100,
        MemoryBytes:    512 * 1024 * 1024, // 512MB
        CPUQuotaMillis: 60000,             // 60s
    },
})

For container isolation, use runtime/backend/docker or runtime/backend/containerd with ProfileStandard.

For maximum isolation, use runtime/backend/gvisor, runtime/backend/kata, or runtime/backend/firecracker with ProfileHardened.

WASM Execution Input

The WASM backend expects a compiled module provided via ExecuteRequest.Metadata:

req := runtime.ExecuteRequest{
    Language: "wasm",
    Code:     "ignored for wasm",
    Gateway:  gateway,
    Metadata: map[string]any{
        "wasm_module": []byte{0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00},
        // or "wasm_module_b64": "<base64-encoded module bytes>"
    },
}

Execution Flow

Client              exec.Exec            run.Runner           Backend
  |                     |                     |                    |
  |-- RunTool(id) ------|                     |                    |
  |                     |-- Run(id, args) ----|                    |
  |                     |                     |-- Validate --------|
  |                     |                     |-- Resolve ---------|
  |                     |                     |-- Execute ---------|
  |                     |                     |<-- Result ---------|
  |                     |<-- RunResult -------|                    |
  |<-- Result ----------|                     |                    |

Examples

See the examples page for runnable examples (source lives in toolexec/examples/):

  • basic/ - Simple tool execution
  • chain/ - Sequential tool chaining
  • discovery/ - Search and execute workflow
  • streaming/ - Streaming execution events
  • runtime/ - Security profile configuration
  • full/ - Complete integration example

Next Steps