Split out of #304, where it was parked as a secondary finding.
What
person_arrived_home:adrian fired 6× in ~100 min on 2026-08-26
(logs/px-mind.log:19947-20029). Each transition is a real dispatch opportunity:
greet_arrival is deliberately exempt from the 30-minute expression cooldown
and from the absence gates (mind.py:478-480) — arrivals are the one moment
SPARK is allowed to interrupt — so a flapping tracker turns that exemption into
repeated unsolicited speech for one arrival.
Mechanism
mind._enrich_tracker_entry (mind.py:909-937) derives at_home as a bare
threshold with no hysteresis:
"at_home": place == "at-dads", # semantic-address branch
"at_home": distance_km < 0.15, # coordinate branch
_detect_findmyhub_arrivals (mind.py:970-993) then fires on any
not prev.at_home and curr.at_home edge. Chipolo GPS accuracy on the live
ha_presence sample is gps_accuracy_m: 34.0, and the house sits near the
150 m radius boundary for at least one tracker, so ordinary jitter crosses it
repeatedly.
GREET_ARRIVAL_COOLDOWN_S = 120 (spark_config.py:16) is the only damper and
it is far shorter than the observed ~17-minute flap interval, so it does not
absorb this.
Wanted
Hysteresis on the state, not just a cooldown on the greeting — the two are
different claims and only one of them is about the world:
- separate enter/exit radii (e.g. enter at 0.15 km, leave only past ~0.30 km),
and/or require N consecutive fresh samples on the same side before flipping;
- ignore samples whose
gps_accuracy_m exceeds the radius they are being
compared against — a 34 m-accurate fix is fine against a 150 m radius, a
200 m-accurate one is not evidence of anything;
- keep the semantic-address branch (
place == "at-dads") as-is; it does not
jitter.
Deliberately not proposed: raising GREET_ARRIVAL_COOLDOWN_S. That would
hide the flap while leaving awareness["findmyhub"] and any future consumer
still reading a state that oscillates six times an hour.
Acceptance
- A stationary tracker sitting on the radius boundary produces at most one
person_arrived_home transition.
- A genuine away→home arrival still fires promptly (the daemon-restart guard in
_last_known_findmyhub is preserved — an empty cache still suppresses the
first observation).
- Unit test with a synthetic distance series that crosses the boundary
repeatedly, asserting one transition.
Split out of #304, where it was parked as a secondary finding.
What
person_arrived_home:adrianfired 6× in ~100 min on 2026-08-26(
logs/px-mind.log:19947-20029). Each transition is a real dispatch opportunity:greet_arrivalis deliberately exempt from the 30-minute expression cooldownand from the absence gates (
mind.py:478-480) — arrivals are the one momentSPARK is allowed to interrupt — so a flapping tracker turns that exemption into
repeated unsolicited speech for one arrival.
Mechanism
mind._enrich_tracker_entry(mind.py:909-937) derivesat_homeas a barethreshold with no hysteresis:
_detect_findmyhub_arrivals(mind.py:970-993) then fires on anynot prev.at_home and curr.at_homeedge. Chipolo GPS accuracy on the liveha_presencesample isgps_accuracy_m: 34.0, and the house sits near the150 m radius boundary for at least one tracker, so ordinary jitter crosses it
repeatedly.
GREET_ARRIVAL_COOLDOWN_S = 120(spark_config.py:16) is the only damper andit is far shorter than the observed ~17-minute flap interval, so it does not
absorb this.
Wanted
Hysteresis on the state, not just a cooldown on the greeting — the two are
different claims and only one of them is about the world:
and/or require N consecutive fresh samples on the same side before flipping;
gps_accuracy_mexceeds the radius they are beingcompared against — a 34 m-accurate fix is fine against a 150 m radius, a
200 m-accurate one is not evidence of anything;
place == "at-dads") as-is; it does notjitter.
Deliberately not proposed: raising
GREET_ARRIVAL_COOLDOWN_S. That wouldhide the flap while leaving
awareness["findmyhub"]and any future consumerstill reading a state that oscillates six times an hour.
Acceptance
person_arrived_hometransition._last_known_findmyhubis preserved — an empty cache still suppresses thefirst observation).
repeatedly, asserting one transition.