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.
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.
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…