bots-garden/mini-mepublic Fork
⑂ v0.9.0
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. fd5d2a7 · on v0.9.0 · k33g · 6d ago
write-a-wasm-tool.md · 89 lines · 4.0 KBmarkdown
Blame HistoryOpen raw

How to write a custom tool as a WebAssembly module

This guide shows how to give the agent a tool that runs as a WebAssembly module inside mm, in a sandbox: the module sees no file, no environment variable and no network, except the project folders you open for it. Use it instead of a command tool when the tool should not have your rights — it reads one folder, or computes something from its arguments only. It assumes TinyGo is installed and that you have an agent YAML file.

Steps

  1. Write the tool as an ordinary Go program. It reads the call's arguments as one JSON object on stdin, and what it prints on stdout or stderr is what the model gets back; exiting with a non-zero code marks the call as failed:

    // tools-src/search_notes/main.go
    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"os"
    	"path"
    	"strings"
    )
    
    func main() {
    	var in struct {
    		Word string `json:"word"`
    	}
    	if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil {
    		fmt.Fprintln(os.Stderr, "bad input:", err)
    		os.Exit(2)
    	}
    	entries, _ := os.ReadDir("notes")
    	for _, e := range entries {
    		data, _ := os.ReadFile(path.Join("notes", e.Name()))
    		for i, line := range strings.Split(string(data), "\n") {
    			if strings.Contains(strings.ToLower(line), strings.ToLower(in.Word)) {
    				fmt.Printf("%s:%d: %s\n", e.Name(), i+1, line)
    			}
    		}
    	}
    }
    
  2. Build it as a WASI module:

    tinygo build -o tools/search_notes.wasm -target=wasip1 -no-debug ./tools-src/search_notes
    
  3. Declare it in the agent file with wasm: in place of command:, and open the folders it needs — read: for read-only, write: for read-write:

    tools:
      - name: search_notes
        description: Find the lines of the notes that mention a word.
        parameters:
          word: { type: string, description: "One word, e.g. wazero" }
        wasm: ./tools/search_notes.wasm
        read: [notes]
    

    The module path is relative to the agent file; the folders are relative to the working directory, and the module opens them by the same name (notes/…).

  4. Ask for it in your own words, from the project directory:

    mm -p "Which notes mention TinyGo?"
    

    The terminal shows the module and the JSON it received, then its output:

    🧰 search_notes: search_notes.wasm {"word":"TinyGo"}
       │ tinygo.md:1: # TinyGo
       │ wazero.md:4: It runs WASI modules, such as the ones TinyGo builds with -target=wasip1.
    

Variants

  • A tool that writes. write: [out] lets it create and change files in out/ only. A folder it may only read is read:; a folder in neither list does not exist for it.
  • A pure computation. Leave out read and write: the module sees no file at all.
  • Paths inside the module. Clean them before opening: TinyGo's WASI layer refuses ./notes/a.md and .. — path.Join("notes", name) gives notes/a.md. A name like ../agent.yaml then becomes agent.yaml, which is not mounted: the sandbox keeps it out.
  • Typed parameters, defaults. The JSON holds each value in its declared type ("top": 5, "exact": true), the default of an optional parameter the call left out, and no key for an optional parameter with no default.
  • A long computation. Add timeout: 2m; past it the module is stopped. Without it, bashTimeout applies.
  • Ask before each call. yolo: false asks for a wasm tool as for a command tool.
  • Another language. Any WASI preview 1 command module should run the same way; only TinyGo modules were tried.

See also

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# How to write a custom tool as a WebAssembly module

This guide shows how to give the agent a tool that runs as a WebAssembly module inside mm, in a sandbox: the module sees no file, no environment variable and no network, except the project folders you open for it. Use it instead of a [command tool](declare-a-custom-tool.md) when the tool should not have your rights — it reads one folder, or computes something from its arguments only. It assumes [TinyGo](https://tinygo.org) is installed and that you have an agent YAML file.

## Steps

1. Write the tool as an ordinary Go program. It reads the call's arguments as one JSON object on stdin, and what it prints on stdout or stderr is what the model gets back; exiting with a non-zero code marks the call as failed:

   ```go
   // tools-src/search_notes/main.go
   package main

   import (
   	"encoding/json"
   	"fmt"
   	"os"
   	"path"
   	"strings"
   )

   func main() {
   	var in struct {
   		Word string `json:"word"`
   	}
   	if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil {
   		fmt.Fprintln(os.Stderr, "bad input:", err)
   		os.Exit(2)
   	}
   	entries, _ := os.ReadDir("notes")
   	for _, e := range entries {
   		data, _ := os.ReadFile(path.Join("notes", e.Name()))
   		for i, line := range strings.Split(string(data), "\n") {
   			if strings.Contains(strings.ToLower(line), strings.ToLower(in.Word)) {
   				fmt.Printf("%s:%d: %s\n", e.Name(), i+1, line)
   			}
   		}
   	}
   }
   ```

2. Build it as a WASI module:

   ```bash
   tinygo build -o tools/search_notes.wasm -target=wasip1 -no-debug ./tools-src/search_notes
   ```

3. Declare it in the agent file with `wasm:` in place of `command:`, and open the folders it needs — `read:` for read-only, `write:` for read-write:

   ```yaml
   tools:
     - name: search_notes
       description: Find the lines of the notes that mention a word.
       parameters:
         word: { type: string, description: "One word, e.g. wazero" }
       wasm: ./tools/search_notes.wasm
       read: [notes]
   ```

   The module path is relative to the agent file; the folders are relative to the working directory, and the module opens them by the same name (`notes/…`).

4. Ask for it in your own words, from the project directory:

   ```bash
   mm -p "Which notes mention TinyGo?"
   ```

   The terminal shows the module and the JSON it received, then its output:

   ```
   🧰 search_notes: search_notes.wasm {"word":"TinyGo"}
      │ tinygo.md:1: # TinyGo
      │ wazero.md:4: It runs WASI modules, such as the ones TinyGo builds with -target=wasip1.
   ```

## Variants

- **A tool that writes.** `write: [out]` lets it create and change files in `out/` only. A folder it may only read is `read:`; a folder in neither list does not exist for it.
- **A pure computation.** Leave out `read` and `write`: the module sees no file at all.
- **Paths inside the module.** Clean them before opening: TinyGo's WASI layer refuses `./notes/a.md` and `..` — `path.Join("notes", name)` gives `notes/a.md`. A name like `../agent.yaml` then becomes `agent.yaml`, which is not mounted: the sandbox keeps it out.
- **Typed parameters, defaults.** The JSON holds each value in its declared type (`"top": 5`, `"exact": true`), the `default` of an optional parameter the call left out, and no key for an optional parameter with no default.
- **A long computation.** Add `timeout: 2m`; past it the module is stopped. Without it, `bashTimeout` applies.
- **Ask before each call.** `yolo: false` asks for a wasm tool as for a command tool.
- **Another language.** Any WASI preview 1 command module should run the same way; only TinyGo modules were tried.

## See also

- A complete example with four modules, a read folder and a write folder: `demos/wasm-tools/`
- Every key and error: [configuration reference](../reference/configuration.md#tools-keys) and [declared tools](../reference/tools.md#declared-tools)
- What the sandbox allows and what it costs: [Declared tools](../explanation/declared-tools.md#wasm-tools)