A single tool round-trip

Time to run it end to end. Start the REPL and ask a question that requires looking at a file. If the schema and dispatcher are right, the model will pick read_file, you will dispatch it, the file contents will come back as a tool message, and the next completion will answer the actual question.

terminal
bash
make run

> read the go.mod file and tell me the module path

# Expected flow:
# 1. Assistant message with tool_calls[read_file({"path":"go.mod"})]
# 2. Dispatcher reads go.mod and appends a role: tool message
# 3. Next completion returns: github.com/learnwithparam/coding-agents-from-scratch-go

The first real agentic interaction. The model wants a file, so it asks for it. The dispatcher obliges. The model uses the result.

agent/tools.go
go
func runReadFile(args string) (string, error) {
	var in struct {
		Path string `json:"path"`
	}
	if err := json.Unmarshal([]byte(args), &in); err != nil {
		return "", fmt.Errorf("read_file: invalid args: %w", err)
	}
	if in.Path == "" {
		return "", fmt.Errorf("read_file: path is required")
	}
	clean := filepath.Clean(in.Path)
	data, err := os.ReadFile(clean)
	if err != nil {
		return "", fmt.Errorf("read_file(%s): %w", clean, err)
	}
	return string(data), nil
}

Arguments arrive as a JSON string, not a parsed map. Unmarshal into a local anonymous struct, validate, then read. filepath.Clean is normalization, not security. Real path guards arrive in the next phase.

Ordering exercise: Put the round-trip in order

Loading practice…

Checkpoint: Tool round-trip checkpoint

Loading practice…