
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.
This page works on all four stages of the loop
- 1Build the request (this page)
- 2Call the model (this page)
- 3Run the tools (this page)
- 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
Runwith an approval gate, Eino’sChatModelAgentwith an interrupt and a checkpoint store, and Genkit’sGeneratewith 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 |