The run_bash tool
Shell is the escape hatch. Any test runner, linter, formatter, or compile step the agent needs can be reached through one well-built shell tool. That same escape hatch is also the most dangerous tool you will wire up, which is why the rest of the module is about containment.
{
Spec: ToolSpec{
Type: "function",
Function: FunctionSpec{
Name: "run_bash",
Description: "Run a shell command and return combined stdout+stderr. Dangerous commands are blocked.",
Parameters: map[string]any{
"type": "object",
"properties": map[string]any{
"cmd": map[string]any{"type": "string", "description": "Command line to execute with /bin/sh -c."},
},
"required": []string{"cmd"},
},
},
},
Run: runBash,
},One field, cmd, executed through /bin/sh -c. This gives the model pipelines, globbing, and redirection. The description is explicit that some commands are blocked so the model does not keep retrying them.
cmd := exec.Command("/bin/sh", "-c", in.Cmd)
cmd.Env = os.Environ()
done := make(chan struct{})
var out []byte
var err error
go func() {
out, err = cmd.CombinedOutput()
close(done)
}()
select {
case <-done:
case <-time.After(30 * time.Second):
_ = cmd.Process.Kill()
return "", fmt.Errorf("run_bash: timed out after 30s")
}cmd.CombinedOutput merges stdout and stderr, which matches how the model expects to read test runner output. A goroutine plus a select implements the timeout without pulling in a context library.
Quiz: Quiz
Loading practice…