Skip to content

Commit 3b26f01

Browse files
committed
docs: add live activity cost tracking design spec
1 parent e05de17 commit 3b26f01

1 file changed

Lines changed: 229 additions & 0 deletions

File tree

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# Live Activity Cost Tracking Design
2+
3+
**Date:** 2026-03-31
4+
5+
**Project:** Helix-iOS
6+
7+
**Status:** Approved for planning
8+
9+
## Goal
10+
11+
Add a lock-screen Live Activity for active conversation sessions that:
12+
13+
- shows the active detected question while the answer is being generated and streamed
14+
- shows cumulative session cost across transcription, question detection, and answer generation
15+
- supports OpenAI plus Chinese providers with provider-specific pricing and parsing
16+
- supports an interrupting `Ask Now` button that runs from the Live Activity without opening the app UI
17+
- supports a separate `Open App` button that explicitly foregrounds Helix
18+
19+
## Non-Goals
20+
21+
- Rebuilding the assistant stack in Swift
22+
- Approximating cost when usage or pricing metadata is unavailable
23+
- Opening the app automatically for `Ask Now`
24+
- Supporting multiple simultaneous response generations
25+
26+
## Current State
27+
28+
- Native Live Activity scaffolding already exists in the iOS app and widget extension.
29+
- Flutter does not currently call the native `startLiveActivity`, `updateLiveActivity`, or `stopLiveActivity` methods.
30+
- The conversation pipeline already exists in Dart and is authoritative for question detection, answer generation, provider selection, and manual ask behavior.
31+
- The LLM abstraction currently streams text only. It does not expose usage metadata or cost.
32+
- The OpenAI realtime transcription path handles transcript events but does not currently parse usage metadata.
33+
- Chinese providers are routed through the existing provider stack, but pricing and usage normalization do not exist yet.
34+
35+
## Product Decisions
36+
37+
- Session cost includes all supported AI work in the active session:
38+
- transcription
39+
- question detection
40+
- answer generation
41+
- Cost display must be provider-specific.
42+
- If usage or pricing is incomplete for a provider or model, Helix shows usage-only or incomplete-cost state instead of invented numbers.
43+
- `Ask Now` must run from the Live Activity without opening the app UI.
44+
- `Ask Now` is interrupting:
45+
- if an old response is still streaming, Helix cancels it and starts a new analysis immediately
46+
- the newest ask wins
47+
- A separate `Open App` control must be available.
48+
49+
## Recommended Architecture
50+
51+
### 1. Keep One Assistant System
52+
53+
Helix should keep a single authoritative conversation system in Dart.
54+
55+
- `ConversationEngine` remains the source of truth for session state, question detection, and answer generation.
56+
- Provider routing remains in the existing provider stack.
57+
- The new feature adds orchestration and accounting layers around the existing pipeline rather than duplicating assistant behavior in Swift.
58+
59+
### 2. Add a Dart Live Activity Service
60+
61+
Create a Dart `LiveActivityService` responsible for:
62+
63+
- subscribing to engine lifecycle, status, question detection, answer streaming, and provider/model state
64+
- maintaining a `LiveActivitySessionSnapshot`
65+
- bridging that snapshot to the native iOS methods that start, update, and stop the Live Activity
66+
- pinning the active question while the answer area updates independently
67+
68+
This service should own the display state contract. The widget extension should render a prepared state object, not derive product logic itself.
69+
70+
### 3. Add a Background Ask Path
71+
72+
Add a native iOS `AppIntent` for `Ask Now` that runs from the Live Activity surface.
73+
74+
- The intent must not open the UI.
75+
- The intent updates the Live Activity into `Analyzing`.
76+
- The intent hands off work to a headless Helix worker path that reuses the existing Dart pipeline.
77+
- The result flows back into the same Live Activity state update path.
78+
79+
Add a separate `Open App` path using explicit deep linking.
80+
81+
### 4. Keep Native Live Activity Code Thin
82+
83+
The native iOS side should only:
84+
85+
- start, update, and stop the activity
86+
- render SwiftUI lock-screen and Dynamic Island views
87+
- host the interaction intents
88+
- bridge background intent execution into the headless Helix worker
89+
90+
It should not own provider pricing logic or independent question-answer logic.
91+
92+
## Cost Accounting Model
93+
94+
### Ledger Model
95+
96+
Session cost should be tracked as a ledger, not a single number guessed from the last answer.
97+
98+
Each ledger item stores:
99+
100+
- `operationType`
101+
- `providerId`
102+
- `modelId`
103+
- `usage`
104+
- `pricingVersion`
105+
- `calculatedCost`
106+
- `currency`
107+
- `startedAt`
108+
- `completedAt`
109+
- `status`
110+
111+
`operationType` has three values:
112+
113+
- `transcription`
114+
- `questionDetection`
115+
- `answerGeneration`
116+
117+
### Usage Rules
118+
119+
- Usage must come from provider-native metadata when available.
120+
- Chat-style providers must emit final usage information after the request finishes.
121+
- Realtime transcription and realtime answer flows must parse usage from their completion events.
122+
- If a request is canceled, the ledger should keep the partial attempt with its actual known usage and cost status.
123+
124+
### Pricing Rules
125+
126+
- Pricing is provider-specific and model-specific.
127+
- Pricing should live in an app-level registry, not in UI code.
128+
- The registry must support different rate dimensions when relevant:
129+
- input
130+
- output
131+
- cached input
132+
- transcription
133+
- If usage exists but pricing is unknown, Helix shows incomplete cost state.
134+
- If pricing exists but usage is missing, Helix does not estimate from text length.
135+
136+
### Provider Scope
137+
138+
The design must support:
139+
140+
- OpenAI
141+
- DeepSeek
142+
- Qwen
143+
- Zhipu
144+
145+
## Live Activity UX
146+
147+
### Lock-Screen Content
148+
149+
While a session is active, the Live Activity shows:
150+
151+
- session mode
152+
- provider and active model
153+
- elapsed session duration
154+
- cumulative session cost
155+
- active question
156+
- answer state and answer text
157+
158+
### Question and Answer Behavior
159+
160+
- Once a question is detected, it becomes the pinned active question.
161+
- The pinned question stays visible while the answer is generated and streamed.
162+
- The answer section updates independently underneath the question.
163+
- A newer detected question replaces the previous pinned question.
164+
165+
### Ask Now Behavior
166+
167+
- `Ask Now` immediately interrupts any in-flight response.
168+
- The current response generation is canceled.
169+
- The Live Activity switches to `Analyzing`.
170+
- Helix reuses the latest available transcript context and starts a fresh analysis cycle.
171+
- The previous answer stops streaming and is replaced by the new active turn when ready.
172+
- Cost ledger entries for interrupted attempts remain recorded, but the UI only presents the newest active turn.
173+
174+
### Open App Behavior
175+
176+
- `Open App` always foregrounds Helix explicitly.
177+
- `Open App` does not imply a new ask.
178+
- It is a navigation control, not an analysis control.
179+
180+
### Error and Edge States
181+
182+
- If there is not enough context for `Ask Now`, show a short `Need more context` state and return to listening.
183+
- If provider usage is unavailable, show a clear incomplete-cost state.
184+
- If background execution fails, show a compact error state and keep the session alive.
185+
- Only one response generation may be active at a time.
186+
187+
## State Machine
188+
189+
The Live Activity state machine should be semantic and minimal:
190+
191+
- `Listening`
192+
- `Analyzing`
193+
- `Answering`
194+
- `ShowingResult`
195+
- `Error`
196+
- `Ended`
197+
198+
State transitions:
199+
200+
- session start -> `Listening`
201+
- detected question -> `Analyzing`
202+
- answer stream begins -> `Answering`
203+
- answer completes -> `ShowingResult`
204+
- `Ask Now` during `Answering` -> cancel old response, then `Analyzing`
205+
- session stop -> `Ended`
206+
207+
## Testing Requirements
208+
209+
- verify question pinning while answer text changes
210+
- verify ledger accumulation across transcription, question detection, and answer generation
211+
- verify provider-specific usage parsing and pricing lookup
212+
- verify interrupted `Ask Now` behavior cancels the old response and starts a new one
213+
- verify incomplete-cost display rules
214+
- verify `Open App` does not trigger analysis
215+
- verify session end behavior cleans up the activity correctly
216+
217+
## Risks
218+
219+
- Background intent handoff into a headless Flutter worker is the most complex integration point.
220+
- Provider usage shapes may differ enough that normalization needs careful tests.
221+
- Realtime flows may expose usage only at completion, which means cost display must tolerate short pending windows.
222+
223+
## Success Criteria
224+
225+
- A user in an active session can read the current question and streamed answer on the lock screen.
226+
- The displayed cost reflects provider-specific accumulated session cost across approved operation types.
227+
- `Ask Now` works without opening the app UI and interrupts old responses correctly.
228+
- `Open App` is available as a distinct control.
229+
- The feature works with OpenAI and the approved Chinese providers through a shared accounting abstraction.

0 commit comments

Comments
 (0)