コンテンツにスキップ

server

複数マシンやチームメンバーでメトリクスを集約したい場合、agent-telemetry-server を立てて agent-telemetry flush でイベントを送信する経路を有効化できます。送信は append-only なイベント列を OTLP/HTTP Logs(POST /v1/logs)で転送する方式です。サーバ送信は オプトイン で、設定しなければローカル単独利用は従来どおり動きます。基本のローカルセットアップは local を参照してください。

仕様の外部契約は docs/spec.md ## サーバ送信、設計判断は docs/design.md ## サーバ側集約パイプライン を参照。

OSS バックエンドでローカル検証する: Datadog などの credential を用意せず、Collector が backend へ push する構成を手元で試したい場合は、OSS 検証用レシピ deploy/oss-observability/(Mimir / Loki / Grafana)を使えます。cd deploy/oss-observability && docker compose up で起動し、Grafana は http://localhost:13001 で開きます。クライアントは [server](または export target)の endpoint = "http://localhost:4318"encoding = "json"signals = ["logs", "metrics"] で OTel Collector に向け、token は不要です。詳細は同ディレクトリの README を参照してください。

この OSS compose は開発用ローカル可視化専用で、本番非対応です。Grafana は anonymous access、OTLP receiver / Mimir / Loki は無認証のため、compose は全 host port を 127.0.0.1(loopback)に bind しています。公開ネットワークに晒さないこと。本番相当の集約は本ページの Bearer 認証付き中央サーバが担います(本番相当で OSS スタックを使う場合の最低要件は同 README の「ローカル限定・本番非対応」を参照)。

agent-telemetry が公式に配布するのは container imageGo binary のみです:

配布物入手元
Container imageghcr.io/ishii1648/agent-telemetry-server(multi-arch: linux/amd64 + arm64)
Go binaryGitHub Releasesagent-telemetry-server_* archive

Helm / Argo CD / Flux / 素の kubectl といった デプロイ手段 と、StorageClass / IngressClass / cert-manager の有無といった cluster topology は運用者の責務です。本書では kubectl apply -f - できる粒度の 参考 YAML スニペット を 2 種類示すので、自分の cluster に合わせて改変してください。

1. image を取得する

docker pull ghcr.io/ishii1648/agent-telemetry-server:latest
# 本番では tag pin (vX.Y.Z) を推奨
docker pull ghcr.io/ishii1648/agent-telemetry-server:v0.6.0

ローカル動作確認は docker run で完結します:

docker run --rm -p 8443:8443 \
  -v $PWD/agent-telemetry-data:/var/lib/agent-telemetry \
  ghcr.io/ishii1648/agent-telemetry-server:latest --listen :8443

既定 listen は loopback (127.0.0.1:8443) で、コンテナ内 loopback は published port から到達できないため、ここでは --listen :8443(全インターフェイス)で起動しています。サーバはアプリ層の認証を持たない(次節)ので、:8443 で公開する場合は信頼できるネットワークに限定するか前段に proxy を置いてください。

2. 認証境界(ネットワーク到達制御 + proxy)

agent-telemetry-server の ingest endpoint /v1/logsアプリケーション層の認証を持ちません(旧 AGENT_TELEMETRY_SERVER_TOKEN は廃止しました)。信頼境界は次の 2 層で表現します(設計判断は issue 0057 / issue 0058):

  • 既定 bind は loopback (127.0.0.1:8443)。手元のマシン以外から到達させない限り認証は不要です。コンテナ / k8s では published port / ClusterIP から届く必要があるので --listen :8443(全インターフェイス)で起動しますが、その場合は ネットワーク到達制御(VPN / 内部ネットワーク / ClusterIP)で送信元を限定するのが前提です。loopback 以外へ bind するとサーバは起動時に警告ログを出します。
  • インターネット公開する場合は、前段に TLS 終端 + 認証 + レート制限を行う reverse proxy / Ingress を必ず置きます(oauth2-proxy / mTLS / OIDC 等)。/v1/logs は書き込み専用で蓄積データを読み出す API を持たないため誤公開でも保存済みメトリクスの漏えいは起きませんが、無認証のままでは偽イベント注入・DoS・backend コスト増の余地が残ります。

クライアント側に token 設定は不要です。認証付き proxy を前段に置く場合のみ、proxy が要求する credential を [server] token に設定してください(付属サーバ自身は無視します)。

運用前提 — TLS / 認証 / レート制限は proxy 側の責務

agent-telemetry-serverアプリ層の認証を持たない平文 HTTP ingest です(書き込み token は issues/0057 で廃止しました)。--listenインターネットへ直接公開しないでください。公開する場合は必ず reverse proxy / ingress(nginx / Caddy / cloud LB など)の背後に置き、次を proxy 側で担保するのが契約です:

関心事どこで担保するか
TLS 終端proxy / ingress。/v1/logs は平文 HTTP なので、公開経路は必ず TLS の背後に置く(§3 の Ingress 例は cert-manager で TLS を張る前提)
認証proxy / ingress。oauth2-proxy / OIDC / mTLS 等で前段認証を入れる。/v1/logs 自身は認証しない
レート制限proxy / ingress。大量 ingest による DoS / backend コスト増の抑制。binary 内にレート制限は無い
接続元の制限proxy / ingress / NetworkPolicy。到達範囲を信頼ネットワーク(VPN / cluster 内 / 許可 IP)へ絞る

binary 単体で閉じているのは「公開しても安価に効く最小限」だけです:

  • 既定 loopback bind: 既定 --listen127.0.0.1:8443。loopback 以外へ bind すると起動時に警告ログを出す
  • request timeout: slow-body / idle keep-alive 接続が goroutine を無期限に占有しないよう ReadHeaderTimeout=10s / ReadTimeout=60s / WriteTimeout=90s / IdleTimeout=120s を設定済み
  • payload 上限: 1 リクエストを 50 MB(圧縮フレーム・展開後の双方)で上限し、zip-bomb 系入力を弾く

user_id は payload 上の自己申告フィールドで、ingest 到達できるクライアントは他ユーザ偽装やイベント汚染が可能です。per-client identity / token scoping は本 binary の対象外で、isolation を約束するデプロイでの追加要件は issues/0058 を参照してください。VPN / port-forward のみで使う(公開しない)場合は §3 の Ingress ブロックを丸ごと削除して構いません。

3. k8s 参考デプロイ — 最小構成(サーバのみ)

サーバ単体を立て、Grafana は既存環境を使う構成です。下記スニペットを kubectl apply -f - してください。# REPLACE_ME コメントの箇所は cluster ごとに調整します。

---
apiVersion: v1
kind: Namespace
metadata:
  name: agent-telemetry
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: agent-telemetry-data
  namespace: agent-telemetry
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: REPLACE_ME   # REPLACE_ME: cluster の StorageClass 名(`kubectl get storageclass`)
  resources:
    requests:
      storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: agent-telemetry-server
  namespace: agent-telemetry
spec:
  replicas: 1                    # SQLite なので 1 固定。スケールアウトは未対応
  strategy:
    type: Recreate               # WAL を複数 pod が同時に開かないため RollingUpdate ではなく Recreate
  selector:
    matchLabels: {app: agent-telemetry-server}
  template:
    metadata:
      labels: {app: agent-telemetry-server}
    spec:
      containers:
        - name: server
          image: ghcr.io/ishii1648/agent-telemetry-server:latest   # REPLACE_ME: 本番では vX.Y.Z で tag pin
          # ":8443" は全インターフェイス bind。認証は持たないので ClusterIP + ネットワーク
          # 到達制御で送信元を限定する前提(公開する場合は下記 Ingress で proxy 認証を入れる)。
          args: ["--listen", ":8443", "--data-dir", "/var/lib/agent-telemetry"]
          ports:
            - {containerPort: 8443, name: ingest}
          volumeMounts:
            - {name: data, mountPath: /var/lib/agent-telemetry}
          readinessProbe:
            httpGet: {path: /healthz, port: ingest}
          resources:
            requests: {cpu: "50m",  memory: "64Mi"}
            limits:   {cpu: "500m", memory: "256Mi"}
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: agent-telemetry-data
---
apiVersion: v1
kind: Service
metadata:
  name: agent-telemetry-server
  namespace: agent-telemetry
spec:
  type: ClusterIP
  selector: {app: agent-telemetry-server}
  ports:
    - {port: 8443, targetPort: ingest, name: ingest}
---
# REPLACE_ME: 外部公開する場合の Ingress 例。cluster の IngressClass / cert-manager Issuer に合わせて調整。
# サーバは無認証なので、公開する場合はこの Ingress(または別の reverse proxy)で TLS 終端に加え
# 認証(oauth2-proxy / OIDC / mTLS 等)とレート制限を必ず入れること。
# 公開しない場合(VPN / port-forward 経由のみ)はこのブロックをまるごと削除してよい。
# apiVersion: networking.k8s.io/v1
# kind: Ingress
# metadata:
#   name: agent-telemetry-server
#   namespace: agent-telemetry
#   annotations:
#     cert-manager.io/cluster-issuer: REPLACE_ME       # 例: letsencrypt-prod
# spec:
#   ingressClassName: REPLACE_ME                       # 例: nginx
#   tls:
#     - hosts: [telemetry.example.com]
#       secretName: agent-telemetry-tls
#   rules:
#     - host: telemetry.example.com
#       http:
#         paths:
#           - path: /
#             pathType: Prefix
#             backend:
#               service:
#                 name: agent-telemetry-server
#                 port: {number: 8443}

4. k8s 参考デプロイ — Grafana 同居版

サーバと Grafana を 同 pod の sidecar として配置することで、ReadWriteOnce PVC のまま両者で同じ SQLite を共有できます。Grafana の datasource provisioning yaml は grafana/provisioning/datasources/agent-telemetry-docker.yamlそのまま ConfigMap として配るので、ローカル make grafana-up と完全に同じダッシュボードが描画されます。

ConfigMap はリポジトリのファイルから生成します:

# datasource + dashboards provisioning
kubectl create configmap agent-telemetry-grafana-provisioning -n agent-telemetry \
  --from-file=datasources.yaml=grafana/provisioning/datasources/agent-telemetry-docker.yaml \
  --from-file=dashboards.yaml=grafana/provisioning/dashboards/agent-telemetry-docker.yaml \
  --dry-run=client -o yaml | kubectl apply -f -

# dashboard JSON 本体
kubectl create configmap agent-telemetry-grafana-dashboards -n agent-telemetry \
  --from-file=agent-telemetry.json=grafana/dashboards/agent-telemetry.json \
  --dry-run=client -o yaml | kubectl apply -f -

ConfigMap サイズ上限: ConfigMap は etcd の制約から 1 MiB が上限です。dashboard JSON が肥大化した場合は、Grafana sidecar pattern(grafana/helm-charts の sidecar dashboards loader)または initContainer で git clone する形に切り替えてください。

そのうえで以下を kubectl apply -f - します。PVC / Service の最小構成は § 3 と共通なので、ここでは Deployment + Service だけを示します(§ 3 の PVC + Namespace は事前に apply 済みである前提)。

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: agent-telemetry
  namespace: agent-telemetry
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels: {app: agent-telemetry}
  template:
    metadata:
      labels: {app: agent-telemetry}
    spec:
      containers:
        - name: server
          image: ghcr.io/ishii1648/agent-telemetry-server:latest   # REPLACE_ME: 本番では tag pin
          # ":8443" は全インターフェイス bind。認証は持たないので ClusterIP + ネットワーク到達制御が前提。
          args: ["--listen", ":8443", "--data-dir", "/var/lib/agent-telemetry"]
          ports:
            - {containerPort: 8443, name: ingest}
          volumeMounts:
            - {name: data, mountPath: /var/lib/agent-telemetry}
          readinessProbe:
            httpGet: {path: /healthz, port: ingest}

        - name: grafana
          image: grafana/grafana-oss:11.5.2
          ports:
            - {containerPort: 3000, name: http}
          env:
            - {name: GF_INSTALL_PLUGINS,         value: "frser-sqlite-datasource"}
            - {name: GF_AUTH_ANONYMOUS_ENABLED,  value: "true"}
            - {name: GF_AUTH_ANONYMOUS_ORG_ROLE, value: "Viewer"}
          volumeMounts:
            # 同じ PVC を別 path で mount。サーバが /var/lib/agent-telemetry/agent-telemetry.db に書いた
            # SQLite が、Grafana 側からは /var/lib/grafana/agent-telemetry.db として見える
            # (docker-compose と同じ datasource yaml をそのまま流用するため)。
            # Grafana 自身の state(grafana.db / plugins / png)も同 PVC root に並ぶが副作用なし。
            - {name: data, mountPath: /var/lib/grafana}
            - name: provisioning
              mountPath: /etc/grafana/provisioning/datasources/agent-telemetry.yaml
              subPath: datasources.yaml
            - name: provisioning
              mountPath: /etc/grafana/provisioning/dashboards/agent-telemetry.yaml
              subPath: dashboards.yaml
            - {name: dashboards, mountPath: /var/lib/grafana/dashboards}
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: agent-telemetry-data
        - name: provisioning
          configMap:
            name: agent-telemetry-grafana-provisioning
        - name: dashboards
          configMap:
            name: agent-telemetry-grafana-dashboards
---
apiVersion: v1
kind: Service
metadata:
  name: agent-telemetry
  namespace: agent-telemetry
spec:
  type: ClusterIP
  selector: {app: agent-telemetry}
  ports:
    - {port: 8443, targetPort: ingest, name: ingest}
    - {port: 3000, targetPort: http,   name: grafana}

Grafana にブラウザでアクセスする手順は次節 § 5 を参照してください。

5. サーバ DB を Grafana で見る

datasource の uid: agent-telemetry を踏襲しているため、ローカル make grafana-up同じダッシュボード JSON がそのまま動きます。

5.1 同居版 Grafana を Port-forward(§ 4 を deploy 済みの場合)

§ 4 の Grafana 同居版を deploy 済みなら、Service を Port-forward するだけで開けます:

kubectl port-forward -n agent-telemetry svc/agent-telemetry 3000:3000
# → http://localhost:3000 で Grafana にアクセス

NodePort / Ingress / LoadBalancer で外部公開する場合は cluster の慣習に合わせて Service.spec.type を変更してください。

5.2 サーバ DB ファイルを手元にコピーして見る

サーバ側に Grafana を同居させていない場合や、個人検証 / 比較目的でスナップショットを手元で見たい場合。AGENT_TELEMETRY_DB を server data dir 内のファイルに向ければ make grafana-up がそのまま動きます:

# サーバから DB をコピー(k8s の場合の例。VPS / docker 環境ならその慣習で)
kubectl cp -n agent-telemetry agent-telemetry-0:/var/lib/agent-telemetry/agent-telemetry.db /tmp/server-snapshot.db

# ローカル Grafana で開く
make grafana-up AGENT_TELEMETRY_DB=/tmp/server-snapshot.db
# → http://localhost:13000

サーバ DB スキーマはクライアント DB と同一なので、ダッシュボードは無調整で描画されます。

6. クライアント設定

~/.config/agent-telemetry/config.tomlXDG_CONFIG_HOME が設定されていれば $XDG_CONFIG_HOME/agent-telemetry/config.toml)に [server] セクションを追加します。旧バージョンが書き出した ~/.claude/agent-telemetry.toml も fallback として読まれますが、stderr に migration warning が出るので、可能なら ~/.config/ 側に移動してください:

user = "you@example.com"

[server]
endpoint = "https://telemetry.example.com"   # サーバの base URL(パスは含めない)
# token は不要(付属サーバは認証しない)。認証付き proxy を前段に置く場合のみ proxy の credential を設定する

設定を確認:

agent-telemetry flush --dry-run        # 送信対象イベント件数と payload サイズだけ表示
agent-telemetry flush --since-last     # 実送信。未送信イベントのみ

[server] が欠落 / 値が空のときは warning を stderr に出して exit code 0 で終了するため、cron に設定したまま config を取り除いても CI / cron が壊れません。

push 経路について: v0.0.10 までは集計行を POST /v1/metrics で送る agent-telemetry push がありましたが、append-only イベント + OTLP/HTTP Logs への移行([0038])に伴い削除されました。v0.0.10 から移行する場合は、そのバージョンのうちに agent-telemetry migrate-to-events(クライアント)/ agent-telemetry-server migrate-to-events(サーバ)を実行してから新 binary に更新してください。

7. flush の定期起動

agent-telemetry flush --since-last は Stop hook の hot path に乗せず、別途定期起動します(hook が遅延すると agent UX が劣化するため)。exit code は 0 = ok / 1 = error です。配送失敗時は last_flushed_sequence を進めないため、次回 flush で同じ範囲を冪等に再送します(サーバ側 INSERT OR IGNORE で重複排除)。

cron(Linux / macOS)

0 * * * * /usr/local/bin/agent-telemetry flush --since-last >> $HOME/.claude/logs/flush.log 2>&1

launchd plist(macOS)

~/Library/LaunchAgents/dev.agent-telemetry.flush.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>dev.agent-telemetry.flush</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/agent-telemetry</string>
    <string>flush</string>
    <string>--since-last</string>
  </array>
  <key>StartInterval</key>
  <integer>3600</integer>
  <key>StandardOutPath</key>
  <string>/Users/REPLACE_ME/.claude/logs/flush.log</string>
  <key>StandardErrorPath</key>
  <string>/Users/REPLACE_ME/.claude/logs/flush.log</string>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/dev.agent-telemetry.flush.plist

8. 新メトリクス追加時の遡及反映

events モデルでは events テーブルの DDL を変えずに新属性 / 新イベント名を増やせるため、旧設計の「サーバ先行デプロイ → 全クライアント binary 更新 → 全件再送」運用は不要です。schema_hash 不一致でサーバが送信を全停止させる仕組みも廃止されました(events table の DDL は安定で、新メトリクスは新属性の追加で表現できるため)。

# 1. 新属性 / 新イベントを emit するクライアント binary を順次配布
#    (旧クライアントは無変更でも既存 events を送り続ける)

# 2. サーバ binary 側の VIEW 定義を更新(events の新属性を引いて新カラムを生やす)
kubectl set image deployment/agent-telemetry-server \
  server=ghcr.io/ishii1648/agent-telemetry-server:v0.6.0 -n agent-telemetry
kubectl rollout status deployment/agent-telemetry-server -n agent-telemetry

# 3. 既存セッションに新属性を遡及反映したい場合は snapshot イベントを再 emit
#    (sync-db --recheck で agent.transcript.scanned 等が新属性付きで再生成される)
agent-telemetry sync-db --recheck
agent-telemetry flush --since-last

新メトリクスの大半は schema 変更を伴いません。属性は events の JSON に入るため、新属性を増やすだけならクライアント binary を差し替えるだけで済み、サーバ DB は無変更で受け続けます。

VIEW / DDL を変更するときの注意: schema.sql(VIEW 定義・index 等)を変更すると埋め込み schema_hash が変わり、サーバ起動時の schema_meta 比較で不一致になります。現状の EnsureSchema は不一致時に schema.sql を再適用し、その先頭で DROP TABLE events してから作り直すため、サーバ集約 DB の events は一度全消去されます。クライアントはローカル events を保持しているので、復旧は全クライアントで agent-telemetry flush --full を実行して再投入します(INSERT OR IGNORE で冪等)。本番では VIEW 変更をまとめて行い、変更デプロイ前に events を退避(DB バックアップ)するか、全クライアントの flush --full を段取りしてください。events テーブル本体の DDL に互換破壊変更を入れる場合は、新 endpoint(例: /v2/logs)を切る運用とします。