sixty

agents

What each agent tracks

8 agents, one wire format. They write byte-identical sketches and normalize a statement to the same shape, so a service split across two languages is one history rather than two. What differs is what each runtime allows.

Server agents
signalNodePythonGoRuby & RailsPHP
latencylatencyyesyesyesyesyes
self_latencyown codeyesyesyesyesyes
cpumore CPUnoyesnonono
blockedwaitingnoyesnonono
rowsrowsyesyesyesyesyes
fanoutN+1yesyesyesyesyes
round_tripsfetched in batchesyesnononoyes
payloadpayloadyesyesyesyesyes
errorserrorsyesyesyesyesyes
planquery planyesnonoyesyes
repeated_queryrepeated queryyesyesyesyesyes
overfetchover-fetchingyesyesyesyesyes
unboundedno limityesyesyesyesyes
new_errornew erroryesyesyesyesyes
recursionrecursionyesyesyesyesyes
runawaycalled in a loopyesyesyesyesyes
collapsereturns lessyesyesyesyesyes
vanishedno longer usedyesyesyesyesyes
traffic_dropused far lessyesyesyesyesyes
missing_tenancydata not scopedyesyesyesyesyes

Why the gaps are where they are

  • CPU and waiting are Python only. Splitting an operation’s own time into the half that was computing and the half that was queueing needs a per-thread clock, and a span that owns its thread for its whole duration. time.thread_time() gives both. Node’s process.cpuUsage() is process-wide with many async contexts interleaved on one thread; Go’s goroutines migrate between threads. Neither could report a number that was true.
  • Query plans are Postgres, on Node, Ruby and PHP. A generic-plan EXPLAIN, cached per statement and issued outside the caller’s span so it is never measured as their work. MySQL and MongoDB can only explain a query that still has its values in it, so capturing a plan there would mean retaining somebody’s data to compose a command out of it. The same refusal in every agent.
  • Round trips are MongoDB only. The driver fetches the first 101 documents and then as much as fits in 16MB per trip, so the waiting grows with bytes rather than with documents. No SQL driver exposes anything equivalent.
  • Everything else is universal by construction. The measurement core is shared and the collector-side detections — repeated queries, over-fetching, unbounded reads, recursion — read data every server agent already sends. An agent does not opt into them.
Client agents
signalBrowserReact NativeSupabase
latencylatencyyesyesyes
errorserrorsyesyesyes
new_errornew errornonoyes
runawaycalled in a loopyesyesno
render_stormrender loopyesnono
dead_interactiondoes nothingyesnono
client_errorbrowser erroryesyesno
web_vitalpage speedyesnono
stuck_loadingnever loadsyesnono
silent_emptyreturns nothingyesyesyes
auth_failuresaccess refusedyesyesyes

A client agent is an addition rather than an alternative. It sees the half of a request that happens after the server span ends — and, in an app with no server of its own, it is the only thing that sees anything at all.

Found by reading the code

These cannot be measured. A handler that was never attached emits nothing, and an effect that re-runs forever is a property of a dependency array. So they come from the build transform reading the source, and they are ranked by how much production traffic the file they live in actually serves — which is the reason they are here rather than in a linter. ESLint cannot tell a dead button in your checkout from one in a page nobody loads.

  • dead_interactiondoes nothing: users click this and nothing observable happens
  • effect_loopeffect loop: an effect writes state it also depends on — found by reading the code, not by measuring
  • missing_cleanupnever cleaned up: a subscription, interval or listener is created without a teardown
  • exposed_secretexposed key: A password-like key is written into code that gets downloaded by everyone who visits the site. Anyone who looks can copy it and use it as you.
  • insecure_transportunencrypted: This talks to another service over a plain, unencrypted connection. Anything travelling on it can be read or changed along the way.

Every agent

Node

The only agent that instruments your own functions without being asked: a build transform opens a span per exported async function, so a query has a caller to be credited to.

npm install @sixty-sh/node

Python

The only agent that can tell computing from waiting. Every other one reports that your code got slower and stops there — this one says which of the two happened, and they need opposite fixes.

pip install sixty-sh

Go

The install is explicit — a middleware, a driver wrap, and two lines at the top of the functions worth measuring — because Go has no build step to hook and no way to reach the caller's context without being handed it.

go get github.com/andana-to/drift/packages/go

Ruby & Rails

The only install with no step two. The railtie adds the middleware, subscribes to sql.active_record and wraps controller actions on boot — there is no initializer to write.

gem 'sixty'

PHP

The only agent that has to work without a background thread. A PHP worker cannot run a timer and everything it learned dies with the request, so windows are pooled in shared memory and merged — losslessly, or the percentiles would be fiction.

composer require sixty-sh/sixty

Browser

What the server cannot see: how long the page actually took for a real person, which clicks did nothing, and which component re-rendered four hundred times.

npm install @sixty-sh/browser

React Native

An app has no server-side span to compare against, so a refused call or an empty response is only visible from the device. This is the agent that makes it visible.

npm install @sixty-sh/react-native

Supabase

The only agent that knows what the endpoint *is* rather than what it cost — so it can tell a slow query from a policy that refused one.

npm install @sixty-sh/supabase
What each sixty agent tracks — Node, Python, Go, Ruby, browser, mobile