You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+8-8Lines changed: 8 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -135,7 +135,7 @@ npm start
135
135
`npm start` runs one process that:
136
136
137
137
1.**Reactive (iMessage)**: listens on Spectrum `app.messages`, extracts preferences with Grok, and replies.
138
-
2.**Follow-ups**: after a proactive alert, answer questions about that headline (e.g. “Why is this hawkish?”) via threaded `message.reply()`.
138
+
2.**Follow-ups**: after one or more proactive alerts, answer questions about any recent alert (up to 10, within 30 minutes each). The agent uses the LLM to infer which alert the question is about from the wording, so users just ask naturally — no thread-reply gymnastics required.
139
139
3.**Proactive (ZeroMQ)**: subscribes to `cpp_engine` headlines in the background, analyzes each with Grok, and pushes iMessage alerts when a user’s preferences match.
140
140
141
141
Outbound messages to the same chat are ordered so preference confirmations are not interrupted by a concurrent alert (see `TECHNICAL.md`).
@@ -232,17 +232,17 @@ Troubleshooting (alerts):
232
232
233
233
### What to test (alert follow-ups)
234
234
235
-
After you receive a proactive macro alert:
235
+
After you receive one or more proactive macro alerts:
236
236
237
-
1. Reply in the same chat with a question about that alert, e.g. `Why is this hawkish?` or `Summarize the whole report`.
238
-
2. You should get a threaded analysis reply (not a “Got it — saved preferences” message).
239
-
3. Follow-ups work for ~30 minutes after the alert; send a new preference message (e.g. “alert me on CPI”) if you want to change settings instead.
237
+
1. Reply in the same chat with a question, e.g. `Why is this hawkish?` or `What does the Waller news mean for rate cuts?`
238
+
2. You should get a threaded analysis reply (not a "Got it — saved preferences" message).
239
+
3. If several alerts are active, the agent passes all of them to the AI which picks the most relevant one based on your question — you don't need to do anything special to reference a specific alert.
240
+
4. Each alert stays in context for **30 minutes** after it was sent; up to 10 alerts are tracked per chat. Send a new preference message (e.g. "alert me on CPI") if you want to change settings instead.
240
241
241
242
Troubleshooting (follow-ups):
242
243
243
-
- Got a preferences reply instead of analysis: phrase your message as a question, or include words like “why”, “summarize”, or “hawkish”. Messages like “alert me on CPI” are treated as preference updates.
244
-
- “I don't have a recent alert in context”: no alert was stored for this chat yet, or the 30-minute context window expired.
245
-
244
+
- Got a preferences reply instead of analysis: phrase your message as a question, or include words like "why", "summarize", or "hawkish". Messages like "alert me on CPI" are treated as preference updates.
245
+
- No alerts active yet: wait for an alert to arrive (or inject one with `npm run pub`), then ask your question within the 30-minute window.
246
246
### Debug-only: standalone ZeroMQ subscriber
247
247
248
248
`npm run sub` runs `subscriber.ts` alone — it only logs headlines that pass the hardcoded C++ macro keyword filter. Use it to verify ZMQ wiring without Spectrum or Grok:
-`alertHistoryBySpace: Map<string, AlertContext[]>` — ordered newest-first list of up to 10 recent alert contexts per space, each with a 30-minute TTL. Used for conversational follow-ups. Each `AlertContext` includes the headline, source, Grok analysis, send timestamp, and the `OutboundMessage.id` of the alert we sent (used for best-effort thread-reply matching on platforms that expose it).
142
142
143
143
This is intentionally **ephemeral** (memory-only). Persisting to a DB can be added later once the preference schema stabilizes.
144
144
145
145
### Alert follow-ups
146
146
147
-
After a proactive alert is sent, the agent stores`AlertContext`for that `space.id`. On later inbound text:
147
+
After a proactive alert is sent, the agent appends`AlertContext`to a per-space history (`alertHistoryBySpace`). On later inbound text:
148
148
149
-
1. If there is recent alert context and the message looks like a **follow-up question** (e.g. ends with `?`, starts with “why”/“summarize”, mentions hawkish/dovish), it is **not** treated as a preference update.
150
-
2. Grok receives the stored headline/analysis plus the user’s question.
151
-
3. The response is sent with `message.reply()` so it threads in iMessage.
152
-
153
-
Preference-shaped messages (e.g. “alert me on CPI”, “threshold 0.5”) still go through preference extraction even if alert context exists.
149
+
1.**Follow-up detection**: if the message looks like a question (ends with `?`, starts with why/summarize/explain/what/how, mentions hawkish/dovish, etc.) **and** at least one unexpired alert is in the history, it is routed as a follow-up rather than a preference update.
150
+
2.**Multi-alert resolution**: all unexpired alert contexts (up to 10, each within 30 minutes of being sent) are passed to Grok together. The model identifies which alert the question is most relevant to and answers about it. For an unambiguous question it answers directly; for an ambiguous one it briefly notes which alert it chose.
151
+
3.**Thread-reply matching (best-effort)**: if the platform delivers the message as a Spectrum `content.type === "reply"` pointing to one of our sent alert message IDs, only that specific alert context is passed — no multi-alert resolution needed. Note: the iMessage provider does **not** expose thread-originator GUIDs on plain inbound text messages at the Spectrum level, so long-press → Reply in iMessage arrives as a plain text message and takes the multi-alert resolution path above. The thread-reply path is preserved for platforms that do expose this.
152
+
4. The response is sent with `message.reply()` so it threads in iMessage.
154
153
154
+
Preference-shaped messages (e.g. "alert me on CPI", "threshold 0.5") still go through preference extraction even if alert history exists.
155
155
### LLM preference extraction
156
156
157
157
Preferences are extracted by calling an **OpenAI-compatible** chat completions endpoint.
@@ -212,7 +212,7 @@ User preferences (tracked keywords + watchlist tickers) live only in the agent,
212
212
1. Parse JSON frame (including the `source` publisher); dedupe by `ts` + headline (or headline alone) for ~2 minutes.
213
213
2. Call Grok (`analyzeHeadlineWithLlm`, passing the headline + source) → `{ sentiment, severity, summary, sourceTrust }`. `sourceTrust` (0–1) is Grok's credibility rating of the publisher; an absent/garbled rating defaults to ~0.3 (low).
214
214
3. For each `userPreferences` entry, enqueue `spaceOutbound.run(spaceId, "alert", …)` (waits if that space has an active alert hold).
215
-
4. Inside the alert task, re-check keywords, `severity >= severityThreshold`, **and**`sourceTrust >= sourceTrustThreshold` against current preferences, then `app.send(space, alertText)` and store `lastAlertBySpace`. The alert text and stored context include the source name and its `low`/`medium`/`high` trust label. (`severityThreshold` is the user-facing “threshold”; it gates on market impact, not bullish/bearish sentiment.)
215
+
4. Inside the alert task, re-check keywords, `severity >= severityThreshold`, **and**`sourceTrust >= sourceTrustThreshold` against current preferences, then `app.send(space, alertText)` and append the result to `alertHistoryBySpace` (along with the `OutboundMessage.id` for best-effort thread-reply matching). The alert text and stored context include the source name and its `low`/`medium`/`high` trust label. (`severityThreshold` is the user-facing “threshold”; it gates on market impact, not bullish/bearish sentiment.)
216
216
217
217
If no user has messaged since startup, preferences are empty and headlines are logged but not alerted. If preferences exist but `spacesById` lacks that `space.id`, the agent logs that the user must message first (Spectrum needs a cached conversation handle for outbound iMessage).
0 commit comments