Skip to content

telemetry: onboarding funnel instrumentation (install → activation → success → retention) #432

Description

@Pritom14

Goal

Instrument the ReverbCode journey (install → first PR raised → reviewed → revised → merged → returns) so drop-off is visible per install. This is a v1 measurement-only pass: no UX changes, no first-run wizard, no user-visible surface. If it emits an event we couldn't already observe, it belongs here; if it changes user behavior, it doesn't.

Locked definitions

  • Activation = first PR raised on this install
  • Success = first PR merged on this install
  • Retention magic number = 4 returns (distinct calendar days after install day)
  • Scope = app-first (Electron). CLI joins later on the same events by sharing the install_id file.

12-stage funnel

# Stage Event Emitter
1 acquire (off-product)
2 install ao.daemon.started (pre-existing) daemon boot
3 first launch ao.app.active + is_first_launch=true renderer
4 prereqs ready ao.onboarding.prereqs_checked + gated prereqs_ready daemon boot-goroutine
5 first project ao.onboarding.first_project_added (pre-existing) session svc
6 first session ao.onboarding.first_session_spawned (pre-existing) session svc
7 first agent output ao.session.first_agent_output lifecycle manager
8 first PR raised (ACTIVATION) ao.session.pr_raised + first_pr_raised CDC subscriber
9 PR reviewed ao.session.pr_reviewed + first_pr_reviewed CDC subscriber
10 changes made ao.session.pr_revised + first_pr_revised CDC subscriber
11 PR merged (SUCCESS) ao.session.pr_merged + first_pr_merged CDC subscriber
12 returns (RETENTION 4x) ao.app.returned with return_count, is_retained, days_since_install renderer

first_* events are the once-per-install onboarding milestones used for clean funnel math in PostHog. Per-session ao.session.* events fire every time the fact occurs (multiple PRs raised, multiple reviews, etc.).

User flows being measured

  1. Cold-start install → activation. User installs the app → app supervisor launches daemon on first run → prereqs probe emits prereqs_checked → user adds first project → spawns first session → agent emits first output → agent opens a PR. Every step is a checkpoint; drop-off between any two is now visible.

  2. Review-and-revise loop. Reviewer approves or requests changes → pr_reviewed; agent addresses feedback and the review thread is resolved → pr_revised. Same signals fire on every subsequent PR too, so the loop's throughput is measurable, not just its first traversal.

  3. Success. PR is merged → pr_merged. First merge per install → first_pr_merged.

  4. Retention. App is opened on a new calendar day after install → ao.app.returned with a return_count. When return_count >= 4 on any launch, is_retained=true flips permanently.

Once-per-install gating

The CDC subscriber runs off pr_created / pr_updated / pr_review_thread_resolved broadcaster events, which carry no prior-state history. To avoid re-emitting first_* events after a daemon restart, a durable file-based milestoneStore at <dataDir>/telemetry_milestones.json records the first-claim per key. Dedup keys used:

  • first_pr_raised / first_pr_merged / first_pr_reviewed / first_pr_revised — one per install
  • pr_merged:<url> — one pr_merged per PR (later pr_updated events on merged PRs are ignored)
  • pr_reviewed:<url>:<decision> — one pr_reviewed per (PR, verdict) pair
  • pr_revised:<pr>:<thread> — one pr_revised per resolved review thread
  • prereqs_ready — one per install

The renderer's return/retention counter uses its own file (<dataDir>/telemetry_app_launches.json) so the daemon never writes it and it is immune to install-id races.

Checkpoint pointers (where the events are emitted)

Backend (Go daemon):

  • backend/internal/daemon/onboarding_cdc.go — CDC subscriber: PR-lifecycle events + first_* milestones
  • backend/internal/daemon/onboarding_prereqs.go — boot-goroutine probe: git / tmux (POSIX) / claude|codex / gh auth
  • backend/internal/lifecycle/manager.gofirstAgentOutputEvent, fires once per spawn from ApplyActivitySignal
  • backend/internal/daemon/daemon.go — wires startOnboardingCDC and emitPrereqsTelemetry into Run()

Frontend (Electron):

  • frontend/src/shared/telemetry.ts — launch state (installDay, distinctActiveDays), computeLaunchUpdate, RETENTION_MAGIC_NUMBER = 4, extra bootstrap fields (isFirstLaunch, isReturnDay, returnCount, isRetained, daysSinceInstall)
  • frontend/src/renderer/lib/telemetry.ts — emits ao.app.active with is_first_launch and ao.app.returned on new-day launches

Allowlist (property filtering for PostHog):

  • backend/internal/adapters/telemetry/posthog.goremotePayloadAllowlist entries for every new event. Events not listed still send with base props but the custom payload is dropped.

Storage

  • Shared distinct_id: <dataDir>/telemetry_install_id (daemon writes/reads; renderer reads via bootstrap). Ensures backend and frontend events are one PostHog person.
  • Once-per-install milestone store: <dataDir>/telemetry_milestones.json (daemon-owned).
  • Return/retention counter: <dataDir>/telemetry_app_launches.json (renderer-owned).

Verification

  • Unit tests: manager_test.go (first_agent_output), onboarding_cdc_test.go (all CDC paths + dedup), posthog_test.go (allowlist entries), telemetry.test.ts × 2 (launch-state transitions + renderer bootstrap).
  • E2E: onboarding_cdc_e2e_test.go — drives real SQLite store → triggers → CDC poller → broadcaster → subscriber → sink.
  • Packaged build: npm run make succeeded; all six funnel strings present in Contents/Resources/daemon/ao.
  • Install-time verification via isolated-HOME sandbox: HOME=$(mktemp -d) AO_PORT=<free> AO_TELEMETRY_REMOTE=posthog … daemon — fresh install id, fresh DB, real PostHog project, ao.daemon.started + ao.onboarding.prereqs_checked observed both in local telemetry_event and remote sink with no rejection warnings.
  • Not yet verified live: PR-stage events (raised/merged/reviewed/revised, first_agent_output) require real agent + GitHub interaction. Covered by unit + E2E only.

PostHog funnel steps (once configured)

Product Analytics → New Insight → Funnel. Use first_* events for clean per-install math. 14-30d window (merges lag). Retention magic#4 = separate Retention insight, not a funnel step.

  1. ao.renderer.loaded
  2. ao.onboarding.prereqs_ready
  3. ao.onboarding.first_project_added
  4. ao.onboarding.first_session_spawned
  5. ao.session.first_agent_output
  6. ao.onboarding.first_pr_raised (ACTIVATION)
  7. ao.onboarding.first_pr_reviewed
  8. ao.onboarding.first_pr_revised
  9. ao.onboarding.first_pr_merged (SUCCESS)

Non-goals (v1)

  • No first-run wizard or UX change.
  • No CLI-side emitters (CLI joins later on the same install_id / event names).
  • No dashboard build in this PR — configuration in PostHog is a follow-up once dashboard access lands.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions