sixty

サーバーエージェント

Node

頼まなくてもあなた自身の関数を計測する唯一のエージェントです。ビルド時の変換がエクスポートされた非同期関数ごとにスパンを開くので、クエリには帰属先となる呼び出し元ができます。

パッケージ
@sixty-sh/node / npm
動作環境
Node 20 以降。デプロイするどんなサーバープロセスでも。
ソース
sixty-sh/sixty-node

導入方法

A server process you control: functions, HTTP routes and SQL.

導入手順は、あなた向けのチェックリストではなく、すでに開いているコーディングエージェント向けのプロンプトとして書かれています。これは意図的です。どのファイルを編集するかではなく、導入が終わった時点で何が成り立っていなければならないかを述べています。コードをどこに置くかはフレームワーク次第で、置き場所を間違えると静かに失敗するからです。エージェントはリポジトリを読んでそれを判断できますが、ドキュメントの段落にはできません。

同じ文面は、 install_sixty が MCP サーバー 経由で返すものであり、コレクタが次の場所で配信しているものでもあります: /v1/setup?kind=node. 実体は1つだけです。

Node の導入手順(全文)
Install the @sixty-sh/node agent in this service so its functions, HTTP routes
and database queries report to sixty.

Before editing, inspect whether this service makes LLM or agent calls. If it
does, ask the user: "Do you want LLM monitoring?" Do not enable it without
their answer. Core HTTP, function and database monitoring is installed either
way. If they say yes, also follow the AI wiring in step 3; if no, leave every
AI client and call site untouched.

1. Install @sixty-sh/node as a dependency (not a devDependency — it runs in
   production; that is the entire point).

2. Call init() from "@sixty-sh/node" in the server entry point, BEFORE the
   application imports anything else. It patches the database client at call
   time, so a module that already imported "pg", "mysql2", "postgres" or
   "mongodb" above it is not instrumented. In an ESM entry, imports are
   hoisted above statements — so either put the init in its own module
   imported first, or use --import as in step 4.

   Then check which database client this project actually uses. pg, mysql2,
   postgres.js and mongodb are found and patched by init() with nothing
   further to do — including Mongoose, which drives the mongodb driver.

   PRISMA IS THE EXCEPTION and it is the one worth checking for, because
   getting it wrong is invisible. Prisma does not use pg — it runs queries
   through its own engine, so no patch reaches them — and its extension hook
   returns a NEW client instead of modifying the one it was given. So the
   application has to use the wrapped client:

      import { instrumentPrisma } from "@sixty-sh/node"
      export const prisma = instrumentPrisma(new PrismaClient())

   Replace the project's existing PrismaClient export with that one. Skip this
   and everything still reports except the queries, which is the failure that
   looks like success.

3. If LLM monitoring was requested, instrument only the SDKs the project
   actually uses: instrumentOpenAI(client), instrumentAnthropic(client),
   instrumentAISDK({ generateText, streamText }), or langChainCallbacks().
   For other providers and OpenAI-compatible services such as DeepSeek, wrap
   the call with generation({ provider, model }, () => call()). Never attach
   prompts, generated text, tool arguments or tool results; sixty records
   timing, provider/model, token usage and cost metadata only.

4. Turn on function-level tracing. Work out which of these this project can
   use; do not do both:

   a. It has a build step (Next, Vite, webpack, Rollup, esbuild). Wrap the
      config with withSixty() from "@sixty-sh/node/transform", or add the
      matching bundler plugin from that same module. The transform needs
      @babel/core and unplugin, which are peer dependencies — without them it
      emits nothing and says nothing, so add them if they are not there.

   b. It has no build step — it is started with a plain "node src/server.js"
      or "tsx src/server.ts". Add --import @sixty-sh/node/register to the
      start command, which both installs the transform and calls init() early
      enough that step 2 is already satisfied.

   NEXT.JS, SPECIFICALLY. Use (a) for the transform, and start init() from a
   flag rather than from instrumentation.js:

      // sixty.mjs, beside package.json
      import { init } from "@sixty-sh/node"
      init()

      # the deployed start command
      node --import ./sixty.mjs node_modules/next/dist/bin/next start

   instrumentation.js looks like the right hook and is compiled once per
   runtime. If the app has middleware there is an edge compilation of it, and
   the bundler pulls the agent's node:fs, node:http and node:crypto into a
   bundle that cannot read those schemes — the build fails. A NEXT_RUNTIME
   guard inside register() does not help: it runs at runtime, and the
   bundling already happened.

   Change the command the deployed process actually runs, not only the local
   dev script. A Dockerfile CMD, a Procfile, or a platform start command
   overrides package.json and is the one that matters.

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 is picked up automatically on Vercel, Render,
   Railway, Fly, Heroku and GitHub Actions. If this deploys some other way,
   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.
- Do NOT apply the transform to client bundles. It is for server code; in a
  framework that builds both, restrict it to the server build.
- Do NOT add any analytics library, user id, session id, or cookie to what is
  reported. The agent is deliberately anonymous and must stay that way.
- If a hot function must not be traced, put // @sixty-ignore above it rather
  than turning the transform off for the whole file.

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.

秘密鍵が必要です — sixty_sk_ で始まり、サーバー側に留まります。ログイン後、設定ページで発行してください。

何を測るか

シグナル単位意味
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.
round_tripsround trips per readone read now waits on the database many times instead of once
plan—the database chose a different plan for this query

どこに入り込むか

  • node:http のサーバーなら何でも — Express、Fastify、Koa、Hono、Next.js のルートハンドラ — パッチを当てるのはサーバーモジュールであってフレームワークではないので、アダプタは要りません。
  • あなた自身の関数 — ビルド時の変換が、エクスポートされた非同期関数を包みます。これが「エンドポイントが遅くなった」を「この関数がクエリを14本出すようになった」に変えます。

データベース

  • pg — 行数、文の形、そして EXPLAIN によるクエリプラン。
  • mysql2 — 行数と影響行数を query でも execute でも。ストリーミングの結果も含みます。
  • postgres.js — タグ付きテンプレートは文の中に値を入れないので、伏せるものがありません。
  • mongodb — コマンドの形を identity に、ドキュメントを行として、そしてカーソルの往復回数。
  • Prisma — 明示的に:instrumentPrisma(new PrismaClient())。

これだけができること

  • 自動の関数スパン — デコレータも include も、関数の先頭の2行も要りません。他のサーバーエージェントはどれも、測る価値のあるコードに印を付けるようあなたに求めます。
  • Postgres のクエリプラン — 汎用プランの EXPLAIN を文ごとにキャッシュし、呼び出し側のスパンの外で実行するので、その人の仕事として測られることはありません。
  • カーソルの往復回数 — 分割して届く MongoDB の読み取り — 他のどのドライバも見せてくれないシグナルです。

できないこと

  • 同時に走るスパンは AsyncLocalStorage で区別しますが、コネクションプールは既定でそれを崩します。アダプタはプールのコールバックを、それを作った文脈へ結び直します。計測していないドライバは、コネクションを返した相手にクエリを帰属させてしまいます。
  • CPU と待ち時間は分けられません。process.cpuUsage() はプロセス全体のもので、多くの非同期文脈が1つのスレッド上で入り混じるため、どのスパンもその一部を正直に自分のものだと主張できません。

設定

どのエージェントも同じ4つの変数を読みます。そして SIXTY_* が答えるところでは DRIFT_* も引き続き答えます — 製品名は変わりましたが、その名前は他人のデプロイから引き上げてよい類のものではありません。

SIXTY_API_KEYこれがないとエージェントは何もせず、そのことを伝えます。推測もせず、未知のエンドポイントに再試行もせず、例外も投げません。
SIXTY_SERVICEこのサービスを何と呼ぶか。読み取れる場合はプロジェクト名が既定になります。
SIXTY_RELEASEいちばん重要なもの。Vercel、Render、Railway、Fly、Heroku、GitHub Actions では自動で拾われます。それ以外ではコミットの SHA を設定してください。これがないとすべての計測が名前のない1つのバケツに入り、比較は永遠にできません。
SIXTY_ENDPOINTどこへ報告するか。既定は http://localhost:4319で、ノートPCの上では正しく、そのアプリが他人に配信された瞬間に間違いになります。

残り — 送信間隔、サンプリング率、何を計測するか — はパッケージ自身の README にあります。エージェントが変わっても正しいままでいられる場所だからです。

sixty の Node エージェント — 何を測り、どう導入するか