init
diff --git a/tools/file.go b/tools/file.go
new file mode 100644
index 0000000..815c2eb
--- /dev/null
+++ b/tools/file.go
@@ -0,0 +1,65 @@
+package tools
+
+import (
+	"os"
+)
+
+type FileReadArgs struct {
+	Path string `json:"path"`
+}
+
+func FileRead(args FileReadArgs) (string, error) {
+	if b, err := os.ReadFile(args.Path); err != nil {
+		return "", err
+	} else {
+		return string(b), nil
+	}
+}
+
+type FileWriteArgs struct {
+	Path     string `json:"path"`
+	Contents string `json:"contents"`
+}
+
+type FileWriteResult struct {
+}
+
+func FileWrite(args FileWriteArgs) (string, error) {
+	if err := os.WriteFile(args.Path, []byte(args.Contents), 0666); err != nil {
+		return "error", err
+	} else {
+		return "done", nil
+	}
+}
+
+type DirListArgs struct {
+	Name string `json:"name"`
+}
+
+type DirEntry struct {
+	Name  string `json:"name"`
+	IsDir bool   `json:"is_dir"`
+}
+
+type DirListResult struct {
+	Entries []DirEntry `json:"entries"`
+}
+
+func DirList(args DirListArgs) (DirListResult, error) {
+	dir := "."
+	if args.Name != "" {
+		dir = args.Name
+	}
+	entries, err := os.ReadDir(dir)
+	if err != nil {
+		return DirListResult{}, err
+	}
+	var ret DirListResult
+	for _, e := range entries {
+		ret.Entries = append(ret.Entries, DirEntry{
+			Name:  e.Name(),
+			IsDir: e.IsDir(),
+		})
+	}
+	return ret, nil
+}
diff --git a/tools/func.go b/tools/func.go
new file mode 100644
index 0000000..9ad2add
--- /dev/null
+++ b/tools/func.go
@@ -0,0 +1,66 @@
+package tools
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	"github.com/invopop/jsonschema"
+)
+
+type FuncTool[I any, O any] struct {
+	name   string
+	desc   string
+	schema *jsonschema.Schema
+	fn     func(args I) (O, error)
+}
+
+func NewFuncTool[I any, O any](name string, fn func(args I) (O, error), desc string) *FuncTool[I, O] {
+	return &FuncTool[I, O]{
+		name,
+		fmt.Sprintf("%s\nResult schema: %s", desc, GenerateSchema[O]()),
+		GenerateSchema[I](),
+		fn,
+	}
+}
+
+func (ft *FuncTool[I, O]) Name() string {
+	return ft.name
+}
+
+func (ft *FuncTool[I, O]) Description() string {
+	return ft.desc
+}
+
+func (ft *FuncTool[I, O]) InputSchema() *jsonschema.Schema {
+	return ft.schema
+}
+
+func (ft *FuncTool[I, O]) Call(inp string) (string, error) {
+	var args I
+	if err := json.NewDecoder(strings.NewReader(inp)).Decode(&args); err != nil {
+		return "", err
+	}
+	out, err := ft.fn(args)
+	if err != nil {
+		return "", err
+	}
+	var resp strings.Builder
+	if err := json.NewEncoder(&resp).Encode(out); err != nil {
+		return "", err
+	}
+	ret := resp.String()
+	fmt.Printf("$$$ %s\n", ret)
+	return ret, nil
+
+}
+
+func GenerateSchema[T any]() *jsonschema.Schema {
+	reflector := jsonschema.Reflector{
+		AllowAdditionalProperties:  false,
+		RequiredFromJSONSchemaTags: true,
+		DoNotReference:             true,
+	}
+	var v T
+	return reflector.Reflect(v)
+}
diff --git a/tools/init.go b/tools/init.go
new file mode 100644
index 0000000..7c2ec4d
--- /dev/null
+++ b/tools/init.go
@@ -0,0 +1,8 @@
+package tools
+
+func Register(reg Registry) error {
+	reg.Add(NewFuncTool("file_read", FileRead, "Reads contents of the given file."))
+	reg.Add(NewFuncTool("file_write", FileWrite, "Writes given contents to a file."))
+	reg.Add(NewFuncTool("dir_list", DirList, "Reads directory with given name, returning all its entries."))
+	return nil
+}
diff --git a/tools/tool.go b/tools/tool.go
new file mode 100644
index 0000000..c99fa56
--- /dev/null
+++ b/tools/tool.go
@@ -0,0 +1,44 @@
+package tools
+
+import (
+	"github.com/invopop/jsonschema"
+)
+
+type Tool interface {
+	Name() string
+	Description() string
+	InputSchema() *jsonschema.Schema
+	Call(inp string) (string, error)
+}
+
+type Registry interface {
+	All() []Tool
+	Add(tool Tool)
+	Get(name string) Tool
+}
+
+type InMemoryRegistry struct {
+	tools map[string]Tool
+}
+
+func NewInMemoryRegistry() *InMemoryRegistry {
+	return &InMemoryRegistry{
+		make(map[string]Tool),
+	}
+}
+
+func (r *InMemoryRegistry) Add(tool Tool) {
+	r.tools[tool.Name()] = tool
+}
+
+func (r *InMemoryRegistry) All() []Tool {
+	var ret []Tool
+	for _, t := range r.tools {
+		ret = append(ret, t)
+	}
+	return ret
+}
+
+func (r *InMemoryRegistry) Get(name string) Tool {
+	return r.tools[name]
+}