First echo loop

Before we add tools, prove the plumbing works. A plain chat round-trip, user turn in, assistant turn out, nothing else. If this fails, none of the tool logic will matter.

main.go
go
func main() {
	apiKey := os.Getenv("OPENROUTER_API_KEY")
	model := getenvDefault("OPENROUTER_MODEL", "google/gemma-3-12b-it")

	if apiKey == "" {
		fmt.Fprintln(os.Stderr, "error: OPENROUTER_API_KEY is not set.")
		os.Exit(1)
	}

	llm := agent.NewOpenRouterClient(apiKey, model)
	a := agent.New(llm, 10)

	fmt.Println("coding-agents-from-scratch-go")
	fmt.Println("Type a request, or /exit to quit.")

	in := bufio.NewScanner(os.Stdin)
	in.Buffer(make([]byte, 0, 64*1024), 1024*1024) // grow the scanner so pasted files up to 1MB are not truncated
	for {
		fmt.Print("\n> ")
		if !in.Scan() {
			return
		}
		line := strings.TrimSpace(in.Text())
		if line == "/exit" {
			return
		}
		out, err := a.Run(line)
		if err != nil {
			fmt.Fprintln(os.Stderr, "error:", err)
			continue
		}
		fmt.Println(out)
	}
}

A tiny REPL. bufio.Scanner reads one line at a time, agent.Run handles the round trip, and the result prints to stdout.

main.go
go
stat, _ := os.Stdin.Stat()
piped := (stat.Mode() & os.ModeCharDevice) == 0

if piped {
	var lines []string
	for in.Scan() {
		lines = append(lines, in.Text())
	}
	msg := strings.TrimSpace(strings.Join(lines, "\n"))
	if msg == "" {
		return
	}
	out, err := a.Run(msg)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error:", err)
		os.Exit(1)
	}
	fmt.Println(out)
	return
}

Checking os.ModeCharDevice tells us whether stdin is a terminal or a pipe. The same binary runs as a REPL for humans and as a one-shot command in scripts. This is the trick that makes CI smoke tests painless.

terminal
bash
# Interactive
make run
> hello, can you hear me?

# Piped (handy for smoke tests)
echo "hello, can you hear me?" | OPENROUTER_API_KEY=sk-... go run .

Two ways to drive the same binary. If the piped form prints a reply, your wire format is correct.

Quiz: Quiz

Loading practice…

AI prompt: Try it: explain the wire format

Loading practice…