telemetry

package
v0.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 16, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Index

Constants

View Source
const (
	EventCommandStart    = "command_start"
	EventCommandComplete = "command_complete"
	EventCommandError    = "command_error"
	EventSessionStart    = "session_start"
	EventSessionEnd      = "session_end"
	EventGuidanceFetch   = "guidance_fetch"
	EventAuthSuccess     = "auth_success"
	EventAuthFailure     = "auth_failure"

	// agent-specific events
	EventGuidanceFetchError = "guidance_fetch_error" // auth, 404, rate limit errors
	EventAttributionShown   = "attribution_shown"    // attribution footer displayed
	EventPrimeExcessive     = "prime_excessive"      // prime called more than threshold times

	// daemon events (sent directly from CLI when daemon unavailable)
	EventDaemonStartFailure = "daemon:start_failure" // daemon failed to start

	// coworker events
	EventCoworkerLoad = "coworker_load" // coworker/subagent loaded into context
)

EventType constants for telemetry events

Variables

This section is empty.

Functions

This section is empty.

Types

type Batch

type Batch struct {
	Events    []Event   `json:"events"`
	CLIVer    string    `json:"cli_version"`
	OS        string    `json:"os"`
	Arch      string    `json:"arch"`
	Timestamp time.Time `json:"batch_timestamp"`
}

Batch represents a batch of events for efficient transmission

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client handles telemetry collection and transmission.

BEST EFFORT DESIGN: Telemetry is intentionally lossy. We prioritize:

  1. CLI responsiveness - never block user operations
  2. Simplicity - no retries, no delivery guarantees
  3. Privacy - easy opt-out, no PII collected

Events may be lost and that's OK - this is for aggregate analytics.

FUTURE: Consider moving telemetry transmission to a separate daemon process. A daemon would provide more reliable delivery without impacting CLI responsiveness, and could handle retries, backoff, and batching more intelligently. For now, disk-based queueing with lazy flush is simpler and sufficient.

func NewClient

func NewClient(sessionID string, opts ...ClientOption) *Client

NewClient creates a new telemetry client. Telemetry is enabled by default unless user has opted out.

func (*Client) Flush

func (c *Client) Flush()

Flush sends all queued events immediately. Non-blocking: spawns goroutine for transmission.

func (*Client) GetStats

func (c *Client) GetStats() Stats

GetStats returns a copy of current session statistics

func (*Client) IsEnabled

func (c *Client) IsEnabled() bool

IsEnabled returns whether telemetry is enabled

func (*Client) ResetDaemonFailureRateLimit

func (c *Client) ResetDaemonFailureRateLimit()

ResetDaemonFailureRateLimit resets the backoff after a successful daemon start. Call this when daemon starts successfully to reset exponential backoff.

func (*Client) SendToDaemon

func (c *Client) SendToDaemon(eventType string, props map[string]any)

SendToDaemon sends a telemetry event to the daemon via IPC (fire-and-forget). This is the preferred method when daemon is running, as it's non-blocking and allows the daemon to handle batching and transmission. Silently fails if daemon is not running or IPC fails.

func (*Client) Start

func (c *Client) Start()

Start begins background processing of telemetry events. Should be called once at CLI startup. If file queue has pending events that need flushing, spawns a background POST.

func (*Client) Stop

func (c *Client) Stop()

Stop gracefully shuts down the telemetry client. Flushes any remaining queued events with a short timeout.

func (*Client) Track

func (c *Client) Track(event Event)

Track queues an event for transmission. This is non-blocking and will never fail from the caller's perspective. If a file queue is configured, events are persisted to disk for reliability.

func (*Client) TrackAsync

func (c *Client) TrackAsync(event Event)

TrackAsync sends a single event asynchronously without queuing. Use for one-off events that should be sent immediately. Fire-and-forget: errors are silently discarded.

func (*Client) TrackCommand

func (c *Client) TrackCommand(command string, duration time.Duration, success bool, errorCode string)

TrackCommand is a convenience method for tracking command execution. Non-blocking - safe to call in hot paths.

func (*Client) TrackDaemonStartFailure

func (c *Client) TrackDaemonStartFailure(errorType, errorMsg string)

TrackDaemonStartFailure reports daemon start failures directly to the telemetry API.

IMPORTANT: This is the ONLY case where the ox CLI sends telemetry directly to the API. All other telemetry goes through the daemon via IPC. This exception exists because daemon start failures occur before the daemon is running, so we cannot use the daemon to report them.

This method:

  • Respects user opt-out (DO_NOT_TRACK=1, SAGEOX_TELEMETRY=false, or config setting)
  • Uses rate limiting with exponential backoff (1min, 2min, 4min... up to 32min)
  • Prevents swarms of failure metrics during persistent daemon issues
  • Sends asynchronously to never block CLI operations

type ClientOption

type ClientOption func(*Client)

ClientOption configures a Client

func WithBaseURL

func WithBaseURL(baseURL string) ClientOption

WithBaseURL sets a custom base URL

func WithEnabled

func WithEnabled(enabled bool) ClientOption

WithEnabled explicitly enables/disables telemetry

func WithHTTPClient

func WithHTTPClient(client *http.Client) ClientOption

WithHTTPClient sets a custom HTTP client

func WithProjectRoot

func WithProjectRoot(projectRoot string) ClientOption

WithProjectRoot sets the project root for disk-based event queueing. Events are persisted to .sageox/cache/telemetry.jsonl and POSTed lazily. Also loads repo_id from project config for event context.

type Event

type Event struct {
	Type      string            `json:"type"`
	Timestamp time.Time         `json:"timestamp"`
	SessionID string            `json:"session_id,omitempty"`
	AgentID   string            `json:"agent_id,omitempty"`
	Command   string            `json:"command,omitempty"`
	Duration  int64             `json:"duration_ms,omitempty"`
	Success   bool              `json:"success"`
	ErrorCode string            `json:"error_code,omitempty"`
	Metadata  map[string]string `json:"metadata,omitempty"`

	// context fields for all events
	RepoID      string `json:"repo_id,omitempty"`      // repository ID from .sageox/config.json
	APIEndpoint string `json:"api_endpoint,omitempty"` // API endpoint used for this event

	// agent-specific fields for guidance tracking
	Path      string `json:"path,omitempty"`       // guidance path fetched
	AgentType string `json:"agent_type,omitempty"` // detected agent (from --agent flag, AGENT_ENV, or native detection)
	Model     string `json:"model,omitempty"`      // claude-3.5-sonnet, gpt-4, etc.
	OxSID     string `json:"oxsid,omitempty"`      // session ID from ox agent prime

	// session tracking fields
	PrimeCallCount int `json:"prime_call_count,omitempty"` // number of times prime was called in this session

	// coworker tracking fields
	CoworkerName  string `json:"coworker_name,omitempty"`  // name of coworker loaded
	CoworkerModel string `json:"coworker_model,omitempty"` // model used by coworker
}

Event represents a telemetry event to be sent to the API

type FileQueue

type FileQueue struct {
	// contains filtered or unexported fields
}

FileQueue provides disk-based event queueing for telemetry.

BEST EFFORT: Telemetry is intentionally "fire and forget". Events may be lost due to:

  • CLI crash before flush
  • Network unavailability during POST
  • File truncation on overflow (>1MB)
  • Disk write failures (silently ignored)

This is acceptable - telemetry is for aggregate analytics, not critical data. We optimize for CLI responsiveness over delivery guarantees.

Events are appended to a JSONL file and POSTed lazily on subsequent CLI invocations. This keeps each CLI command fast (no blocking network calls) while ensuring events are eventually transmitted when conditions allow.

FUTURE: Consider moving telemetry transmission to a separate daemon process. A daemon would provide more reliable delivery without impacting CLI responsiveness, and could handle retries, backoff, and batching more intelligently. For now, the lazy flush approach is simpler and sufficient for our needs.

func NewFileQueue

func NewFileQueue(projectRoot string) *FileQueue

NewFileQueue creates a new file-based event queue. The queue file is stored at .sageox/cache/telemetry.jsonl in the project root. Returns nil if projectRoot is empty (telemetry disabled without project context).

func (*FileQueue) Append

func (q *FileQueue) Append(event Event) error

Append adds an event to the queue file. This is a fast, append-only operation that does not block on network I/O. Returns nil on success; errors are non-fatal (telemetry should never break the CLI).

func (*FileQueue) Count

func (q *FileQueue) Count() int

Count returns the number of queued events.

func (*FileQueue) Path

func (q *FileQueue) Path() string

Path returns the path to the queue file.

func (*FileQueue) ReadAll

func (q *FileQueue) ReadAll() ([]Event, error)

ReadAll reads all queued events from the file. Returns an empty slice if the file doesn't exist or is empty.

func (*FileQueue) ShouldFlush

func (q *FileQueue) ShouldFlush() bool

ShouldFlush returns true if the queue should be flushed. Triggers when: event count > threshold OR oldest event > age threshold.

func (*FileQueue) Truncate

func (q *FileQueue) Truncate() error

Truncate removes all events from the queue file. Called after successful POST to clear transmitted events.

type Stats

type Stats struct {
	SessionID     string
	CommandCount  int
	ErrorCount    int
	TotalDuration time.Duration
	CommandCounts map[string]int
	FirstCommand  time.Time
	LastCommand   time.Time
}

Stats holds aggregated session statistics

func NewStats

func NewStats(sessionID string) *Stats

NewStats creates a new Stats instance

func (*Stats) RecordCommand

func (s *Stats) RecordCommand(command string, duration time.Duration, success bool)

RecordCommand records a command execution

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL