receiver-contract.md 5.1 KB

Telemetry Receiver Contract (analytics.v2)

Endpoint

  • Method: POST
  • Path: /collect
  • Content-Type: application/json
  • Body: AnalyticsEvent[]
  • Success status: any 2xx
  • Deduplication key: eventId

Delivery Semantics

  • At-least-once delivery.
  • Client batches events (default 20) and retries failed requests.
  • Client persists unsent queue to local JSONL on shutdown and restores on startup.
  • Receiver must handle duplicated events idempotently.

Event Schema

type AnalyticsEvent = {
  schemaVersion: "analytics.v2";
  eventId: string;
  eventName: "feature_used" | "error_occurred";
  occurredAt: string; // ISO timestamp
  installationId: string; // installation-level identity (stable across shared server-token)
  instanceId: string; // instance-level identity (distinguishes multi-instance same machine)
  deploymentMode:
    | "source"
    | "docker"
    | "curl_installer"
    | "desktop_installer"
    | "npm_binary"
    | "unknown";
  sessionId?: string; // 24-char hex hash of internal sessionKey (not the raw key)
  commitHash: string; // app/runtime commit hash
  appVersion: string;
  platform: string; // process.platform
  properties: Record<string, unknown>;
};

Breaking Change Notes

  • analytics.v2 adds explicit source attribution: ownerModule, executionKind, and phase.
  • module = "router" now describes router/tokenSaver/judge/fallback health only; real provider requests are reported through module = "session" unless they belong to memory/judge direct LLM calls.
  • module = "session" may include reused agent-loop executions for Always-On, subagents, compaction, or tool-secondary model calls. Use ownerModule and executionKind to isolate ordinary user sessions.
  • Removed field: projectCommitHash.
  • Removed field: projectPath (no filesystem paths in outbound events).
  • sessionId is now a hashed anonymous id, not the raw sessionKey (which may embed paths).
  • error_occurred no longer includes message or stack; only classification fields below.
  • Removed event types: app_started, session_active (DAU uses any feature_used / error_occurred).

feature_used Two-Layer Model

For eventName = "feature_used", properties follows:

type FeatureUsedProperties = {
  /** Business surface being measured. */
  module: "router" | "always_on" | "memory" | "cron_job" | "session";
  /**
   * Business owner of an execution event. For ordinary user chat this is
   * "session"; for Always-On agent loops it is "always_on" even though
   * `module` remains "session".
   */
  ownerModule?: "router" | "always_on" | "memory" | "cron_job" | "session";
  executionKind?:
    | "user_session"
    | "subagent"
    | "always_on"
    | "router_judge"
    | "memory"
    | "cron_job"
    | "compaction"
    | "tool_secondary";
  /** Business phase, e.g. router judge/decision/fallback or Always-On discovery/workspace/execution/report/apply. */
  phase?: string;
  loopStage:
    | "module_event"
    | "loop_start"
    | "model_request"
    | "model_response"
    | "tool_prepare"
    | "tool_call"
    | "permission_check"
    | "loop_end";
  outcome?: "success" | "failed" | "aborted" | "timeout" | "denied";
  errorCategory?:
    | "model_request_error"
    | "permission_error"
    | "tool_param_error"
    | "tool_runtime_error"
    | "tool_result_parse_error"
    | "loop_error"
    | "runtime_error";
  provider?: string;
  model?: string;
  /** HTTP(S) API base from provider config (no userinfo, query, or fragment). */
  providerBaseUrl?: string;
  // plus other module-specific metadata (path-like keys stripped client-side)
  [key: string]: unknown;
};

Session model_request events are emitted after routing, when the real provider request starts (model_eventrequest_started), not at turn submit time.

Ordinary user chat should be queried as:

properties.module = 'session'
AND properties.ownerModule = 'session'
AND properties.executionKind = 'user_session'

Always-On reused agent-loop health should be queried as module = 'session' AND ownerModule = 'always_on', grouped by phase.

error_occurred Properties

  • module: same module space as above plus runtime/ui contexts.
  • ownerModule, executionKind, phase: same attribution semantics as feature_used.
  • loopStage: where the error occurred.
  • errorCategory: normalized category.
  • code: error code (if available).

No message, stack, or caller-supplied metadata is included.

Privacy

  • Outbound events must not contain raw filesystem paths.
  • Property keys matching path-like names (path, cwd, root, etc.) and absolute-path string values are stripped before upload.

Aggregation Guidance

  • Installation-level active users (DAU): distinct installationId per day with any event (feature_used or error_occurred).
  • Instance-level active users: distinct instanceId per day.
  • Module metrics: group by properties.module.
  • Ordinary user session metrics: filter by module=session + ownerModule=session + executionKind=user_session.
  • Loop-stage funnel/error rates: group by properties.module + properties.loopStage + properties.outcome.
  • Session-scoped funnels: group by hashed sessionId.