What the agent does
The agent reads how the service starts before it adds a route. In a Gin app like gothinkster/golang-gin-realworld-example-app, go.mod declares Go 1.21 with gin-gonic/gin v1.10.0, gorm.io/gorm v1.25.12 and the SQLite driver, and hello.go is the whole bootstrap: common.Init() opens the SQLite file at DB_PATH, defaulting to ./data/gorm.db, Migrate(db) runs GORM's AutoMigrate for the user and article models, gin.Default() builds the router with RedirectTrailingSlash turned off, and the /api group is registered in two halves, anonymous routes first and then the ones behind users.AuthMiddleware(true). There is already a /api/ping route that answers {"message": "pong"}, but it says nothing about the database, and it sits under the /api prefix an ingress probe may not be allowed to reach.
It adds a common/health.go with two handlers and a HealthRegister function in the style of users.UsersRegister and articles.ArticlesRegister. GET /healthz is liveness: it returns 200 with {"status": "ok"} and touches nothing, so a deadlocked database never makes the orchestrator restart a process that is otherwise serving. GET /readyz is readiness: it takes the *sql.DB from db.DB(), the same call hello.go already makes to close the pool on exit, and runs PingContext with a two-second timeout; success is 200 with {"status": "ok", "database": "ok"}, failure is 503 with "database": "unavailable" and the error in the log rather than in the body. Both routes are registered on the root router before any AuthMiddleware, so no JWT is needed to probe them, and /api/ping stays as it is.
The tests follow the ones in the repository. common/unit_test.go already builds a router with httptest.NewRecorder, uses assert.New(t) from testify, and proves a failing Ping() by chmod-ing the database file to 0000 and back, so the new common/health_test.go reuses that approach: one test for each route's success body and status, and one that makes the database unreachable and asserts 503 from /readyz. The test database path comes from GetTestDBPath(), as the existing tests do, so nothing touches ./data/gorm.db.
Before it pushes, the agent runs what .github/workflows/ci.yml runs: bash ./scripts/gofmt.sh, go vet ./..., bash ./scripts/coverage.sh for the tests across the Go versions the matrix lists where the sandbox has them, and golangci-lint run with the repository's .golangci.yml, which enables staticcheck, unused and misspell. It adds the two routes to readme.md under the API section and to AGENTS.md, which documents the package layout for coding agents, so the next change finds them.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "codex",
"input": "Add health check endpoints to this Gin service and open a pull request. Read go.mod, hello.go, common/database.go, common/unit_test.go, users/routers.go, users/middlewares.go, .golangci.yml, scripts/gofmt.sh, scripts/coverage.sh, .github/workflows/ci.yml, readme.md and AGENTS.md first. Add common/health.go with a HealthRegister function in the style of the existing Register functions: GET /healthz returns 200 and {\"status\":\"ok\"} without touching anything, and GET /readyz calls PingContext on the *sql.DB from db.DB() with a two-second timeout, returning 200 with {\"status\":\"ok\",\"database\":\"ok\"} or 503 with \"database\":\"unavailable\" and the error logged, not returned. Register both on the root router in hello.go before any AuthMiddleware so no token is needed, and leave /api/ping unchanged. Add common/health_test.go using httptest and testify as common/unit_test.go does, covering both routes and a failing readiness check using the test database path and the same file permission approach the existing tests use. Run scripts/gofmt.sh, go vet ./..., scripts/coverage.sh and golangci-lint run, paste the output in the pull request, and document the two routes in readme.md and AGENTS.md. Do not change existing routes or models.",
"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 with common/health.go, common/health_test.go, a three-line change to hello.go that registers the routes, and the documentation lines in readme.md and AGENTS.md. The description shows a curl of each route from the sandbox with its status and body, and the output of scripts/gofmt.sh, go vet, scripts/coverage.sh and golangci-lint run from the branch.
The CI workflow runs the same scripts on the pull request across Go 1.21, 1.22 and 1.23 and uploads coverage, so the new test file shows up in the Coveralls delta rather than being invisible. The agent pushes one commit for the handlers and tests and one for the documentation.
A reviewer checks that /readyz cannot be reached through AuthMiddleware, that the two-second timeout fits the probe interval they plan to configure, and that the 503 body gives away nothing about the database path. Then they point their orchestrator's liveness probe at /healthz and its readiness probe at /readyz.
Run it on every new service without a health route
A platform team that wants every Go service to expose the same two routes starts one run per repository with POST /o/{org}/sessions, this prompt and the repository in resources. The agent reads each service's own bootstrap and database package, so a service on Postgres gets a PingContext on its pool and one with no database gets a /readyz that checks whatever it does depend on, and each run opens its own pull request.
The key's webhook_url receives run.completed for each run with its session_url, so a script can post the pull request links to the team channel as they land.
The use case this job follows, with its automation and trigger: Hand off from your terminal.
Why run it on Rush?
The route itself is twenty lines; the care is in where it is registered, what it checks and how the failure is tested. On a laptop the test that makes the database unreachable is the one that gets skipped. In a Rush sandbox the agent writes it, runs it against a throwaway SQLite file, and the test output is kept in the transcript with the curl of each route, so the pull request shows the 503 path working, not only the 200 one.
The result is a pull request that passes the repository's own gates, gofmt, go vet, the coverage script and golangci-lint, before a reviewer opens it, and that documents the routes where the next engineer and the next agent will look. The same request works unchanged across every Go service the team owns.
Frequently asked questions
Why two routes instead of one?
Because they answer different questions. /healthz says the process is alive and should not be restarted; /readyz says it can serve traffic, which for this app means the SQLite file at DB_PATH answers a ping. Tying liveness to the database would make a database outage restart every instance for no gain.
Do the probes need a JWT?
No. The routes are registered on the root router in hello.go before users.AuthMiddleware is attached to the /api group, so an orchestrator or a load balancer can call them with no token. The existing /api/ping is left exactly as it was.
What does the readiness check do when the database hangs rather than fails?
PingContext runs with a two-second timeout, so a hung pool returns 503 after two seconds instead of holding the probe open until the orchestrator's own timeout fires. The timeout is a constant in common/health.go that a reviewer can adjust to their probe settings.
How is the failing case tested without a second database?
The same way common/unit_test.go already tests Init(): it opens the test database at GetTestDBPath(), changes the file's permissions to 0000, asserts that /readyz returns 503, and restores the permissions afterwards. No new test infrastructure is added.