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.
| signal | Node | Python | Go | Ruby & Rails | PHP |
|---|---|---|---|---|---|
latencylatency | yes | yes | yes | yes | yes |
self_latencyown code | yes | yes | yes | yes | yes |
cpumore CPU | no | yes | no | no | no |
blockedwaiting | no | yes | no | no | no |
rowsrows | yes | yes | yes | yes | yes |
fanoutN+1 | yes | yes | yes | yes | yes |
round_tripsfetched in batches | yes | no | no | no | yes |
payloadpayload | yes | yes | yes | yes | yes |
errorserrors | yes | yes | yes | yes | yes |
planquery plan | yes | no | no | yes | yes |
repeated_queryrepeated query | yes | yes | yes | yes | yes |
overfetchover-fetching | yes | yes | yes | yes | yes |
unboundedno limit | yes | yes | yes | yes | yes |
new_errornew error | yes | yes | yes | yes | yes |
recursionrecursion | yes | yes | yes | yes | yes |
runawaycalled in a loop | yes | yes | yes | yes | yes |
collapsereturns less | yes | yes | yes | yes | yes |
vanishedno longer used | yes | yes | yes | yes | yes |
traffic_dropused far less | yes | yes | yes | yes | yes |
missing_tenancydata not scoped | yes | yes | yes | yes | yes |
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’sprocess.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.
| signal | Browser | React Native | Supabase |
|---|---|---|---|
latencylatency | yes | yes | yes |
errorserrors | yes | yes | yes |
new_errornew error | no | no | yes |
runawaycalled in a loop | yes | yes | no |
render_stormrender loop | yes | no | no |
dead_interactiondoes nothing | yes | no | no |
client_errorbrowser error | yes | yes | no |
web_vitalpage speed | yes | no | no |
stuck_loadingnever loads | yes | no | no |
silent_emptyreturns nothing | yes | yes | yes |
auth_failuresaccess refused | yes | yes | yes |
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_interaction— does nothing: users click this and nothing observable happenseffect_loop— effect loop: an effect writes state it also depends on — found by reading the code, not by measuringmissing_cleanup— never cleaned up: a subscription, interval or listener is created without a teardownexposed_secret— exposed 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_transport— unencrypted: 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/nodePython
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-shGo
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/goRuby & 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/sixtyBrowser
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/browserReact 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-nativeSupabase
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