The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
2.5 KiB
List the persons in one retention acquisition cohort and show, for each, which subsequent intervals they came back in.
Pair this with query-retention: first run the retention query to see the cohort table (rows are acquisition cohorts, columns are intervals after acquisition), then call this tool with the same retention query as source to drill into one cohort.
Selectors:
interval: which acquisition cohort to list, 0-based.0is the acquisition interval itself (every actor who entered the cohort),1is the cohort that entered one interval later, and so on. Defaults to0. This selects a row of the retention table; the returned columns then cover every interval for that cohort.
Response:
results is the per-person grid. Each row contains distinct_id, email, name, followed by one column per retention interval — <period>_0 … <period>_N, where <period> is the retention period (day / week / month / hour). Each interval column is 1 if the actor was active in that interval and 0 if not. <period>_0 is the acquisition interval and is always 1. Rows are ordered by how many intervals the actor returned in (most-retained first). Results are limited to the top 100 actors.
For the per-interval retention counts and percentages (the Day N — count (pct%) curve), run query-retention on the same source — those are computed over the full, un-capped cohort. Don't sum these (capped) rows to get cohort retention numbers.
The number and names of the interval columns come from the source: retentionFilter.period sets the prefix, and retentionFilter.totalIntervals (or retentionCustomBrackets.length + 1 when custom brackets are set) sets how many columns there are.
There is no includeRecordings selector — retention's persons output is appearance-based and does not surface matched session recordings.
Guidance:
- Keep the
sourceretention query identical to the one whose cohort the user is asking about — the cohort definition (target/returning events, period, type, brackets) determines who is in each cohort. intervalpicks the cohort (a row of the retention table), not a single cell. The response always spans every return interval as<period>_Ncolumns. To get who returned in a specific interval, filter the rows where that column is1— the query returns the cohort's whole trajectory, not a single cell.- To narrow large result sets, tighten the source (date range, filters) rather than expecting more than 100 rows.