Skip to content

dotty-behaviour: load optional .env in compose#100

Merged
BrettKinny merged 1 commit into
mainfrom
dotty-behaviour-env-file
May 23, 2026
Merged

dotty-behaviour: load optional .env in compose#100
BrettKinny merged 1 commit into
mainfrom
dotty-behaviour-env-file

Conversation

@BrettKinny
Copy link
Copy Markdown
Owner

Summary

  • Add env_file: [- path: .env, required: false] to dotty-behaviour/docker-compose.yml so the container picks up host-resident secrets at \$REMOTE_DIR/.env.

Why

Surfaced during the PR #93 bench-test: the new bare-greet suppression worked, but the named-greet path stayed silent. Root cause was VLM calls aborting:

VLM call aborted — no api key set (VLM_API_KEY/VISION_API_KEY/OPENROUTER_API_KEY all empty)

config.py reads those keys from os.environ, but the compose service didn't expose them. PR #91 documented the keys in .env.example; this is the missing plumbing step that actually makes them reach the running container. Without it the room-view, scene-synthesis, security-cycle, and audio-caption paths all silently no-op.

Approach

  • env_file: long-form (compose v2.24+, host runs 2.35.1) with required: false so a missing .env no longer breaks startup. The file lives at /mnt/user/appdata/dotty-behaviour-src/.env on Unraid, owned by the operator. It survives deploys because deploy-behaviour.sh ships only tracked files — .env is untracked and stays put.
  • Compose-only change. No code, no schema, no tests touched.

Test plan

  • docker compose config parses (the env_file long-form is valid for the deployed compose version)
  • Deploy via BEHAVIOUR_HOST=root@tower.local bash scripts/deploy-behaviour.sh — container recreates cleanly with no .env present (required: false)
  • Operator writes .env with OPENROUTER_API_KEY=..., restart, confirm /api/vision/explain returns a non-error VLM response

🤖 Generated with Claude Code

VLM and audio-caption paths read OPENROUTER_API_KEY / VISION_API_KEY /
VLM_API_KEY from the process env, but the compose service didn't expose
those vars to the container — so on the running deployment all VLM
calls aborted with "no api key set" (room-view recognition silent, scene
synthesis silent, audio captioning silent).

Add `env_file: [- path: .env, required: false]`. The file lives at
\$REMOTE_DIR/.env on the host (Unraid:
/mnt/user/appdata/dotty-behaviour-src/.env); it's not part of the deploy
tar (deploy-behaviour.sh ships tracked files only) so it persists across
deploys. `required: false` keeps startup working when no .env exists yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 23, 2026 09:49
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

Adds optional .env injection to the dotty-behaviour Docker Compose service so host-resident secrets (e.g., VLM/OpenRouter API keys) can be passed through to the running container without breaking startup when the file is absent.

Changes:

  • Add env_file (long-form) pointing at .env with required: false to make secret injection optional.
  • Document intended operator workflow for keeping .env untracked and persistent across deploys.

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

# Optional host-resident .env at $REMOTE_DIR/.env (Unraid:
# /mnt/user/appdata/dotty-behaviour-src/.env). Stays out of the
# deploy tar (only tracked files ship) and survives recreates.
# Use for OPENROUTER_API_KEY / VISION_API_KEY / VLM_API_KEY etc.
@BrettKinny BrettKinny merged commit 727961f into main May 23, 2026
10 checks passed
@BrettKinny BrettKinny deleted the dotty-behaviour-env-file branch May 23, 2026 10:01
BrettKinny added a commit that referenced this pull request May 23, 2026
* #111: rip ACP/voice path from bridge.py

ACPClient + MessageIn/MessageOut + /api/message + /api/message/stream +
voice tool helpers + voice escalate/memory_log/remember/remember_person
+ _voice_preparer + _ConvoLogger are gone. /health drops acp_running /
cached_session / session_turns. The dashboard's per-person memory page
keeps its three brain.db read/mutate helpers
(_voice_memory_person_records_blocking / _voice_memory_approve_blocking
/ _voice_memory_delete_blocking) since /ui/memory is still live.

Perception consumers + vision/audio endpoints stay for commits 2-3 of
the #111 rip. _greeter_llm_client is stubbed to RuntimeError so the
ProactiveGreeter (still wired into lifespan until commit 2) fails fast
on its first prompt() call rather than NameError on a missing acp ref.

bridge.py: 6079 → 4564 lines.

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

* #111: rip perception consumers from bridge.py

All 13 in-process perception consumers — face_lost_aborter, sound_turner,
wake_word_turner, face_greeter, face_identified_refresher, purr_player,
named_acknowledger, scene_synthesis (loop is partial — see commit 3),
idle_photographer, sleep_dreamer, dance_reflector, security_capture,
ProactiveGreeter wiring — are gone. The in-memory perception bus
(_perception_state / _perception_listeners / _perception_recent_events
/ _sound_balance_history) goes with them, along with /api/perception/event
+ /api/perception/state + /api/perception/feed, the legacy convo-log
last_user_line cache, and the HouseholdRegistry / SpeakerResolver wiring.

Calendar machinery (_fetch_weather / _fetch_calendar_events / _refresh_caches
/ _calendar_poll_loop / summarize_for_prompt / _build_context /
_build_perception_block / Event TypedDict) goes too — dotty-behaviour
serves /api/calendar/today now (per #100 / dotty-behaviour/routes/calendar.py).

The dashboard perception card now reads empty stubs
(_dashboard_perception_state_getter / *_recent_getter /
*_last_user_line_getter / *_sound_balance_series / *_vision_failures_last_hour
all return empty), so the card renders without errors but shows no data
until the dashboard ports to dotty-behaviour. _identity_display_name
returns None for the same reason.

Dispatch helpers kept: _dispatch_abort, _dispatch_set_state,
_dispatch_set_tier1slim_model, _dispatch_set_toggle — all four are
needed by the dashboard / admin endpoints. The consumer-only dispatchers
(face_greeting / say / set_head_angles / set_face_identified /
purr_audio) went with the consumers.

bridge.py: 4564 → 2868 lines.

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

* #111: rip VLM / vision / audio code from bridge.py

/api/vision/explain, /api/vision/latest/{device_id}, /api/audio/explain
are gone. The room_view sentinel branch (VISION_ROOM_VIEW_SYSTEM_PROMPT,
__ROOM_VIEW_V1__, _build_room_view_question, _parse_room_view_response,
_ROOM_VIEW_* constants) goes with it — dotty-behaviour now owns the
room_view roster recognition path (PR #101 / #102 / #103).

_call_vision_api + VLM HTTP plumbing + _classify_vision_result +
_call_narrative_llm + _call_audio_caption_api + _audio_format_from_upload
go too — dotty-behaviour owns every VLM call now (and dotty-pi-ext owns
narrative calls from voice tools).

Scene-synthesis / idle-photographer / dream / dance NDJSON writers
(_scene_synthesis_log_path / _idle_perception_log_path /
_write_idle_perception_record / _is_notable_perception /
_idle_photographer_pick_device / _dreams_log_path / _dances_log_path /
_write_jsonl_record / _write_dream_record / _write_dance_record /
_split_dream_text / _compose_scene_synthesis /
_write_scene_synthesis_ndjson / _maybe_emit_scene_synthesis /
_perception_sleep_dreamer / _perception_dance_reflector /
_perception_idle_photographer / _scene_synthesis_loop) are gone — every
consumer they fed lives in dotty-behaviour.

The legacy /admin/* endpoints that targeted the retired ZeroClaw daemon
(_apply_model_swap, _read_voice_model_from_cfg,
_voice_profile_for_model, _admin_schedule_restart, _ADMIN_DAEMON_CFG,
/admin/model) were retired alongside — /admin/persona stays for
external persona-file edits, /admin/safety stays as a static
MCP_TOOL_ALLOWLIST mutation surface, /admin/smart-mode keeps just the
tier1slim hot-swap path, /admin/state + /admin/kid-mode keep their
xiaozhi-admin passthrough.

bridge.py: 2868 → 911 lines.

Final state: dashboard-only. Cache stubs (_vision_cache / _audio_cache /
_scene_synthesis_cache / _perception_state) all empty dicts so the
dashboard perception card renders without errors but shows no data
until it ports to dotty-behaviour. The brain.db memory funcs
(_voice_memory_person_records_blocking / *_approve_blocking /
*_delete_blocking) survive because /ui/memory is the only operator
surface on per-person memory rows.

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

---------

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.

2 participants