Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(telemetry-py): reliable event delivery and per-install identity#292

Merged
KIvanow merged 4 commits into
masterfrom
fix/py-telemetry-delivery-identity
Jul 2, 2026
Merged

fix(telemetry-py): reliable event delivery and per-install identity#292
KIvanow merged 4 commits into
masterfrom
fix/py-telemetry-delivery-identity

Conversation

@KIvanow

@KIvanow KIvanow commented Jul 1, 2026

Copy link
Copy Markdown
Member

Summary

Product analytics under-reported vs downloads because buffered PostHog events (flushAt=20, flushInterval=10s) only drained on an explicit shutdown() that short-lived consumers never call, and distinct_id was read from the target Valkey ({name}:__instance_id), collapsing every client sharing one Valkey into a single "person".

Changes

  • Flush the *_init event immediately on start (asyncio.to_thread) so the install/active signal lands independent of any exit hook.
  • Register a best-effort atexit flush on the enabled path only; opt-out still returns NOOP and registers nothing.
  • distinct_id is now a per-install UUID persisted at ~/.betterdb/instance_id (honors XDG_STATE_HOME, override BETTERDB_INSTANCE_ID); the Valkey-scoped id is demoted to a deployment_id property for fleet roll-up.
  • agent-memory: add memory_session aggregate (integer counts only) at close/exit and a memory_consolidated event.

Applies to agent-memory-py, retrieval-py, agent-cache-py, semantic-cache-py. TS/npm packages still pending.

Checklist

  • Unit / integration tests added
  • Docs added / updated
  • Roborev review passed — run roborev review --branch or /roborev-review-branch in Claude Code (internal)
  • Competitive analysis done / discussed (internal)
  • Blog post about it discussed (internal)

Note

Medium Risk
Changes how installs are counted in analytics and removes semantic-cache PostHog env overrides; writes a local instance id file unless overridden—operators relying on runtime API keys need to adjust.

Overview
Improves product telemetry accuracy and delivery across the Python BetterDB packages (agent-cache, agent-memory, retrieval, semantic-cache).

Identity: PostHog distinct_id is now a per-machine install UUID (file under ~/.betterdb/instance_id, XDG_STATE_HOME, or BETTERDB_INSTANCE_ID), so many processes sharing one Valkey no longer collapse to one “user.” The former Valkey {name}:__instance_id value is kept as a deployment_id event property for fleet roll-up.

Delivery: Init events are flushed immediately after capture (asyncio.to_thread), and enabled clients register an atexit flush so short-lived scripts that never call shutdown()/close() still drain PostHog’s buffer.

agent-memory: Adds in-process integer-only session counters emitted once as memory_session on close() or process exit, plus a memory_consolidated event; tests cover install id and deployment id behavior.

semantic-cache: Drops runtime BETTERDB_POSTHOG_API_KEY / BETTERDB_POSTHOG_HOST overrides (baked wheel key only), documented in CHANGELOG.md.

Reviewed by Cursor Bugbot for commit 5af5dc2. Bugbot is set up for automated code reviews on this repo. Configure here.

Product analytics under-reported vs downloads because buffered PostHog
events (flushAt=20, flushInterval=10s) only drained on an explicit
shutdown() that short-lived consumers never call, and distinct_id was
read from the target Valkey ({name}:__instance_id), collapsing every
client sharing one Valkey into a single "person".

- Flush the *_init event immediately on start (asyncio.to_thread) so the
  install/active signal lands independent of any exit hook.
- Register a best-effort atexit flush on the enabled path only; opt-out
  still returns NOOP and registers nothing.
- distinct_id is now a per-install UUID persisted at ~/.betterdb/instance_id
  (honors XDG_STATE_HOME, override BETTERDB_INSTANCE_ID); the Valkey-scoped
  id is demoted to a deployment_id property for fleet roll-up.
- agent-memory: add memory_session aggregate (integer counts only) at
  close/exit and a memory_consolidated event.

Applies to agent-memory-py, retrieval-py, agent-cache-py, semantic-cache-py.
TS/npm packages still pending.
Comment thread packages/agent-memory-py/betterdb_agent_memory/analytics.py
When the install-id file can't be read or written (read-only fs,
ephemeral container without a writable home), _get_install_id minted a
fresh UUID on every call, fragmenting one install into many distinct_ids.
Memoize the id in a module-global on the write-failure path so a single
process reports one stable ephemeral identity. Successful file writes are
unaffected. Addresses Cursor Bugbot finding on #292.
Comment thread packages/agent-memory-py/betterdb_agent_memory/memory_store.py
Rely solely on the build-time baked PostHog key, matching the other three
Python packages. The BETTERDB_POSTHOG_API_KEY/HOST runtime overrides were
unused and made key resolution inconsistent across packages.
Comment thread packages/semantic-cache-py/betterdb_semantic_cache/analytics.py
- _capture_session: only arm the one-shot flag once counts are non-zero, so
  an early atexit with zero activity no longer blocks a later close() from
  emitting memory_session.
- close(): flush the analytics queue after capturing the session summary so
  it lands even if shutdown()'s posthog close swallows an error, matching the
  atexit backstop.
- semantic-cache-py CHANGELOG: document the baked-only telemetry key change,
  superseding the stale env-override note in the 0.1.0 entry.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5af5dc2. Configure here.

# Flush the start event immediately so it lands even for processes that
# exit before the flush interval or any exit hook fires. Off-loaded to a
# thread so the network round-trip doesn't stall the event loop.
await asyncio.to_thread(self.flush)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Init failure orphans PostHog client

Medium Severity

_PostHogAnalytics registers an atexit flush in __init__, before init finishes. If init raises (for example when resolving the install id path calls Path.home()), MemoryStore._ensure_analytics_started swaps _analytics to NOOP_ANALYTICS without shutting down the constructed client. The orphaned instance still flushes at exit, while the store skips session atexit registration yet keeps incrementing _session_counts, so memory_session summaries can be lost.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5af5dc2. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged. This only triggers on a published wheel with telemetry enabled and init() actually raising — and nearly everything in init() is internally guarded, leaving only Path.home() failing (no resolvable HOME). Even then the impact is just a leaked idle consumer thread flushing an empty queue; no crash and no wrong events. Far too rare to block this PR — will be folded into the broader telemetry cleanup.

# Persistence failed — hold the id for the rest of this process so
# repeated calls return a stable ephemeral identity.
_ephemeral_install_id = new_id
return new_id

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ephemeral id not process-wide

Medium Severity

When on-disk install id persistence fails, _get_install_id stores the fallback UUID in a module-level _ephemeral_install_id. Each BetterDB Python package defines its own copy of that global, so one process loading both betterdb_agent_memory and betterdb_agent_cache (or retrieval/semantic-cache) can emit PostHog events under multiple distinct_id values despite the docstring promising a single ephemeral per-process identity.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5af5dc2. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged, and intentionally left as-is. Divergence only happens with a read-only home dir AND multiple betterdb packages in one process AND no BETTERDB_INSTANCE_ID pin. Different ephemeral ids per package on the same machine are harmless for our install/active metric, and BETTERDB_INSTANCE_ID already pins it when exactness matters. Too narrow to warrant cross-package env mutation here — will revisit in further telemetry improvements.

@KIvanow KIvanow requested a review from jamby77 July 2, 2026 07:01
@KIvanow KIvanow merged commit 369b025 into master Jul 2, 2026
3 checks passed
@KIvanow KIvanow deleted the fix/py-telemetry-delivery-identity branch July 2, 2026 13:37
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant