In a lantern-lit market arcade, the Gopher studies a boxed kit beside open trays of neatly packed parts, while the Ghost, a stone android, turns over a well-made wrench from one of the kits.

Run it

One agent task three ways: a hand-built harness, Eino, and Genkit

The same approval-gated diagnosis written on the book’s agent package, on Eino, and on Genkit, run against the same scripted replies through a transport that records every request. Run the comparison, time a cancellation during a retry, and read what each version sends.

Draws on chapter 16 of the bookUpdated

This page works on all four stages of the loop

  1. 1Build the request (this page)
  2. 2Call the model (this page)
  3. 3Run the tools (this page)
  4. 4Decide (this page)

Chapter 16’s ports put one piece of the triage agent on two Go frameworks and on the book’s own agent package: a crash group’s diagnosis with three tools, run_tests, issue_file, and submit_diagnosis, where issue_file needs a person’s approval. All three versions reach the model through an *http.Client you supply, so all three run against the same scripted replies, with no key and no network, and a recorder keeps every request each one sends.

This page is the hands-on companion to Go agent frameworks compared, which covers what the comparison found and how to read it.

What it shows

  • The same task in three styles: the book’s Run with an approval gate, Eino’s ChatModelAgent with an interrupt and a checkpoint store, and Genkit’s Generate with its tool-approval middleware.
  • A comparison generated by code and pinned by a test, so a framework upgrade that changes behavior fails a test.
  • Defaults that differ between versions, found by reading what each one sends.

Set up

The ports have their own Go module, which keeps everything the frameworks pull in away from the book’s main module. From the code directory of a clone of go-agents-book-code:

$ cd ch16
$ go run ./cmd/harness
$ go run ./cmd/eino
$ go run ./cmd/genkit

Each runs the scripted diagnose replies, pauses for the approval of issue_file, resumes with a yes, and prints issues filed 1, diagnosis submitted true. Under Go 1.27 the Eino program starts with a warning from its JSON library, bytedance/sonic: the release pinned here only knows Go versions through 1.26, so it switches to encoding/json. Nothing breaks. The chapter uses the warning as an example of how a library carrying assembly for particular Go versions can lag behind a new Go release.

Run the comparison

$ go run ./compare

It runs each version on three scripts, diagnose (no surprises), tool-error (the first call names a package that doesn’t exist), and retry (the first call gets a 503), and prints the table the chapter discusses: lines of code, modules underneath, model calls to finish, the first request’s size, whether schemas are marked strict, how a tool’s text result is encoded, what a tool error and a 503 do, and how many bytes each version keeps while it waits for the person. go test ./compare checks the output against a saved copy.

Time a cancellation

$ go run ./compare -cancel

Each version gets the retry script and a cancellation at the 100 millisecond mark. Across five runs in the chapter, the book’s version was back in 100 to 103 milliseconds, since its backoff wait watches the context. Genkit took 421 to 485 milliseconds: the OpenAI library beneath its plugin sleeps between retries with time.Sleep, and a context can’t cut that short. The Eino port, as written, doesn’t retry the 503 at all, so it returns at once.

Read what each version sends

$ go run ./compare -bodies

This dumps the body of each request the three versions made while running diagnose. Pick out, in each version, the request sent right after the run_tests result came back, and set the three side by side. Check what each version sends for strict in its schemas, which order the tools come in (Genkit assembles the list from a Go map, so it can differ from one run to the next), and whether the test output arrives as plain text or as a JSON string with its newlines escaped. Neither framework added a prompt of its own to what the model saw.

Things to change

Send Eino’s tool errors to the model. In eino/eino.go, wrap the run_tests tool with utils.WrapToolWithErrorHandler, then run go test ./eino and go run ./compare and look at the tool-error rows: the run now continues, as the book’s version does.

Turn off the hidden retries. In genkit/genkit.go, add option.WithMaxRetries(0) to the plugin’s options and run go run ./compare -cancel again. After that edit the table test fails. The 503 row is different, and the rows that count lines move too, because your edit is part of the code they measure. go test ./compare -run TestTable -update writes the new table so you can read the diff, and git checkout compare/testdata/table.adoc puts the saved copy back; undo your edit to genkit/genkit.go as well.

Put the eight questions to a framework the chapter didn’t port. Choose ADK for Go v2 or trpc-agent-go, start at its runner’s Run method, and search its module for MaxIterations, recover(, and time.Sleep.

Key files

File What it holds
ch16/task The shared tools, the scripted replies, and Wire, the transport that keeps each request
ch16/harness/harness.go The task on the book’s agent package, with chapter 14’s approval gate
ch16/eino/eino.go The Eino port: an interrupt, a runner, and a checkpoint store
ch16/genkit/genkit.go The Genkit port: tool-approval middleware and resuming from history
ch16/compare The comparison, the cancellation timing, and the saved table