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 image と Go binary のみです:
| 配布物 | 入手元 |
|---|---|
| Container image | ghcr.io/ishii1648/agent-telemetry-server(multi-arch: linux/amd64 + arm64) |
| Go binary | GitHub Releases の agent-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: 既定
--listenは127.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.toml(XDG_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>&1launchd 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.plist8. 新メトリクス追加時の遡及反映
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)を切る運用とします。