Skip to content

activity-decay

Timer decay: “active in the last 30 days” as a per-user journey instead of a nightly sweep. The trigger sets the flag and the execution waits; every later event restarts the execution, which resets the 30-day silence clock with a fresh, empty journal; 30 days of silence unsets the flag (removed, not set to false, so exists predicates stop matching) and the execution completes. The next event starts a new one.

"*" is activity as it really arrives: whatever names your apps send, page_view and purchase and login alike. It never matches an event Cowliss wrote itself (those are all under system.), which is what stops the trait write below from flipping a segment whose entry event restarts this journey, forever.

Example: add it with cow add example activity-decay; rename the trait to your domain’s vocabulary, or narrow the pattern (checkout.*, or a list) if only some events should count as activity.

Trigger { event: "*" }
Tags demo, retention
Consent purpose emailMarketing

Timer loop as computed state. “Active in the last 30 days” expressed as a per-user durable timer instead of a nightly sweep: the trigger event sets the flag, every further activity event restarts the execution (which resets the silence clock with an empty journal), and 30 days of silence removes the flag (unset, not false, since exists predicates stop matching only when the key is gone). The execution then completes; the next activity starts a fresh one.

Any rolling-window state a segment should see: active_30d, at_risk, slipping_away. Pair it with a segment on the trait and a second journey triggered by that segment’s entry: the composition pattern.

Rename the trait and the activity event. The window ("30d") is plain configuration. api.restart() is what keeps a journey that loops for months bounded: it ends the execution and starts a fresh one under the same workflow id, so the journal never grows past one window.

import { defineJourney } from "@cowliss/cli/journeys";
/**
* Timer decay: "active in the last 30 days" as a per-user journey instead of
* a nightly sweep. The trigger sets the flag and the execution waits; every
* later event restarts the execution, which resets the 30-day silence clock
* with a fresh, empty journal; 30 days of silence unsets the flag (removed,
* not set to false, so `exists` predicates stop matching) and the execution
* completes. The next event starts a new one.
*
* `"*"` is activity as it really arrives: whatever names your apps send,
* page_view and purchase and login alike. It never matches an event Cowliss
* wrote itself (those are all under `system.`), which is what stops the
* trait write below from flipping a segment whose entry event restarts this
* journey, forever.
*
* Example: add it with `cow add example activity-decay`; rename the trait to
* your domain's vocabulary, or narrow the pattern (`checkout.*`, or a list)
* if only some events should count as activity.
*/
export default defineJourney({
trigger: { event: "*" },
purpose: "emailMarketing",
tags: ["demo", "retention"],
run: async (_event, api) => {
await api.traits.set("active_30d", true);
const active = await api.waitForEvent("*", { timeout: "30d" });
if (active) {
// This is the loop, and it is bounded: restart ends this execution and
// starts a fresh one under the same workflow id with an empty journal,
// so a user who stays active for years never grows one.
await api.restart({ event: active });
}
await api.traits.unset("active_30d");
},
});

The example ships this scenario, copied in beside the journey. cow test replays it on a virtual clock: the trigger fires at the start, events arrive at their offsets, and expect is the ordered list of capability calls the run must make, each input matched as a subset.

activity-decay.json
{
"user": { "id": "usr_1", "traits": {} },
"events": [{ "at": "10d", "event": "page_view" }],
"expect": [
{ "activity": "setTrait", "input": { "key": "active_30d", "value": true } },
{ "activity": "restart" }
]
}
Terminal window
# copy it into your project
cow add example activity-decay
# run its scenario on a virtual clock
cow test activity-decay --scenario scenarios/activity-decay.json