sixty

agent serveur

Go

L’installation est explicite — un middleware, une enveloppe de pilote, et deux lignes en tête des fonctions qui méritent d’être mesurées — parce que Go n’a pas d’étape de build où s’accrocher ni de moyen d’atteindre le contexte de l’appelant sans qu’on le lui passe.

paquet
github.com/sixty-sh/sixty-go sur Go modules
tourne sur
Go 1.22 ou plus récent. Aucune dépendance, ni dans le go.sum qu’il ajoute.
source
sixty-sh/sixty-go

L’installer

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.

L’installation est écrite comme un prompt pour l’agent de code que vous avez déjà ouvert, pas comme une liste de tâches pour vous. C’est délibéré : elle nomme ce qui doit être vrai une fois l’installation terminée plutôt que les fichiers à modifier, parce que l’endroit où va le code dépend du framework et que le mettre au mauvais endroit échoue silencieusement. Un agent peut lire votre dépôt et le déduire ; un paragraphe sur une page de documentation, non.

Le même texte est ce que renvoie install_sixty via le serveur MCP et ce que le collecteur sert à /v1/setup?kind=go. Il n’en existe qu’une seule copie.

l’installation Go, en entier
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.

Il lui faut une clé secrète — elle commence par sixty_sk_ et reste côté serveur. Générez-en une sur la page Réglages une fois connecté.

Ce qu’il mesure

signalunitéce que cela veut dire
rowsrows per callthis query returns more rows than it used to
fanoutqueries per callthis operation now issues more database calls per invocation — an N+1
latencyms per callthis operation takes longer end to end than it used to
self_latencyms per callthe time spent in this function itself got longer — its children did not
payloadbytes per callthe serialized result of this operation got bigger
errorserror ratea larger fraction of calls are throwing
runawaycalls per minutethis operation is being called far more often than anything triggers it
repeated_querytimes per requestthe identical query runs several times within one request
overfetchrows per callfar more rows are fetched than the code appears to use
unboundedrows per callthis query has no upper bound on what it can return
recursionlevels deepthis operation calls itself, deeper than it should
new_erroroccurrencesan 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.

Où il s’accroche

  • net/http — sixty.Middleware enveloppe n’importe quel handler, y compris le mux à motifs de Go 1.22, chi, gorilla et echo.
  • Noms de route — sixty.SetRoute(r, "/orders/{id}") là où le routeur connaît le motif et le chemin non.
  • Vos propres fonctions — ctx, done := sixty.Start(ctx, "orders.List"); defer done() — deux lignes, et le contexte doit circuler.

Bases de données

  • database/sql — N’importe quel pilote. L’agent enveloppe le pilote plutôt que la connexion, compte les lignes au fur et à mesure que l’appelant les parcourt, et reproduit chaque interface optionnelle que le vrai pilote implémente.

Ce que seul celui-ci fait

  • Un module sans dépendances — Il n’ajoute rien au go.sum. Ceci est chargé dans les binaires de production d’autres gens, et une dépendance serait un conflit de versions causé par un outil de surveillance.
  • Des lignes comptées à la lecture — Pas depuis une tranche renvoyée — database/sql n’en a pas. Le span se ferme quand les lignes se ferment, donc une requête dont les lignes ne sont jamais fermées remonte tard.

Ce qu’il ne peut pas faire

  • Le contexte doit circuler. Une goroutine à qui l’on ne passe pas le ctx fait son travail en dehors de l’opération, ce qui est correct pour du travail de fond et faux pour un fan-out que vous vouliez mesurer. Il n’existe pas de version à contexte ambiant et nous n’en construirons pas.
  • Les plans de requête ne sont pas capturés.
  • Le CPU et l’attente ne sont pas séparés. Les goroutines migrent entre threads, donc une horloge par thread ne décrit pas un span.

Configuration

Chaque agent lit les mêmes quatre variables, et DRIFT_* répond toujours partout où SIXTY_* répond — le produit a été renommé, et ce nom ne nous appartient pas au point de le retirer des déploiements des autres.

SIXTY_API_KEYSans elle l’agent reste inerte et le dit. Il ne devine jamais, ne réessaie jamais contre un point d’entrée inconnu, et ne lève jamais d’exception.
SIXTY_SERVICEComment appeler ce service. Par défaut le nom du projet là où il est lisible.
SIXTY_RELEASELa plus importante. Récupérée automatiquement sur Vercel, Render, Railway, Fly, Heroku et GitHub Actions ; partout ailleurs, mettez-y le SHA du commit. Sans elle, chaque mesure atterrit dans un unique seau sans nom et aucune comparaison n’est jamais possible.
SIXTY_ENDPOINTOù remonter. Par défaut http://localhost:4319, ce qui est juste sur un portable et faux dès l’instant où l’application est servie à quelqu’un d’autre.

Le reste — intervalle d’envoi, taux d’échantillonnage, quoi instrumenter — est dans le README du paquet lui-même, là où il peut rester vrai à mesure que l’agent change.

L’agent Go de sixty — ce qu’il mesure et comment l’installer