Skip to content

#16: ZeroClaw cleanup — rip /api/message, ProactiveGreeter, ACPClient + install-bridge fixes#95

Open
BrettKinny wants to merge 6 commits into
mainfrom
loop-batch-16-zeroclaw-cleanup
Open

#16: ZeroClaw cleanup — rip /api/message, ProactiveGreeter, ACPClient + install-bridge fixes#95
BrettKinny wants to merge 6 commits into
mainfrom
loop-batch-16-zeroclaw-cleanup

Conversation

@BrettKinny
Copy link
Copy Markdown
Owner

Summary

Closes the load-bearing post-#36 dead code from the ZeroClaw retirement in four staged commits. Net: ~2,228 LOC removed, bridge.py shrinks from 6,151 → 5,127 (-17%), full test suite stays green.

Phase A — /api/message + /api/message/stream routes

Per #16, these legacy ZeroClaw voice-server endpoints were "unused by real voice traffic" — voice now flows through PiVoiceLLM in xiaozhi-server. Also drops the _dashboard_send_message wrapper and its dead wire-up into bridge/dashboard.py (send_message=... was set but never consumed by the dashboard module). -398 LOC.

Phase B — ProactiveGreeter (Layer 6) rip

bridge/proactive_greeter.py and its test were lifted into dotty-behaviour/consumers/face_greeter.py at #36 cutover. Deletes both files plus the adapter section + lifespan instantiate/teardown blocks in bridge.py. Also renames FastAPI app title from "ZeroClaw Bridge""Dotty Admin Dashboard". -1,150 LOC.

Phase B2 — ACPClient + voice-preparer cascade

With Phases A + B removing all acp.prompt() callers, the 410-LOC ACPClient class is safely orphaned. Also drops the now-dead voice-preparer helper cascade (_voice_preparer, _wrap_voice, _wrap_voice_with_block, _build_perception_block, _build_speaker_block, _resolve_speaker_for_request), MessageIn / MessageOut, the ZEROCLAW_BIN / session-timeout env-var block, and acp.shutdown()/acp.ensure_alive() in lifespan. /health collapses to a shape-check. -670 LOC across bridge.py + tests.

Phase D — scripts/install-bridge.sh

The bridge no longer needs the zeroclaw binary, so the script's --zeroclaw-bin requirement was a stale install-time gate that hard-failed for any fresh-clone user without a Rust workspace. Drops the flag, the existence-check, the Environment=ZEROCLAW_BIN=... from the systemd unit; renames service zeroclaw-bridgedotty-bridge. grep -E 'zeroclaw|ZEROCLAW' now returns zero matches in the file.

Deferred (separate follow-up)

The model-swap path inside bridge.py_apply_model_swap, _admin_model route, _admin_safety route's restart trigger, ZEROCLAW_VOICE_* / ZEROCLAW_DISCORD_* / ZEROCLAW_WORKSPACE env vars, DOTTY_VOICE_PROVIDER="zeroclaw" default — is tangled with the still-live _admin_safety MCP-allowlist editor and the not-yet-implemented PiVoiceLLM smart-mode model swap. #16's original Deferred section already flagged this area as "moot under pi-runtime decom" so it's the natural follow-up.

Test plan

  • python3 -m py_compile bridge.py — clean
  • .venv/bin/pytest tests/ -q — 232 passed, 0 failed (was 262 pre-cleanup; the 30-test delta is the deleted test_proactive_greeter.py (21) + MessageTests+MessageStreamTests (7) + 3→1 HealthTests collapse (2), all expected)
  • bash -n scripts/install-bridge.sh — clean
  • No remaining acp.*, ACPClient, ZEROCLAW_BIN, or class ACPClient references in bridge.py

Closes #16

🤖 Generated with Claude Code

BrettKinny and others added 6 commits May 23, 2026 10:21
…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>
Iterating on .env config required a full bridge restart. Add a maintenance
POST /admin/reload-config that re-reads a whitelist of env vars into the
module globals in place, returning a diff of what changed.

Scope is deliberately tight: vars whose only consumer is the request path
(VISION_*, VLM_*, WEATHER_*, CALENDAR_TTL_SEC). Vars tied to background
loops (CALENDAR_POLL_SEC, CALENDAR_IDS), file-watcher paths, LOCAL_TZ, or
the voice-provider selector are excluded — those need a real restart.

The route lives on the existing _admin_router (localhost-only via
_admin_require_localhost), so no CSRF, no extra auth. Secrets are masked
in the response: first4…last4, or *** for short values.

Tests bypass the localhost guard via dependency_overrides and toggle
bridge.csrf._ENFORCE (the documented kill-switch) since TestClient
requests go through CSRFMiddleware.

Closes #77

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The legacy ZeroClaw voice-server endpoints. Post-#36 voice traffic flows
through PiVoiceLLM in xiaozhi-server; nothing in production hits these
two routes. Per #16 they pay the ACP 24K-token tax for no signal and act
as a misleading probe.

- Drop both routes (305 + 190 LOC).
- Drop the _dashboard_send_message wrapper and the send_message= wire-up
  through bridge/dashboard.py — never consumed by the dashboard.
- Drop the route tests (MessageTests, MessageStreamTests). Baseline
  drops 7 tests; the rest of the suite is unaffected (255 passed).

Refs #16. ACPClient + ProactiveGreeter + ZEROCLAW_* env vars follow in
the next commits — this one removes the load-bearing consumers first so
the dependency chain unwinds cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Layer 6 ProactiveGreeter was lifted into dotty-behaviour as the
face_greeter consumer at #36. bridge.py's copy and its adapters
(_PerceptionBusAdapter, _CalendarFacade, _greeter_llm_client,
_greeter_tts_pusher) have been dead code ever since — instantiated in
lifespan, never reached by any traffic.

- Delete bridge/proactive_greeter.py (574 LOC) and its test file
  (453 LOC).
- Remove the adapter section (~91 lines), the _proactive_greeter
  module global, the lifespan instantiate try-block, and the lifespan
  teardown if-block.
- Rename the FastAPI title from "ZeroClaw Bridge" to
  "Dotty Admin Dashboard" — accurate post-#36.

Tests drop from 255 to 234 (the 21 tests in test_proactive_greeter.py).
All remaining tests pass.

Refs #16. ACPClient + ZEROCLAW_* env vars follow in Phase B2 — now
unblocked since this commit removes the last acp.prompt caller.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase A removed /api/message; Phase B removed ProactiveGreeter. Both
were the live consumers of ACPClient — class is now safely orphaned.

- Delete class ACPClient (~410 LOC) and the `acp = ACPClient()` global.
- Delete ZEROCLAW_BIN / REQUEST_TIMEOUT_SEC / SESSION_* env-var block
  at the top of bridge.py — all tied to ACPClient lifecycle.
- Delete the now-dead voice-preparer helper cascade: _voice_preparer,
  _wrap_voice, _wrap_voice_with_block, _build_speaker_block,
  _build_perception_block, _resolve_speaker_for_request (175 LOC).
- Delete MessageIn / MessageOut / _SessionInvalid (~15 LOC).
- Drop acp.ensure_alive() startup + acp.shutdown() teardown from
  lifespan.
- Collapse /health to {status, service} — the acp_running /
  cached_session / session_turns fields are meaningless without acp.
- Update tests: remove _StubProc, _install_acp_stub, ZEROCLAW_BIN
  setdefaults, and replace the 3-test HealthTests class with a single
  shape-check.

bridge.py shrinks 5754 → 5127 (-627 LOC). All 232 remaining tests pass.

Refs #16. Model-swap path (ZEROCLAW_VOICE_CFG / _UNIT / DISCORD_* /
WORKSPACE env vars + _apply_model_swap) follows in Phase D — it's a
separate functional unit from ACPClient.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ment

bridge.py no longer shells out to the zeroclaw binary post-#36, so the
install script's --zeroclaw-bin requirement is a stale install-time
gate that turns a fresh-clone install into a hard failure for any user
who doesn't happen to have a Rust workspace lying around.

- Drop --zeroclaw-bin flag, ZEROCLAW_BIN default, the existence-check
  block, and the systemd Environment= line for it.
- Rename SERVICE_NAME zeroclaw-bridge → dotty-bridge.
- Rename BRIDGE_DIR default /root/zeroclaw-bridge → /root/dotty-bridge.
- Update Description= and the .env stub header to match.

bash -n lints clean; grep -E 'zeroclaw|ZEROCLAW' on the file now
returns zero matches.

Refs #16. Closes the install-bridge.sh half of the #16 cleanup. The
remaining model-swap rip (_apply_model_swap + admin routes +
ZEROCLAW_VOICE_* env vars inside bridge.py) is deferred to a
follow-up issue — that path was already marked "moot under pi-runtime
decom" in #16's original Deferred section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 removes legacy ZeroClaw-era bridge functionality now that the voice path has moved elsewhere, while also updating the bridge installer/service naming and tightening up behaviour-side face-greeter logic to match the post-cutover architecture.

Changes:

  • Remove /api/message + /api/message/stream, ACPClient, and related voice-preparer/context-building plumbing from bridge.py.
  • Delete the bridge-side ProactiveGreeter (and its tests) and align face-greeting behaviour with the behaviour-side consumer/household registry model.
  • Update scripts/install-bridge.sh to drop the zeroclaw binary requirement and rename the installed systemd service to dotty-bridge; add an admin /admin/reload-config endpoint + tests.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
bridge.py Removes ZeroClaw ACP + message endpoints; simplifies /health; adds /admin/reload-config.
bridge/dashboard.py Removes unused send_message wiring from dashboard configuration state.
bridge/proactive_greeter.py Deletes the legacy bridge-side proactive greeter implementation.
tests/test_proactive_greeter.py Deletes proactive greeter unit tests (feature removed).
tests/test_bridge_routes.py Removes message/message-stream tests; updates /health assertions; adds /admin/reload-config tests.
tests/test_dashboard_csrf.py Removes now-unneeded ZEROCLAW_BIN env setup.
scripts/install-bridge.sh Renames install/service to dotty-bridge and drops --zeroclaw-bin requirement + unit env.
dotty-behaviour/household/registry.py Adds __len__ to support truthiness/population checks.
dotty-behaviour/consumers/face_greeter.py Changes face-detected suppression rule to “registry non-empty” (via truthiness) instead of “has appearances”.
dotty-behaviour/tests/test_consumer_face_greeter.py Adds coverage for the updated face-detected suppression behaviour.
Comments suppressed due to low confidence (1)

bridge.py:99

  • The default kid-mode state path still points at /root/zeroclaw-bridge/..., but scripts/install-bridge.sh now installs into /root/dotty-bridge and the service is renamed to dotty-bridge. This leaves state/log files split across legacy and new directories and undermines the “zeroclaw cleanup” intent. Consider updating the default paths (kid/smart/convo logs) to a dotty-bridge location or setting these via the generated systemd unit’s EnvironmentFile so installs are self-consistent.
_KID_STATE_FILE = Path(
    os.environ.get("DOTTY_KID_MODE_STATE", "/root/zeroclaw-bridge/state/kid-mode")
)
# Voice-daemon LLM is selected by `smart_mode` only. kid_mode is orthogonal —
# guardrails (content sandwich, denied tools, persona) are independent of the
# model. smart_mode OFF → DEFAULT_MODEL. smart_mode ON → SMART_MODEL.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread bridge.py
@@ -635,7 +628,6 @@ def _tier1slim_target_for_smart_mode(enabled: bool) -> tuple[str, str, str]:
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("zeroclaw-bridge")
Comment thread bridge.py
Comment on lines +4805 to +4809
raw = os.environ.get(env_name, "")
try:
new = caster(raw) if raw != "" else (
"" if caster is str else caster(0)
)
Comment thread bridge/dashboard.py
Comment on lines 32 to 33
# Bridge wires its in-process message handler in via configure(). Lets the
# "Say" action invoke the same path /api/message uses without an HTTP hop.
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.

llama-swap migration cleanup backlog

2 participants