Commit e99991f
authored
🤖 feat: emit compaction_completed telemetry (#1201)
Backend now emits a `compaction_completed` telemetry event (PostHog)
after successful history compaction, including privacy-preserving
(base-2 rounded) token bucket metrics.
Also adds typed payload + oRPC schema validation and unit coverage for
the backend emission.
---
<details>
<summary>📋 Implementation Plan</summary>
# 🤖 Compaction completed telemetry (PostHog)
## Goals
- Capture a dedicated telemetry event whenever a history **compaction
completes successfully**.
- Include **token deltas** so we can quantify effectiveness:
- **starting token count** (pre-compaction)
- **new token count** (post-compaction summary)
- Send data to **PostHog** so we can later:
- build dashboards (compaction size distribution, reduction ratios)
- segment by model / compaction type
- trigger in-app UX (e.g., show a feedback survey after compaction)
## Non-goals (for this change)
- No new UI (survey modal, toast, etc.).
- No raw user content / summary text ever leaves the machine.
- No changes to compaction behavior itself.
---
## Recommended approach (backend-only capture in
`CompactionHandler.handleCompletion()`)
**Why:** Compaction already happens in the main process, and you
explicitly want the backend to be the sole emitter (no renderer
dependency).
**Net new LoC (product code):** ~120–180
### 1) Define a new telemetry event payload (transparency + typing)
Add a new payload type + union member:
- `src/common/telemetry/payload.ts`
- `export interface CompactionCompletedPayload { ... }`
- extend `TelemetryEventPayload` with:
- `{ event: "compaction_completed"; properties:
CompactionCompletedPayload }`
Suggested properties (privacy-preserving, base-2 rounded):
- `model: string`
- `duration_b2: number` (seconds)
- `input_tokens_b2: number` (proxy for pre-compaction history size)
- `output_tokens_b2: number` (proxy for post-compaction history size)
- `compaction_source: "manual" | "idle"`
**Token source of truth:** prefer provider-reported usage from the
compaction stream metadata (same tokenizer/model as the compaction):
- `inputTokens` = `event.metadata.contextUsage?.inputTokens ??
event.metadata.usage?.inputTokens ?? 0`
- `outputTokens` = `event.metadata.contextUsage?.outputTokens ??
event.metadata.usage?.outputTokens ?? 0`
### 2) Keep Zod telemetry schema in sync (even if the renderer never
emits this event)
- `src/common/orpc/schemas/telemetry.ts`
- add `CompactionCompletedPropertiesSchema`
- add `{ event: z.literal("compaction_completed"), properties: ... }` to
`TelemetryEventSchema`
### 3) Emit from the backend after successful compaction
- `src/node/services/compactionHandler.ts`
- Extend `CompactionHandlerOptions` with optional `telemetryService?:
TelemetryService`.
- After `performCompaction(...)` succeeds (and after the dedupe check),
call:
- `telemetryService?.capture({ event: "compaction_completed",
properties: { ... } })`
- Apply privacy rounding using `roundToBase2`
(`src/common/telemetry/utils.ts`).
- `compaction_source` should be derived from the already-existing
`isIdleCompaction` check.
- Ensure the event is emitted **once** per compaction-request (reuse
`processedCompactionRequestIds`).
- Wire the TelemetryService through:
- `WorkspaceService` → `new AgentSession({ telemetryService: ... })`
- `AgentSession` → `new CompactionHandler({ telemetryService: ... })`
- Keep the option optional to avoid rewriting unit tests that don’t care
about telemetry.
<details>
<summary>Alternative approach (renderer-emitted) — NOT recommended per
requirements</summary>
The existing telemetry pipeline is renderer → oRPC → backend → PostHog.
While that’s usually fine (and still avoids classic adblocker problems
because it’s not PostHog JS), you explicitly want *only* backend
emission, so this plan does not rely on `trackEvent()` from the
renderer.
</details>
---
## Tests + validation
- Unit tests (recommended): extend
`src/node/services/compactionHandler.test.ts`
- Inject a fake `telemetryService` with a spy `capture()`.
- Assert `compaction_completed` is captured exactly once on successful
compaction.
- Assert it is **not** captured for non-compaction stream-end events.
- Repo checks:
- `make fmt-check && make lint && make typecheck && make test`
- Manual smoke test:
- Run packaged, or set `MUX_ENABLE_TELEMETRY_IN_DEV=1`.
- Trigger `/compact`.
- Confirm `compaction_completed` appears in PostHog with rounded
properties.
---
## PostHog: create dashboard + insights (via MCP tools)
**Net new LoC (product code):** 0 (PostHog objects only)
### 1) Identify the correct PostHog org/project
Use the MCP tools to confirm we’re operating in the PostHog project that
receives mux telemetry:
- `posthog_organizations-get`
- `posthog_projects-get`
### 2) Create a dashboard
- `posthog_dashboard-create` with:
- `name`: `Mux • Compaction`
- `description`: `Compaction completion volume and effectiveness`
- `pinned`: `true` (optional)
- `tags`: `["mux", "compaction"]`
### 3) Create insights (query-run → create → attach to dashboard)
For each insight:
1) `posthog_query-run` (validate the query returns successfully)
2) `posthog_insight-create-from-query`
3) `posthog_insight-update` to attach it to the new dashboard (set
`dashboard: <id>`)
Suggested insights:
- **Trend:** daily count of `compaction_completed` (last 30 days)
- **Breakdown:** `compaction_completed` by `compaction_source` (manual
vs idle)
- **Table (HogQL):** effectiveness by `model` and `compaction_source`
<details>
<summary>Example HogQL for the effectiveness table</summary>
```sql
SELECT
properties['model'] AS model,
properties['compaction_source'] AS compaction_source,
count() AS compactions,
avg(toFloat(properties['input_tokens_b2'])) AS avg_input_tokens_b2,
avg(toFloat(properties['output_tokens_b2'])) AS avg_output_tokens_b2,
avg(toFloat(properties['output_tokens_b2']) / nullIf(toFloat(properties['input_tokens_b2']), 0)) AS avg_ratio
FROM events
WHERE event = 'compaction_completed'
AND timestamp >= now() - INTERVAL 30 DAY
GROUP BY model, compaction_source
ORDER BY compactions DESC
```
</details>
### 4) Verify dashboard wiring
- `posthog_dashboard-get` (ensure the dashboard exists)
- `posthog_insight-query` for each insight (ensure the query executes;
it may legitimately be empty until events arrive)
---
## Future follow-ups (not in scope)
- **Survey after compaction:** gate a post-compaction prompt with a
PostHog feature flag (evaluated via the existing backend
`ExperimentsService`), then record survey answers to PostHog as a
separate event.
- Consider migrating other telemetry events to backend-only for
consistency.
</details>
---
_Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `high`_
Signed-off-by: Thomas Kosiewski <[email protected]>1 parent 36b88ce commit e99991f
File tree
7 files changed
+105
-0
lines changed- src
- common
- orpc/schemas
- telemetry
- node/services
7 files changed
+105
-0
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
81 | 89 | | |
82 | 90 | | |
83 | 91 | | |
| |||
125 | 133 | | |
126 | 134 | | |
127 | 135 | | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
128 | 140 | | |
129 | 141 | | |
130 | 142 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
125 | 141 | | |
126 | 142 | | |
127 | 143 | | |
| |||
211 | 227 | | |
212 | 228 | | |
213 | 229 | | |
| 230 | + | |
214 | 231 | | |
215 | 232 | | |
216 | 233 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
33 | 34 | | |
34 | 35 | | |
35 | 36 | | |
| |||
89 | 90 | | |
90 | 91 | | |
91 | 92 | | |
| 93 | + | |
92 | 94 | | |
93 | 95 | | |
94 | 96 | | |
| |||
147 | 149 | | |
148 | 150 | | |
149 | 151 | | |
| 152 | + | |
150 | 153 | | |
151 | 154 | | |
152 | 155 | | |
| |||
169 | 172 | | |
170 | 173 | | |
171 | 174 | | |
| 175 | + | |
172 | 176 | | |
173 | 177 | | |
174 | 178 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
8 | 10 | | |
9 | 11 | | |
10 | 12 | | |
| |||
117 | 119 | | |
118 | 120 | | |
119 | 121 | | |
| 122 | + | |
| 123 | + | |
120 | 124 | | |
121 | 125 | | |
122 | 126 | | |
| |||
125 | 129 | | |
126 | 130 | | |
127 | 131 | | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
128 | 137 | | |
129 | 138 | | |
130 | 139 | | |
131 | 140 | | |
132 | 141 | | |
133 | 142 | | |
| 143 | + | |
134 | 144 | | |
135 | 145 | | |
136 | 146 | | |
| |||
160 | 170 | | |
161 | 171 | | |
162 | 172 | | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
163 | 204 | | |
164 | 205 | | |
165 | 206 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
| 13 | + | |
12 | 14 | | |
13 | 15 | | |
14 | 16 | | |
| |||
20 | 22 | | |
21 | 23 | | |
22 | 24 | | |
| 25 | + | |
23 | 26 | | |
24 | 27 | | |
25 | 28 | | |
| |||
37 | 40 | | |
38 | 41 | | |
39 | 42 | | |
| 43 | + | |
40 | 44 | | |
41 | 45 | | |
42 | 46 | | |
| |||
50 | 54 | | |
51 | 55 | | |
52 | 56 | | |
| 57 | + | |
53 | 58 | | |
54 | 59 | | |
55 | 60 | | |
| |||
130 | 135 | | |
131 | 136 | | |
132 | 137 | | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
133 | 156 | | |
134 | 157 | | |
135 | 158 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
116 | 116 | | |
117 | 117 | | |
118 | 118 | | |
| 119 | + | |
119 | 120 | | |
120 | 121 | | |
121 | 122 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| |||
120 | 121 | | |
121 | 122 | | |
122 | 123 | | |
| 124 | + | |
123 | 125 | | |
124 | 126 | | |
125 | 127 | | |
| |||
133 | 135 | | |
134 | 136 | | |
135 | 137 | | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
136 | 142 | | |
137 | 143 | | |
138 | 144 | | |
| |||
301 | 307 | | |
302 | 308 | | |
303 | 309 | | |
| 310 | + | |
304 | 311 | | |
305 | 312 | | |
306 | 313 | | |
| |||
0 commit comments