Skip to content

fix(slv): wire dashboard memory slider to SLV backend#7

Closed
Love4yzp wants to merge 25 commits into
suharvest:masterfrom
Love4yzp:fix/slv-memory-history
Closed

fix(slv): wire dashboard memory slider to SLV backend#7
Love4yzp wants to merge 25 commits into
suharvest:masterfrom
Love4yzp:fix/slv-memory-history

Conversation

@Love4yzp

Copy link
Copy Markdown
Collaborator

「通用/General」的记忆滑块在 SLV 下是死的:get_history/set_history 读写 legacy 才有的 conv._client._config.max_history(hasattr 守卫→静默跳过)。且 SLV 用裸 Session()、没设 max_input_tokens,runner 只 add_assistant→历史无上限增长,文档里的 ollama_max_history(默认3, 0=无记忆) 从未被遵守。

改:SLV 加 get_history_turns()/set_history_turns()/_cap_history_turns(),把 session.history 裁到最近 N 个 user 锚定回合(工具回合算1个,0=清空),每回合开始+滑块改动即时生效。dashboard 两个 handler 改为 backend 无关(优先 SLV 方法→回退 legacy→回退 config)。legacy 行为不变。

裁剪逻辑单测覆盖 N<总数/N≥总数/N=0/空/工具回合/头部orphan 全过;py_compile 通过。⚠️ 机器人离线,尚未设备实测。

🤖 Generated with Claude Code

Love4yzp and others added 25 commits June 4, 2026 17:17
Exhibition-tuning batch for the SLV Reachy deploy:
- audio: selectable mic input channel (audio_input_channel / CLAWD_AUDIO_INPUT_CHANNEL),
  lower client-VAD silero threshold (0.3) for the Reachy Mini mic, TTS service URL.
- vad: v2v_client_vad_threshold defaults to None (backend default) instead of 0.5.
- vision: stop driving head pitch/roll from the exhibition camera (bbox/landmark
  coords are too noisy) — publish base body_yaw only, head stays neutral.
- vision: stop queueing emotion motion from the vision stream (log-only now).
- emotions: listening/thinking become antenna-only (no head pose).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the head a single-writer resource so expression layers compose instead
of stomping each other (the root cause of head jitter / 'pulling the wrong
way' was three controllers writing the Stewart platform independently).

The head_tracking loop now computes the gaze anchor (face/DOA + body yaw) and
sums additive layers on top — emotion accents, speech wobble, idle micro —
clamps into a safe envelope, and emits ONE velocity-limited target:

- Emotion tags inject a transient _HeadAccent (relative offset with
  attack/hold/release smoothstep envelope) that rides on top of gaze and
  decays back to it, instead of commanding an absolute pose via goto_target.
- Speech wobble folded into the same per-tick sum (unchanged feel).
- Idle micro-motion layer (off by default: motion_head_idle_micro).
- Tunable: motion_emotion_accent_gain (0.6), motion_head_{yaw,pitch,roll}_limit.
- Preserves daemon-compat split + deadbands; legacy body tracking still
  never re-anchors the head.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Promote the compositor getattr defaults to first-class Config fields with a
`motion:` yaml section, so accent strength / idle micro can be tuned on the
robot without code edits:
- motion_emotion_accent_gain (0.6), motion_head_idle_micro (false),
  motion_head_{yaw,pitch,roll}_limit (25/18/18).
- slv deploy yaml gains a motion: section (accent_gain + idle_micro set;
  limits commented to defaults).
motion_plugin reads these via getattr, so behavior is unchanged at defaults.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audio capture intermittently bound the wrong device on Jetson: PortAudio
in-container sometimes drops the USB card from enumeration, so _find_device
returned None and capture fell back to the onboard Tegra audio (default
device) — which has no real mic, so ASR only ever saw noise/silence and
returned empty transcriptions. The duplex/bg-reader candidate lists also
hardcoded hw:0,0 / plughw:0,0, but the Reachy Mini USB Audio is on ALSA
card 2 here (cards 0/1 are the Jetson HDA/APE), so those never matched.

Discover the ALSA card index from /proc/asound/cards (the kernel's
authoritative view, unaffected by the PortAudio race) and target
plughw:<card>,0 / hw:<card>,0 as the primary capture candidates, falling
back to the resolved PortAudio index then the default. Device-agnostic:
no card number is hardcoded.

Verified on the Jetson (192.168.1.113): logs now show "resolved ALSA card
2" and the duplex stream opening on hw:2,0 instead of the onboard fallback.

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

_visual_attention_active() gated on _last_face_trigger_time being within the
attention window, but that timestamp is the sparse face-GREETING trigger: it
fires once per visitor arrival and then sits on _face_trigger_cooldown_s. A
visitor standing in front of the robot longer than the window (but silent
since the greeting) read as inattentive, so their short utterances were
dropped by the meaningful-text gate even while clearly engaging the robot —
the wake-word-free flow felt dead.

Key attention off continuous presence instead: _last_face_time /
_last_face_count, which the vision plugin refreshes every frame a face is
seen. Keeps the face soft-gate (still needs a face present to accept short
unattended speech) but makes it actually track who is in front of the robot.

Both existing attention tests still pass (face present → short text accepted;
no vision plugin → dropped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes intermittent empty ASR: audio capture bound the wrong device
(PortAudio enumeration race → onboard Tegra instead of USB mic) and the
voice attention gate keyed off the sparse face-greeting trigger so short
utterances were dropped while a face was present. Verified live on the
Jetson — real ambient speech now transcribes correctly.

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

Ports the reachy-mini SDK upgrade onto master (the original work lived on the
disjoint feat/head-compositor line). Same logical changes, re-applied to
master's current files.

- pyproject: reachy-mini>=1.8.0, websockets>=15,<16 (SDK WS transport ceiling),
  numpy>=2.2.5 (SDK requirement), httpx>=0.28.0.
- tests/test_sdk_contract.py (new): introspects the *real installed* SDK (not
  the MagicMock, which auto-stubs anything) to pin the motion/media/pose/
  app-base API surface — the post-upgrade verification gate.
- Daemon data-loop fix: the Zenoh->WebSocket migration (SDK 1.5.0) left
  _ensure_daemon probing the dead Zenoh port 7447, and spawning the daemon
  without --fastapi-port (so it bound 8000 while the client dialed
  reachy_daemon_port=38001). Now spawn/probe/connect all use
  reachy_daemon_port and the spawn passes --fastapi-port — matching the
  Jetson docker deploy. Covered by tests/test_daemon_readiness.py (new).
- conftest: add enable_motors/disable_motors to the mock (real call sites
  that were silently auto-stubbed).
- Stale Zenoh references corrected in README and face-tracker log labels.

Verified: full suite 646 passed, 58 skipped, 0 failed on reachy-mini 1.8.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ned vision-cm4

Tidy-up pass to make the runtime architecture legible and align versions:

- docs/ARCHITECTURE.md: live component/version/port map (mermaid), the three
  data loops (conversation / vision-attention / control), a deploy-layout matrix
  (live vs legacy parallel tree vs alternative-hw vs removed), a code-layout
  matrix (active SLV backend vs legacy conversation_plugin vs clientloop
  runtime), the "claw" naming origin, and a retirement checklist.
- deploy/jetson/reachy/Dockerfile.daemon: pin reachy-mini>=1.8.0 (was unpinned,
  which is why the live daemon floated to 1.5.1 while the client built at 1.8.0).
- scripts/: verify_reachy_sdk.sh (read-only diagnostic + optional motion test),
  backup_robot_reachy.sh (non-destructive image/state backup),
  rebuild_deploy_reachy.sh (Jetson-native rebuild+redeploy), hotpatch_reachy_sdk.sh.
- Remove orphaned deploy/vision-cm4 (0 references; recoverable from git history).

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

The "记忆/Memory" slider was dead under the SLV backend: get_history/set_history
reached into `conv._client._config.max_history` / `conv._client._history`, which
only exist on the legacy plugin (guarded by hasattr → silent no-op). Worse, SLV
builds a bare Session() with no max_input_tokens and the runner only appends to
it, so conversation history grew UNBOUNDED — the documented `ollama_max_history`
(default 3, 0 = stateless) was never honored.

SLV plugin now implements the contract the dashboard expects:
- get_history_turns() / set_history_turns(n): read/persist ollama_max_history.
- _cap_history_turns(): trim self._session.history to the last N user-anchored
  turns (matching ovs_agent's own turn grouping; tool turns counted as one;
  0 = drop all prior history). Applied at the start of each turn (before the
  new user message) and live when the slider changes.

dashboard_plugin get_history/set_history made backend-agnostic: prefer the SLV
methods, fall back to the legacy _client path, else read config. Legacy backend
behavior unchanged.

Trim logic unit-tested for: N<count, N>=count, N=0 (stateless), empty history,
tool turns, and leading-orphan rollback. py_compile clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Love4yzp Love4yzp closed this by deleting the head repository Jun 12, 2026
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.

1 participant