#23: bare-greet suppression — any roster member#93
Merged
Conversation
…bearing The face-detected bare "Hi!" path previously suppressed only when at least one roster member had an `appearance:` field. Households where members were registered with just display_name + calendar prefix (the common configuration) still saw a bare greet stack on top of the room-view named greet, producing a double-greeting on each walk-in. Suppress when the registry has any members — accept silent-listen-mode for unidentified faces as the design (`Closes #23`, approach b). - HouseholdRegistry: add `__len__` (with the standard `_reload_if_changed` guard). - FaceGreeter: `_roster_has_appearances()` → `_roster_is_populated()`, checking `bool(self._household)`. - Tests: new non-empty case (display_name only fixture); existing appearance-bearing case preserved. Closes #23 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the face-detected “bare Hi!” suppression logic so it triggers whenever the household registry contains any members (not only members with appearance:), preventing double-greeting when room-view recognition is in use.
Changes:
- Add
HouseholdRegistry.__len__()sobool(registry)reflects whether the roster is non-empty (with the usual hot-reload guard). - Update
FaceGreetersuppression check to use roster population (bool(self._household)) and rename helper_roster_has_appearances()→_roster_is_populated(). - Add a new test ensuring bare greet is suppressed even when roster entries only have
display_name(noappearance).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| dotty-behaviour/tests/test_consumer_face_greeter.py | Adds regression test for suppression when roster is non-empty without appearance. |
| dotty-behaviour/household/registry.py | Implements __len__ to make registry truthiness reflect non-empty roster. |
| dotty-behaviour/consumers/face_greeter.py | Switches suppression logic to “any roster member” and renames helper accordingly. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+13
to
+14
| proactive greeter / room-view recognition fires a richer named | ||
| greet within 1-2 s. |
This was referenced May 23, 2026
BrettKinny
added a commit
that referenced
this pull request
May 23, 2026
* #101: vision/room_view — pure prompt-build + reply parser The room_view roster recognition path emits face_recognized events on roster match, completing the named-greet half that PR #93's bare-greet suppression defers to. Without it, known household members get silent walk-ins instead of "Hi Brett!". This commit lands the pure logic — prompt template, closed-set system prompt, builder, and deterministic parser — as a standalone module so it's testable without FastAPI / asyncio. Wiring into /api/vision/ explain follows in the next commit. Ported verbatim from bridge.py (the retired ZeroClaw bridge, still in the repo as the dashboard service): - bridge.py:561-571 (system prompt) - bridge.py:594 (sentinel) - bridge.py:3370-3413 (prompt template + regex + moods) - bridge.py:3416-3440 (builder) - bridge.py:3443-3482 (parser) Adaptations: - Builder takes registry as a parameter (was a module global on bridge.py); enables unit-testing with a small fake instead of a YAML fixture. - Registry parameter typed against a small _RegistryLike Protocol so the module stays decoupled from HouseholdRegistry's full interface. 17 unit tests cover sentinel/constant contract, builder happy path + empty-registry fallback + raising-registry, parser closed-set match, unknown name, off-roster name, format mismatch, missing/invalid mood, trailing punctuation tolerance. All green; full suite 194/194 (was 177 — 17 new tests). Refs #101 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #101: wire room_view sentinel branch into /api/vision/explain Route layer for the room_view path. On question == "__ROOM_VIEW_V1__": - build roster-aware prompt from the attached HouseholdRegistry - gate-check: skip on dance_active / talk_active / cooldown (mirrors bridge.py:3518-3552); cached no-person + waiter signal on skip - VLM call with ROOM_VIEW_SYSTEM_PROMPT (closed-set name vocab, doubles as the kid-mode safety guard since only roster names + "unknown" can come back) - parse → cache with source="room_view" + room_match_person_id - broadcast PerceptionEvent(name="face_recognized", data={"identity":..., "source": "room_view"}) on roster match — the bus signal FaceGreeter + ProactiveGreeter need to fire the named greet - plumb mood into perception_state[device]["face_mood"] for snapshot Falls back to the v1 description path when the registry is empty or absent — keeps the source="room_view" cache attribution so the dashboard distinguishes capture types. The gate logic lives in a private _room_view_gates_block helper rather than inline so the cooldown integration test can exercise the branch without simulating dance/talk state. 4 integration tests added: - happy path: roster match → cache populated correctly + face_recognized on bus + face_mood in perception state - off-roster: cache populated, NO face_recognized event - cooldown: second call within window skips VLM, caches sentinel - empty registry: sentinel falls back to v1 question, no broadcast Full suite 198/198 (was 194 — 4 new integration tests). Existing v1 route tests untouched. Closes #101 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * #101: Dockerfile — COPY new vision/ package The flat-layout Dockerfile explicitly lists each top-level package as a separate COPY line. The new vision/ package (commit 67e9909) wasn't added to that list, so the deployed image lacked the module and container startup hit `ModuleNotFoundError: No module named 'vision'` on the routes/vision.py import. Refs #101 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 tasks
BrettKinny
added a commit
that referenced
this pull request
May 23, 2026
Tightens PR #93's bare-greet suppression. The previous _roster_is_populated() check suppressed bare "Hi!" whenever the household had ANY member, but the named-greet path (PR #102) requires `appearance:` set to match a person via the room_view VLM. A roster with members configured but no appearances silently walked everyone in forever. Now: suppress only when at least one roster member is identifiable (has non-empty appearance). Members without appearance can't reach the named-greet path, so they correctly fall back to the bare "Hi!". The existing `test_face_detected_suppressed_when_roster_non_empty` was asserting the buggy behaviour; inverted to test the correct semantics. Existing appearance-bearing suppression test preserved. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
appearance:field__len__toHouseholdRegistrysobool(registry)reflects "non-empty"FaceGreeter._roster_has_appearances()→_roster_is_populated()Why
Before: the bare path only suppressed when at least one roster member had a non-empty
appearance:. Households that registered members with justdisplay_name+calendar_prefix(the common config) still saw a bare "Hi!" stack on top of the room-view named greet — double-greeting each walk-in.After: any populated roster suppresses bare-greet. Unidentified faces enter silent-listen-mode by design; identified faces get "Hi NAME" via the room_view recognition path. This matches approach (b) from the issue body — Brett's stated intent.
Test plan
pytest tests/test_consumer_face_greeter.py tests/test_household_registry.py -q— 18 passedpytest tests/ -q(full dotty-behaviour suite) — 177 passedtest_face_detected_suppressed_when_roster_non_emptyasserts the broader behaviour (roster with display_name only, no appearance, still suppresses)test_face_detected_suppressed_when_roster_has_appearancespreserved (appearance-bearing fixture still suppresses)test_face_detected_fires_bare_hi_when_no_rosterunchangedCloses #23
🤖 Generated with Claude Code