agente de servidor
Go
La instalación es explícita — un middleware, un envoltorio del driver y dos líneas al principio de las funciones que merecen medirse — porque Go no tiene paso de compilación al que engancharse ni forma de alcanzar el contexto de quien llama sin que se lo den.
- paquete
github.com/sixty-sh/sixty-goen Go modules- funciona sobre
- Go 1.22 o posterior. Sin dependencias, ni en el go.sum que añade.
- código
- sixty-sh/sixty-go
Cómo instalarlo
Any net/http server and any database/sql driver: functions, HTTP routes and SQL. Spans are threaded through context, so the install is explicit rather than automatic.
La instalación está escrita como un prompt para el agente de código que ya tienes abierto, no como una lista de tareas para ti. Es deliberado: nombra lo que tiene que ser cierto cuando la instalación esté terminada en vez de qué archivos editar, porque dónde va el código depende del framework y ponerlo en el sitio equivocado falla en silencio. Un agente puede leer tu repositorio y deducirlo; un párrafo en una página de documentación no.
El mismo texto es lo que devuelve install_sixty a través de el servidor MCP y lo que el colector sirve en /v1/setup?kind=go. Hay una sola copia.
la instalación de Go, completa
Install the sixty agent in this Go service so its functions, HTTP routes and
database queries report to sixty.
The package is github.com/sixty-sh/sixty-go, imported as "sixty". It
has no dependencies of its own.
Before editing, inspect whether this service makes LLM or agent calls. If it
does, ask the user: "Do you want LLM monitoring?" If yes, use StartAgent,
StartGeneration and StartTool, thread each returned context through the work,
and call generation.SetUsage with token/cost metadata before End. The API
does not accept prompts, outputs, tool arguments or tool results.
1. In main(), before the server starts:
defer sixty.Init(sixty.Config{}).Shutdown(context.Background())
The zero Config reads the environment. Shutdown sends the last window, so
a short-lived process still reports; on a long-lived server it runs at
exit. If this service already traps signals for graceful shutdown, call
Shutdown there instead of deferring.
2. Wrap the HTTP handler — sixty.Middleware(handler) — at the outermost level,
so the span covers the other middleware rather than sitting inside it.
Anything that speaks net/http works: chi, gorilla/mux, echo, the standard
library's own mux.
If this project uses a router that knows its route patterns, add one more
middleware AFTER routing that calls sixty.SetRoute(r.Context(), pattern) —
chi.RouteContext(r.Context()).RoutePattern(), or the equivalent. Without
it, /users/42/orders is templated to /users/:id/orders, which is close and
occasionally wrong.
3. Measure the database. Replace the sql.Open call with sixty.Open, passing
the same driver name and DSN:
db, err := sixty.Open("pgx", dsn)
If this project builds its pool some other way — a Connector, a pgx pool
through stdlib, a wrapper library — use sixty.WrapDriver(d) around the
driver it registers instead. Postgres is what the detector understands;
another database will report timings and nothing else.
4. Measure the functions worth measuring. This is the step that turns "this
endpoint got slow" into "this function started issuing 14 queries", and
skipping it leaves the feed with routes and queries and nothing between:
func (s *Store) GetUserOrders(ctx context.Context, id int) (_ []Order, err error) {
ctx, span := sixty.Start(ctx, "orders.GetUserOrders")
defer span.Capture(&err)
...
}
Put it on the service or repository layer — the code between the handler
and the database — not on handlers the middleware already covers. Name
operations package.Function, and keep the names stable: the name is the
identity, and renaming one starts its history over.
Do NOT put it on functions called hundreds of thousands of times a second.
A span costs a few hundred nanoseconds, which is nothing next to a request
and everything next to a tight loop.
5. Set these environment variables wherever the service is deployed:
SIXTY_API_KEY = a secret key starting sixty_sk_ — ask me for it. Do not
invent one, and do not commit it.
SIXTY_SERVICE = my-app
SIXTY_ENDPOINT = https://ingest.sixty.sh
The release identifier usually needs nothing: the Go toolchain stamps the
commit into the binary and the agent reads it back. If this builds with
-buildvcs=false, or from a source tarball with no repository, set
SIXTY_RELEASE to the commit SHA — without one, every measurement lands in
a single nameless bucket and no comparison can ever be made.
Constraints — correctness requirements, not style preferences:
- Do NOT change any application behaviour. This is instrumentation only: no
refactors, no reordering of business logic, no "while I was in here" fixes.
- Thread the context. sixty.Start returns a new ctx and the work must use it,
or the queries underneath are recorded as belonging to nobody. There is no
ambient-context version of this and you should not build one: no
goroutine-local storage, no //go:linkname, no global "current span".
- A goroutine started inside a measured function must be passed that ctx if
its work should count towards the operation. One that outlives the request
should NOT be — it would attribute background work to whoever happened to
start it.
- Do NOT add any analytics, user id, session id, or cookie to what is
reported. The agent is deliberately anonymous and must stay that way.
- Leave the sql.Rows handling alone. The agent counts rows as the caller
reads them and closes the span when the rows close, so a query whose rows
are never closed reports late — which is also a connection leak worth
fixing on its own terms.
When you are done, tell me which files you changed and what the deployed start
command now is, so I can confirm data is arriving.Necesita una clave secreta — empieza por sixty_sk_ y se queda en el servidor. Genera una en la página de Ajustes cuando hayas iniciado sesión.
Qué mide
| señal | unidad | qué significa |
|---|---|---|
rows | rows per call | this query returns more rows than it used to |
fanout | queries per call | this operation now issues more database calls per invocation — an N+1 |
latency | ms per call | this operation takes longer end to end than it used to |
self_latency | ms per call | the time spent in this function itself got longer — its children did not |
payload | bytes per call | the serialized result of this operation got bigger |
errors | error rate | a larger fraction of calls are throwing |
runaway | calls per minute | this operation is being called far more often than anything triggers it |
repeated_query | times per request | the identical query runs several times within one request |
overfetch | rows per call | far more rows are fetched than the code appears to use |
unbounded | rows per call | this query has no upper bound on what it can return |
recursion | levels deep | this operation calls itself, deeper than it should |
new_error | occurrences | an error that did not occur in the previous release |
missing_tenancy | — | This reads a table of per-person data without saying whose rows it wants. Unless your database is filtering it for you, everyone gets everyone else's. |
collapse | — | This is handing back roughly half the data it used to, or less. If that was not deliberate, something is filtering out rows that somebody expects to see. |
vanished | — | It was being used steadily until this release and has not been used once since. Usually the link, button, or redirect that led here stopped working. |
traffic_drop | — | This is still being used, but a fraction as often, and its share of your traffic fell too — so it is not just a quiet period. |
Dónde se engancha
- net/http — sixty.Middleware envuelve cualquier handler, incluido el mux de patrones de Go 1.22, chi, gorilla y echo.
- Nombres de ruta — sixty.SetRoute(r, "/orders/{id}") donde el router conoce el patrón y la ruta no.
- Tus propias funciones — ctx, done := sixty.Start(ctx, "orders.List"); defer done() — dos líneas, y el contexto hay que ir pasándolo.
Bases de datos
- database/sql — Cualquier driver. El agente envuelve el driver y no la conexión, cuenta las filas según las itera quien llama, y replica cada interfaz opcional que implemente el driver real.
Qué hace solo este
- Un módulo sin dependencias — No añade nada a go.sum. Esto se carga dentro de binarios de producción de otra gente, y una dependencia sería un conflicto de versiones causado por una herramienta de monitorización.
- Filas contadas según se leen — No de una porción devuelta — database/sql no tiene ninguna. El span se cierra cuando se cierran las filas, así que una consulta cuyas filas no se cierran nunca reporta tarde.
Qué no puede hacer
- Hay que ir pasando el contexto. Una goroutine a la que no se le pasa el ctx hace su trabajo fuera de la operación, lo cual es correcto para trabajo en segundo plano e incorrecto para un fan-out que querías medir. No hay una versión de esto con contexto ambiental y no la vamos a construir.
- No se capturan planes de consulta.
- La CPU y la espera no se separan. Las goroutines migran entre hilos, así que un reloj por hilo no describe un span.
Configuración
Todos los agentes leen las mismas cuatro variables, y DRIFT_* sigue respondiendo allí donde lo hace SIXTY_* — el producto se renombró y ese nombre no es nuestro para retirarlo de los despliegues de otra gente.
SIXTY_API_KEY | Sin ella el agente se queda inerte y lo dice. Nunca adivina, nunca reintenta contra un endpoint desconocido, y nunca lanza una excepción. |
|---|---|
SIXTY_SERVICE | Cómo llamar a este servicio. Por defecto, el nombre del proyecto donde sea legible. |
SIXTY_RELEASE | La que más importa. Se recoge automáticamente en Vercel, Render, Railway, Fly, Heroku y GitHub Actions; en cualquier otro sitio, ponla al SHA del commit. Sin ella cada medición cae en un único cubo sin nombre y no se puede hacer ninguna comparación. |
SIXTY_ENDPOINT | Dónde reportar. Por defecto http://localhost:4319, que es correcto en un portátil y erróneo en cuanto la aplicación se sirve a alguien más. |
El resto — intervalo de envío, tasa de muestreo, qué instrumentar — está en el propio README del paquete, que es donde puede seguir siendo cierto según cambia el agente.