What the agent does
The agent reads how the service starts, because tracing has to wrap the router and the database handle at the point they are created. In a Gin API shaped like gothinkster/golang-gin-realworld-example-app, go.mod declares go 1.21 and depends on gin-gonic/gin v1.10.0, gorm.io/gorm v1.25.12 with the SQLite driver v1.5.7, golang-jwt/jwt/v5 v5.2.1 and stretchr/testify v1.10.0. hello.go is the whole bootstrap: common.Init() opens the database, Migrate(db) runs the GORM auto-migrations, gin.Default() builds the router with its logger and recovery middleware, the /api group registers the users, profiles, articles and tags routes with users.AuthMiddleware, a /api/ping route answers pong, and r.Run listens on PORT or 8080. common/database.go opens SQLite at DB_PATH, default ./data/gorm.db, and keeps the handle in a package variable that GetDB() returns to every handler. A search for otel across the module finds nothing.
It reads the repository's own rules next. AGENTS.md documents the layout: one package per domain with models.go, serializers.go, routers.go, validators.go and a unit_test.go beside them, and common/ for shared code. CI in .github/workflows/ci.yml runs on Go 1.21, 1.22 and 1.23, checks formatting with scripts/gofmt.sh, and runs scripts/coverage.sh, which loops over every package directory and runs go test -coverprofile. .golangci.yml enables staticcheck, unused and misspell. scripts/run-api-tests.sh builds the binary, starts it on 8080 and runs the RealWorld Postman collection against it. The agent checks the minimum Go version the current OpenTelemetry modules require against the go 1.21 directive and the CI matrix before it adds anything, and says in the pull request whether it had to raise the directive and drop a matrix entry or pin older module versions to keep 1.21.
The change follows the layout it found. A new common/telemetry.go builds a tracer provider from go.opentelemetry.io/otel/sdk/trace with a resource that names the service, an OTLP HTTP exporter from otlptracehttp configured by the standard OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_SERVICE_NAME variables, and returns a shutdown function that flushes pending spans. hello.go calls it first, defers the shutdown, and adds otelgin.Middleware from go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin to the router right after gin.Default(), so every route under /api gets a server span named by its route pattern. common.Init() registers the GORM tracing plugin from gorm.io/plugin/opentelemetry/tracing with db.Use, so each query becomes a child span carrying the statement. Both variables are added to .env.example, which already documents PORT, GIN_MODE and DB_PATH.
The proof is a test and a run. common/telemetry_test.go wires an in-memory exporter from go.opentelemetry.io/otel/sdk/trace/tracetest into the provider, sends GET /api/ping through a router built the same way hello.go builds it, and asserts one server span with the /api/ping/ route attribute; a second test registers a user and asserts the request span has a GORM child span for the insert. The agent then builds the binary, starts it with the exporter pointed at a stdout exporter it enables for the run, sends the registration and article requests the Postman collection uses, and keeps the printed span tree. It runs bash scripts/gofmt.sh, bash scripts/coverage.sh and golangci-lint run, then opens the pull request with the span output in the description.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Add OpenTelemetry tracing to this Gin API. Read hello.go, common/database.go, users/middlewares.go, AGENTS.md, go.mod, .env.example and .github/workflows/ci.yml first. Check which Go version the current otel, otelgin and gorm.io/plugin/opentelemetry modules need against the go directive and the CI matrix, and say what you changed about either. Add common/telemetry.go with a tracer provider, a service-name resource and an OTLP HTTP exporter configured by OTEL_SERVICE_NAME and OTEL_EXPORTER_OTLP_ENDPOINT, with shutdown called from main. Add otelgin.Middleware to the router and the GORM tracing plugin to common.Init. Document both variables in .env.example. Add tests with an in-memory span exporter that assert a server span for GET /api/ping and a database child span for user registration. Run the binary with a stdout exporter, make a registration and an article request, and keep the span output. Run scripts/gofmt.sh, scripts/coverage.sh and golangci-lint, and open a pull request with the span output.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
- Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
- Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.
What comes back
What comes back is a pull request on its own branch that adds common/telemetry.go and common/telemetry_test.go, changes hello.go to initialize tracing, defer its shutdown and register the otelgin middleware, changes common/database.go to register the GORM plugin, updates go.mod and go.sum with the OpenTelemetry modules, and adds two lines to .env.example. If the module versions required a newer Go, the go directive and the CI matrix change too, and the description says so in its first paragraph. Route handlers, models and serializers are untouched.
The description quotes the span tree from the run: the server span for POST /api/users/ with its child query spans from GORM, and the same for the article request, with span names and the parent relationship visible. A reviewer checks that the middleware sits before the /api group so the auth middleware runs inside the request span, that common.Init() still works when no exporter endpoint is set, which the tests exercise by using the in-memory exporter, and that the gofmt, coverage and lint runs in the transcript are clean.
Run it on every Go service without tracing
A script on your server lists the organization's repositories with GET /o/{org}/github, fetches each go.mod, and sends one POST /o/{org}/sessions with this prompt for every module that lacks a go.opentelemetry.io/otel requirement, naming that repository in resources. Each run works in its own sandbox and opens its own pull request; a request_id per repository makes the script safe to re-run.
Set a webhook_url on the API key and Rush POSTs run.completed when each turn finishes, with the session_url to read the pull request from; a run.failed names the service that needs a person.
The use case this job follows, with its automation and trigger: Hand off from your terminal.
Why run it on Rush?
Adding tracing to a Gin service is a few imports and then a morning of checking: did the middleware wrap every route, do the GORM spans attach to the request span, does the service still start with no collector configured, does the go 1.21 directive still hold. The agent answers each with a command in its own sandbox and keeps the output, and your laptop never builds the module.
The pull request carries the span tree it produced, tests that keep the wiring covered, and the format, coverage and lint runs the repository's own CI performs. Several Go services can be instrumented at once, each in its own sandbox and branch, and the transcript shows every step behind each one.
Frequently asked questions
Will it send traces somewhere?
Only where OTEL_EXPORTER_OTLP_ENDPOINT points. The pull request sets no endpoint; .env.example documents the variable, and the tests use an in-memory exporter. Point the variable at your collector or vendor and add headers through OTEL_EXPORTER_OTLP_HEADERS, with no code change.
Does the GORM plugin trace SQLite queries?
Yes. The plugin hooks GORM's callbacks, not a specific driver, so the SQLite queries this app runs through gorm.io/driver/sqlite appear as child spans with the statement text. The second test asserts exactly that for the insert that user registration performs.
What happens to the go 1.21 directive?
The agent checks it rather than assuming. Current OpenTelemetry Go modules state a minimum Go version in their own go.mod; if it is above 1.21, the agent raises the directive and removes the matching entry from the CI matrix, and says so first thing in the pull request. If you need 1.21, say so in the prompt and it pins module versions that still support it.
Will the JWT middleware still see the right context?
Yes. otelgin.Middleware is added right after gin.Default(), before the /api group registers users.AuthMiddleware, so the auth check runs inside the request span and anything it does appears as a child. The middleware reads and writes c.Request.Context(), which the handlers already receive.