Documentation
¶
Index ¶
- Constants
- type Batch
- type Client
- func (c *Client) Flush()
- func (c *Client) GetStats() Stats
- func (c *Client) IsEnabled() bool
- func (c *Client) ResetDaemonFailureRateLimit()
- func (c *Client) SendToDaemon(eventType string, props map[string]any)
- func (c *Client) Start()
- func (c *Client) Stop()
- func (c *Client) Track(event Event)
- func (c *Client) TrackAsync(event Event)
- func (c *Client) TrackCommand(command string, duration time.Duration, success bool, errorCode string)
- func (c *Client) TrackDaemonStartFailure(errorType, errorMsg string)
- type ClientOption
- type Event
- type FileQueue
- type Stats
Constants ¶
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:
- CLI responsiveness - never block user operations
- Simplicity - no retries, no delivery guarantees
- 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) 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 ¶
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 ¶
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 ¶
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 ¶
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 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 ¶
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 ¶
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) ReadAll ¶
ReadAll reads all queued events from the file. Returns an empty slice if the file doesn't exist or is empty.
func (*FileQueue) ShouldFlush ¶
ShouldFlush returns true if the queue should be flushed. Triggers when: event count > threshold OR oldest event > age threshold.