Skip to content

#23: bare-greet suppression — any roster member#93

Merged
BrettKinny merged 1 commit into
mainfrom
loop-batch-23-bare-greet
May 23, 2026
Merged

#23: bare-greet suppression — any roster member#93
BrettKinny merged 1 commit into
mainfrom
loop-batch-23-bare-greet

Conversation

@BrettKinny
Copy link
Copy Markdown
Owner

Summary

  • Suppress the bare "Hi!" face-detected greet when the household registry has any members, not just members with an appearance: field
  • Adds __len__ to HouseholdRegistry so bool(registry) reflects "non-empty"
  • Renames 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 just display_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 passed
  • pytest tests/ -q (full dotty-behaviour suite) — 177 passed
  • New test test_face_detected_suppressed_when_roster_non_empty asserts the broader behaviour (roster with display_name only, no appearance, still suppresses)
  • Existing test_face_detected_suppressed_when_roster_has_appearances preserved (appearance-bearing fixture still suppresses)
  • Empty-roster positive case test_face_detected_fires_bare_hi_when_no_roster unchanged

Closes #23

🤖 Generated with Claude Code

…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>
Copilot AI review requested due to automatic review settings May 23, 2026 00:22
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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__() so bool(registry) reflects whether the roster is non-empty (with the usual hot-reload guard).
  • Update FaceGreeter suppression 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 (no appearance).

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.
@BrettKinny BrettKinny merged commit c5f0505 into main May 23, 2026
10 checks passed
@BrettKinny BrettKinny deleted the loop-batch-23-bare-greet branch May 23, 2026 09:38
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>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bare-greet ↔ name-greet stacking suppression

2 participants