Skip to content

Commit 8871cac

Browse files
committed
added onboarding doc for users
1 parent 6894450 commit 8871cac

2 files changed

Lines changed: 301 additions & 1 deletion

File tree

ONBOARDING.md

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
# Onboarding, users, and going live
2+
3+
This document explains how people become users today, what they need to text,
4+
how Photon Spectrum fits in, and when you need persistent storage. It is aimed
5+
at hosting the product and pairing it with a marketing landing page in another
6+
repo.
7+
8+
For what users can say after they are set up, see [`MESSAGING.md`](MESSAGING.md).
9+
For runtime internals, see [`TECHNICAL.md`](TECHNICAL.md).
10+
11+
---
12+
13+
## The two different questions (this is the common confusion)
14+
15+
People often mix up **“can many users use it at the same time?”** and **“do we
16+
need a database?”** They are separate:
17+
18+
| Question | Answer today | Needs a database? |
19+
| --- | --- | --- |
20+
| Can Alice and Bob both text the agent and get their own alerts? | **Yes** — each iMessage thread is a separate user in memory. | **No** (while the agent process is running) |
21+
| Do Alice’s settings survive if we restart or redeploy the agent? | **No** — preferences live only in RAM until she texts again. | **Yes**, if you want settings (and “registered” state) to survive restarts |
22+
| Can we run two agent servers behind a load balancer? | **Not cleanly** — each instance has its own memory; users would be split and confused. | **Yes**, for shared state |
23+
| Does the landing page need to write users into a DB before they can text? | **No** — texting the number is enough to start using the product as built today. | Only if *you* want web signup, billing, or invite-only access |
24+
25+
**Short version:** persistent storage is **not** required for *concurrent*
26+
multi-user chat. It **is** required for anything that must **outlive a single
27+
process** (restarts, deploys, accounts, billing, invite lists, analytics tied
28+
to identity).
29+
30+
---
31+
32+
## How Photon Spectrum works for this project
33+
34+
The agent (`ts_agent`) runs a **Spectrum server** that listens for inbound
35+
iMessage events. When someone texts your line, Spectrum delivers a
36+
`[space, message]` tuple to the agent:
37+
38+
- **`space`** — the conversation (thread). One-to-one chats get one `space.id`
39+
per person; that id is how we store preferences today.
40+
- **`message.sender`** — who sent the text (Spectrum’s sender id for that
41+
person).
42+
43+
The agent does **not** implement a separate “sign up” API and does **not** send
44+
“you need to register” replies. Spectrum is the front door: **only if Photon
45+
delivers the text into `app.messages`** does your code run (preferences, Grok,
46+
alerts).
47+
48+
If an unknown tester sees a registration message on iMessage, that is almost
49+
certainly **Photon’s cloud gate** (sender not linked to your project in shared
50+
mode), not `ts_agent`. Your console will show **no** `[iMessage] inbound event`
51+
for those texts.
52+
53+
Photon’s [Spaces and Users](https://photon.codes/docs/spectrum-ts/spaces-and-users)
54+
doc describes the **SDK model** (`space`, `user`, `space.send`, `im.user(phone)`)
55+
for conversations your **code** creates or replies in. It does **not** define
56+
end-user “registration” on the platform — that lives in Photon’s **iMessage line /
57+
plan** setup; see [iMessage provider → Line model](https://photon.codes/docs/spectrum-ts/providers/imessage#line-model).
58+
59+
### Shared pool vs dedicated line (Photon plan)
60+
61+
From Photon’s [iMessage provider docs](https://photon.codes/docs/spectrum-ts/providers/imessage#line-model):
62+
63+
| Photon plan | Line model | What end users see |
64+
| --- | --- | --- |
65+
| Free / Pro | **Shared pool** — routing may use different pool numbers per recipient | iMessage from a number that can differ; senders often must be **linked** to your project in the dashboard |
66+
| Business | **Dedicated** — one number for your project | Everyone texts the **same** agent number |
67+
68+
Run `cd ts_agent && npm run build && npm run info` to see what **your** project
69+
token reports (`dedicated` vs shared). Startup also prints a hint when shared.
70+
71+
| Token / mode | What users text | What you configure |
72+
| --- | --- | --- |
73+
| **Dedicated** | Your project’s phone number(s) from `npm run info` | Provision the line in the Photon dashboard; strangers can DM that number without per-phone linking in our repo |
74+
| **Shared** | The shared Photon number (dashboard) | **Link each tester’s sending phone** to this project, or Photon blocks them (auto-reply / no agent logs) |
75+
76+
Put the number users should text on your landing page. On **Business /
77+
dedicated**, that is your stable agent line. On **Free/Pro / shared**, also
78+
document that testers must complete Photon’s sender linking (or upgrade plan).
79+
80+
### Blue bubble only
81+
82+
The agent only processes **`platform === "iMessage"`** and **text** messages.
83+
Green-bubble SMS or non-text events are logged and skipped. Users need an Apple
84+
device (or Mac) with iMessage to talk to the agent.
85+
86+
---
87+
88+
## Can anyone register by texting? What do they say?
89+
90+
### Two layers: Photon gate vs your agent
91+
92+
| Layer | Who controls it | Unknown phone texts your line |
93+
| --- | --- | --- |
94+
| **Photon (iMessage cloud)** | Dashboard linking (shared) or dedicated line (Business) | May get “need to register” / no delivery to agent |
95+
| **`ts_agent` (this repo)** | No allowlist in code | Once `[iMessage]` logs appear, first text = user in memory |
96+
97+
There is **no allowlist**, **no invite code**, and **no required magic word**
98+
in **this codebase**. Anyone whose message **reaches** `app.messages` can:
99+
100+
1. Become a “user” on first contact (their thread is stored in memory).
101+
2. Have every subsequent text interpreted as a **preference update** (unless it
102+
looks like a follow-up to a recent alert — see [`MESSAGING.md`](MESSAGING.md)).
103+
104+
That is **self-service onboarding in the agent** — but only **after** Photon
105+
routes the iMessage to you.
106+
107+
### What they need to say (nothing special)
108+
109+
They do **not** need to text `REGISTER` or `START`. Any normal preference
110+
phrase works on the first message, for example:
111+
112+
- `Alert me on CPI and FOMC, threshold 0.5`
113+
- `Watch TSLA and NVDA`
114+
- `Only big alerts, threshold 0.8`
115+
116+
The agent replies with something like:
117+
118+
```text
119+
Got it — saved your macro preferences for this chat.
120+
Tracked keywords: CPI, FOMC
121+
Watchlist: (none)
122+
Sentiment threshold: 0.5
123+
Source trust: any source
124+
```
125+
126+
From then on they are a user for that chat: proactive macro alerts can fire when
127+
headlines match their settings (see below).
128+
129+
**Empty keywords + empty watchlist** means “match every headline” (subject to
130+
thresholds). So even a vague first message like `hello` may still parse into
131+
defaults and start matching broadly — worth knowing before you publish a public
132+
number.
133+
134+
### What “registered” means in code today
135+
136+
On each inbound text the agent:
137+
138+
1. Caches the Spectrum `space` handle (`spacesById`) so it can **send** alerts
139+
later.
140+
2. Merges extracted preferences into `userPreferences` keyed by **`space.id`**.
141+
142+
There is no separate `users` table and no check against a landing-page signup.
143+
144+
### Proactive alerts: one extra requirement
145+
146+
Headlines come from `cpp_engine` over ZeroMQ. For a user to **receive** proactive
147+
alerts:
148+
149+
1. **`cpp_engine` must be running** and connected to the agent.
150+
2. The user must have **messaged at least once since the agent last started**
151+
so the agent has cached their `space` for outbound iMessage.
152+
3. Their preferences must match an incoming headline (keywords/watchlist +
153+
severity + source trust).
154+
155+
If the agent restarts, preferences in memory are **gone** until they text again
156+
(and the space cache is empty until they text again). After they text once,
157+
settings are rebuilt from that message onward.
158+
159+
---
160+
161+
## End-to-end flow (landing page + hosted agent)
162+
163+
```text
164+
Landing page (other repo)
165+
166+
│ "Text +1 …… on iMessage to get macro alerts"
167+
168+
User's iPhone (iMessage, blue bubble)
169+
170+
171+
Photon Spectrum ──► ts_agent (hosted)
172+
│ │
173+
│ ├── saves prefs per space.id (RAM)
174+
│ └── pushes filter union to cpp_engine (ZMQ)
175+
176+
cpp_engine (hosted) ── headlines ──► ts_agent ── alerts ──► user
177+
```
178+
179+
Recommended copy for the landing page:
180+
181+
1. **CTA** — Text our iMessage number: `+1 …` (from `npm run info` or Photon
182+
dashboard).
183+
2. **First message examples** — 2–3 lines from the table in
184+
[`MESSAGING.md`](MESSAGING.md) (e.g. CPI/FOMC + threshold, or a watchlist).
185+
3. **Requirements** — iMessage (Apple device); not SMS/green bubble.
186+
4. **What happens next** — They get a confirmation reply; macro alerts arrive
187+
when news matches their settings (may take a few seconds for watchlist sync —
188+
see README → Dynamic filter sync).
189+
190+
The landing page does **not** need to call this repo unless you add web signup
191+
later.
192+
193+
---
194+
195+
## Hosting checklist
196+
197+
When you deploy (VPS, container, etc.), run **both** processes and keep **one**
198+
agent instance (the agent uses a single-instance lock):
199+
200+
| Component | Role |
201+
| --- | --- |
202+
| `cpp_engine` | Polls/simulates headlines, filters, publishes to `ZMQ_ENDPOINT` |
203+
| `ts_agent` | Spectrum + Grok + per-user routing + `FILTER_ENDPOINT` to engine |
204+
205+
Environment (minimum):
206+
207+
- **Spectrum:** `PROJECT_ID`, `PROJECT_SECRET`
208+
- **Grok:** `XAI_API_KEY`
209+
- **Live news (optional):** `FINNHUB_API_KEY` on `cpp_engine` for `--live`
210+
- **ZMQ:** `ZMQ_ENDPOINT` / `FILTER_ENDPOINT` aligned between processes
211+
212+
Operational notes:
213+
214+
- **Restarts wipe in-memory users** — expect users to text again after deploy, or
215+
add persistence (below).
216+
- **Public number = public product** — budget Grok/Finnhub for unsolicited texts
217+
unless you add gating.
218+
- **One agent process** — do not run duplicate `npm start` (duplicate replies).
219+
220+
---
221+
222+
## When to add persistent storage
223+
224+
Add a database (or similar) when you need any of the following:
225+
226+
| Need | Why memory is not enough |
227+
| --- | --- |
228+
| Preferences survive deploys/restarts | Maps are empty after restart |
229+
| “Registered” / opted-in flag | No durable record of who joined |
230+
| Invite-only or paid access | Must check identity before accepting prefs |
231+
| Landing page creates account first | Web stores phone/email; agent must match inbound sender |
232+
| Support / billing / GDPR delete | Need stable `user_id` and audit trail |
233+
| Horizontal scale (multiple agents) | Shared prefs + space routing |
234+
235+
A minimal schema later might look like:
236+
237+
- `users`: `id`, `sender_id` (Spectrum), `phone` (if available), `created_at`,
238+
`status` (`active` / `blocked`)
239+
- `preferences`: `user_id`, JSON matching `UserPreferences` in `ts_agent`
240+
- `spaces`: `user_id`, `space_id` (last known Spectrum thread)
241+
242+
Load on startup; upsert on each inbound message; still cache `spacesById` in
243+
memory for outbound (Spectrum needs the live `Space` object).
244+
245+
**Phase 1 without a DB:** landing page + public number + examples in
246+
[`MESSAGING.md`](MESSAGING.md) is valid for a demo or small beta if you accept
247+
reset-on-restart and open registration.
248+
249+
---
250+
251+
## Product options you may want later (not implemented yet)
252+
253+
These are design choices for when open registration is too risky or UX needs a
254+
clearer “join” step:
255+
256+
| Approach | User experience | Implementation sketch |
257+
| --- | --- | --- |
258+
| **Open (today)** | Text anything preference-like | No change |
259+
| **Explicit opt-in** | First reply: “Text START to enable alerts” | Ignore preference parsing until `START` or store `opted_in` in DB |
260+
| **Invite-only** | “You need an invite” | Allowlist `sender.id` in env or DB |
261+
| **Web-first signup** | Enter phone on site, then text | DB pending row; first text links `sender.id` |
262+
| **Welcome on first text** | Short onboarding before prefs | Detect new `space.id`; send welcome; optional second message for prefs |
263+
264+
Document any choice you ship in this file and in the landing page repo.
265+
266+
---
267+
268+
## Quick FAQ
269+
270+
**Do users need to say “register”?**
271+
No. First iMessage that reaches the line starts a user session for that thread.
272+
273+
**Is storage required for multiple users?**
274+
No for simultaneous use; yes if settings and identity must survive restarts or
275+
you want signup/billing/invite control.
276+
277+
**Why did you say storage wasn’t needed before?**
278+
Meant: the *architecture already supports many chats at once* without adding a
279+
database first. That is not the same as “never use a database for production.”
280+
281+
**Someone got “you need to register” — is that us?**
282+
Almost certainly **Photon**, not this repo (grep finds no such copy). Check
283+
the agent terminal: no `[iMessage] inbound event` = Photon never delivered it.
284+
Fix: link their sending number (shared) or use a **dedicated Business line**.
285+
286+
**What if someone texts but never sets keywords?**
287+
Defaults apply; empty keyword + empty watchlist can match all headlines (see
288+
[`MESSAGING.md`](MESSAGING.md) → Defaults).
289+
290+
**Can the landing page register users without texting?**
291+
Not with the current agent. The landing page is marketing + instructions unless
292+
you build a separate signup service and later teach the agent to honor it.
293+
294+
---
295+
296+
## Related docs
297+
298+
- [`MESSAGING.md`](MESSAGING.md) — what to text after onboarding
299+
- [`README.md`](README.md) — build, run, test, dynamic filter sync
300+
- [`TECHNICAL.md`](TECHNICAL.md) — Spectrum loop, in-memory maps, ZMQ channels

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Dual-language starter repo with:
55
- `cpp_engine/`: a C++20 engine built with CMake and linked to ZeroMQ (libzmq + cppzmq)
66
- `ts_agent/`: a Node.js + TypeScript agent using `zeromq`, xAI (Grok), and `dotenv`
77

8-
For implementation details (build/linking choices, intended ZeroMQ protocol), see `TECHNICAL.md`. For what end users should text the agent (preferences, watchlist, follow-ups), see `MESSAGING.md`.
8+
For implementation details (build/linking choices, intended ZeroMQ protocol), see `TECHNICAL.md`. For what end users should text the agent (preferences, watchlist, follow-ups), see `MESSAGING.md`. For registration, hosting, multi-user vs persistent storage, and landing-page flow, see [`ONBOARDING.md`](ONBOARDING.md).
99

1010
## Repository layout
1111

0 commit comments

Comments
 (0)