I’m building a sport coach that sends nudges over Telegram. One question came up early, and it’s less simple than it looks: not what to write, but when and how often during the day. Too many reminders become noise you ignore. Too few have no effect. And the right dose depends on the person: someone diligent doesn’t need the same rhythm as someone who hasn’t moved in several days.

Fixed times (8am, 1pm, 6pm every day) solve the problem on the surface, but they’re mechanical: same hours every day, indifferent to what the person does. My goal was the opposite: that the coach nudges the way a real human coach would. A human doesn’t write at fixed times. They reach out at slightly unpredictable moments, without disturbing you in the middle of the night or hounding you, and they push harder when the person drops off. In other words: random timing, but under constraints.

That is exactly what a random point process describes. The first model I settled on is a non-homogeneous Poisson process. It ran the coach in production for several weeks before I replaced it. This article describes that model: its intuition, what it does, and what led me to change it.

A reminder as a random event

A Poisson process describes events that occur over time at a certain rate: on average, so many events per unit of time. Here, the events are the reminders. The simplest version assumes a constant rate, for example two reminders a day spread uniformly.

A constant rate won’t do. It would place a reminder at three in the morning with the same probability as at noon. But the opportunity for a workout isn’t uniform across the day: it concentrates on a few moments. So we need a rate that depends on the hour. That is what “non-homogeneous” means.

An intensity that follows the day

We replace the constant rate with a function of time, $\mu(t)$, called the intensity. It is close to zero at night and higher around the windows where a workout is plausible.

I build this intensity as a sum of three Gaussian curves, centred on the morning, noon, and evening. Each curve has its position, width, and weight. In my configuration, the centres are 7:30, 12:30, and 18:00. The evening has the highest weight: it’s when people train the most, once the workday is over.

Formally, this intensity is a sum of three Gaussians, up to a global scale factor $\lambda$ (the rate, set in the next section):

$$ \mu(t) = \lambda \sum_{k=1}^{3} w_k \, \exp\!\left(-\frac{(t - c_k)^2}{2\,\sigma_k^2}\right) $$

with centres $c = (\text{7:30},\ \text{12:30},\ \text{18:00})$, widths $\sigma = (45,\ 45,\ 60)$ minutes, and weights $w = (1.2,\ 0.8,\ 1.5)$.

In a Poisson process, the number of reminders doesn’t depend on the height of the intensity at a given instant, but on the area under the curve: over a time range, that number follows a Poisson distribution whose mean is that area. So it’s the area, not the line, that you should read on the graph below.

Scheduling is bounded to an active window. Before 6am and after 8pm (the hatched zones on the graph), the intensity is zero: no reminder is drawn. Within the window, the intensity stays strictly positive; it dips low between the bumps (around 10am, about 0.5% of the peak) without ever vanishing.

Reminder intensity over 24 hours: three peaks at 7:30, 12:30 and 18:00, off-window hatched

The area under μ(t) gives the number of reminders. Scheduling is bounded to an active window (hatched outside); three zones around 7:30, 12:30 and 18:00, the evening one the widest.

It’s a pure Poisson process, not a self-exciting (Hawkes) one. A reminder doesn’t make the next one more likely. The intensity depends only on the hour and the person’s state, never on reminders already sent.

A global rate that adapts to behaviour

The shape says when to nudge. We still need how much: the rate $\lambda$, the intensity’s scale factor. I modulate it with two indicators of recent behaviour: the reminder follow-through rate (compliance, written $C$) and inactivity (the number of consecutive days without a workout, written $D$):

$$ \lambda = 2.2 + 1.5\,(1 - C) + 0.4\,D $$

with $C \in [0, 1]$ and $D \geq 0$. The rule reads directly: when someone ignores reminders ($C$ low) and stops training ($D$ high), $\lambda$ rises; when someone is diligent, $\lambda$ falls. The number of reminders in a day then follows a Poisson distribution with mean $\Lambda = \int \mu(t)\,dt$ over the active window. With no data (cold start), we begin from a neutral state.

$C$ and $D$ are two scalars: they summarise past behaviour in two numbers. That point matters for what follows.

The same intensity scaled by the rate lambda for three behaviours: diligent, average, disengaged

The same shape, scaled by the rate λ. The more the person drops off (low compliance, high inactivity), the higher the intensity, and so the expected number of reminders. The peak hours don’t change; only the level does.

Concrete times: thinning

A continuous intensity isn’t enough; we need to draw precise times from it. The classic method is thinning, due to Lewis and Shedler (1979).

The principle is as follows. We first simulate a homogeneous process at the maximum rate $\mu_{\max}$, which is easy: it’s a sequence of exponential delays. We then keep each candidate drawn at instant $t$ with probability $\mu(t)/\mu_{\max}$, and discard the rest. The kept instants follow exactly the intended intensity:

μ_max = max_t μ(t)
t = 0
while t < T:
    t += Exponential(μ_max)         # candidate drawn at the maximum rate
    if Uniform(0,1) < μ(t) / μ_max:  # acceptance test
        emit a reminder at time t

A few practical adjustments

Raw thinning can place two reminders a few minutes apart, or land on a round hour that feels mechanical. Three adjustments are enough:

  • a minimum gap (30 minutes) between two reminders;
  • a random offset (jitter, ±15 minutes) to avoid overly regular times;
  • a cap on the number of reminders per day.

With a fixed random seed, the result is deterministic: same indicators, same generated day. So it’s reproducible and testable.

What the model produces

A typical day yields two or three reminders, concentrated around the plausible windows, with more reminders if the person has dropped off. Above all, the times have the look of a human coach’s: varied from one day to the next, never in the middle of the night, more frequent when the person disengages. That was the original goal. The model is simple, transparent, and all its logic fits in a few formulas. For a first model, it’s a sound base.

The model’s limits

The times looked like a human’s. But a human coach does more than vary their hours: they remember what the person did and decide whether it’s better to leave them alone. This model has nothing of the sort. It summarises the whole past in two numbers, $C$ and $D$, and replays the same mechanics every morning.

The clearest flaw appeared when I replayed history. Simulating twenty-two real active days a large number of times, I found that in nearly 5% of cases the process drew no reminder at all. It’s a property of a Poisson process: even with a strictly positive rate, the probability of drawing zero events is never zero. That makes for days where the coach stays silent while the person is waiting for a nudge.

The cause is more general than this symptom. A Poisson process is meant to model an exogenous flux: events that arrive from the outside world and that we merely observe. But sending a reminder isn’t an event I observe. It’s a decision I make, for a specific person, under constraints: don’t saturate, stay cautious, adapt to their state. A model of exogenous arrivals doesn’t match a decision problem.

Reframed, the problem isn’t “simulate a flux” but “decide when and at what dose to intervene for a person whose state I only know indirectly.” That is what the literature calls a just-in-time adaptive intervention (JITAI), and it’s modelled as a decision process under partial observation, a POMDP. That’s the subject of the next article.

Conclusion

The non-homogeneous Poisson process spreads adaptive reminders across a day in a simple, cheap, and reproducible way. Its logic fits in a few formulas. It ran my coach for several weeks and remains available as a fallback in the system.

But it only answers “when”. The question “should I nudge, and at what dose” calls for a model that keeps an estimate of the person’s state up to date and weighs the cost of each reminder. That’s the subject of the next article.