Skip to content

Drop-in Guide: agentic 3-phase guide-robot blueprint for Unitree Go2 (muShanghai 2026)#2289

Open
arome3 wants to merge 4 commits into
dimensionalOS:mainfrom
arome3:abraham/drop-in-guide
Open

Drop-in Guide: agentic 3-phase guide-robot blueprint for Unitree Go2 (muShanghai 2026)#2289
arome3 wants to merge 4 commits into
dimensionalOS:mainfrom
arome3:abraham/drop-in-guide

Conversation

@arome3
Copy link
Copy Markdown

@arome3 arome3 commented May 28, 2026

muShanghai 2026 Hackathon Submission — Agents track

Drop-in Guide is an interaction-intelligence layer on dimOS that turns a Unitree Go2 into a zero-prep guide robot for unfamiliar buildings.

Drop a robot in any building. It learns the space by asking questions. It guides anyone through it — and waits if you fall behind.

Submitted to the Agents track with secondary fit in Autonomy & Navigation.

What this PR adds

A new blueprint drop-in-guide plus six dimos/experimental/*_skill.py modules that compose three coherent phases:

Phase What happens Key skill
1 — Generative priming Operator drives Go2 through unfamiliar space. At each spot the robot uses OpenAI Vision (describe_scene) to propose a tag name; operator confirms with one keypress; tag_location fires. scene_caption_skill, teleop_velocity_skill
2 — Guided navigation Visitor says "take me to X". Claude logs the grounding (log_nav_decision), waves, calls navigate_with_text. Auto-waves on arrival when planner's /goal_reached fires. If visitor says "wait" mid-trip, robot pauses and resumes on "okay". arrival_announcer_skill, voice-wait prompt rules
3 — Reactive Q&A "What places do you know?", "Give me a tour", "What did you skip?" reactive_qa_skills

Validated in replay (data/go2_short.db)

  • ✅ All 22 modules load including new ArrivalAnnouncerSkill
  • /goal_reached subscription wires correctly
  • [SYSTEM ARRIVAL EVENT] triggers speak + Hello wave in correct order
  • ✅ Voice-wait flow: "wait, I'm behind"stop_navigation + speak("I'll wait for you here.") (signature line)
  • ✅ Resume: "okay, I'm ready"log_nav_decision + navigate_with_text (resume same target)
  • ✅ Calibrated uncertainty fires when no tagged match (no hallucinated arrival)

Architecture decisions

  • Composes from unitree_go2, not unitree_go2_spatial — avoids CUDA-required modules (EdgeTAM/SecurityModule) so the demo runs natively on Apple Silicon. Real Go2 hardware is the target.
  • Synchronous describe_scene instead of dimOS's default async observe — Vision result returns inline to Claude, eliminating the tool_update race that made the priming dialogue brittle.
  • Out[Twist] teleop bypassteleop_velocity_skill publishes directly to MovementManager.tele_cmd_vel, sidestepping the nav planner's costmap which refuses pure-rotation and pure-backward goals at low map quality during the first walkthrough.
  • /goal_reached → synthetic human_inputArrivalAnnouncerSkill subscribes to the planner's arrival signal and injects a [SYSTEM ARRIVAL EVENT] prompt into McpClient's human_input stream, so the agent auto-greets without a polling loop.
  • JSONL audit trace — every nav decision writes query, matched_tier, confidence, target to nav_trace.jsonl for grounded review. Methodology ported from operator-grounded verification work in healthcare AI.
  • Voice-triggered pause vs camera tracker — the camera-based follower-presence check (lead_with_follow_skill) is unreliable on macOS because the Go2 camera streams continuously regardless of where the visitor is. Voice-triggered pause (visitor says "wait") is more reliable and ships in the prompt as PHASE 2b.

Field-debugging gotchas (worth a heads-up for reviewers)

Tested on real Go2 at venue:

  • obstacle_avoidance=True (default) silently blocked all forward velocity while letting rotation through — symptom was the robot trotting in place. The blueprint sets obstacle_avoidance=False in global_config to keep the demo flowing.
  • PR Forward SHM transports to Rerun and unify Go2 replay IPC #2245 (danvi/experimental/route-replay-through-SHM) is a runtime dependency for the live demo — it contains a camera fix without which describe_scene gets stale frames.
  • macOS multicast routes get clobbered on WiFi change — the daemon needs sudo route add -net 224.0.0.0/4 -interface lo0 after every WiFi event.

How to run

uv sync
# macOS only: route LCM multicast to loopback
sudo route add -net 224.0.0.0/4 -interface lo0
export ANTHROPIC_API_KEY=...
export OPENAI_API_KEY=...
dimos --robot-ip 192.168.12.1 --viewer none run drop-in-guide --daemon

Then either:

Files added

  • dimos/robot/unitree/go2/blueprints/agentic/drop_in_guide.py — main blueprint composition + 3-phase system prompt
  • dimos/experimental/arrival_announcer_skill.py
  • dimos/experimental/decision_audit_skill.py
  • dimos/experimental/lead_with_follow_skill.py
  • dimos/experimental/reactive_qa_skills.py
  • dimos/experimental/scene_caption_skill.py
  • dimos/experimental/teleop_velocity_skill.py
  • DROP_IN_GUIDE_README.md — full thesis, three-intelligences framing, demo arc, operator-console screenshot
  • assets/drop_in_guide_operator_console.png — UI capture (LFS-opt-out via single .gitattributes line)
  • dimos/robot/all_blueprints.py — register drop-in-guide blueprint + scene-caption-skill module

Test plan

  • dimos --replay --replay-db data/go2_short.db run drop-in-guide --daemon → 22 modules start
  • Phase 1: drive Go2 to a feature, press IY → confirm tag via list_tagged_places
  • Phase 2: "take me to X" → robot waves → walks → auto-waves on arrival
  • Phase 2b: "wait, I'm behind" mid-nav → robot stops + says "I'll wait for you here." → "okay, I'm ready" → resumes
  • Phase 3: "what places do you know?" → robot lists tagged places

🤖 Generated with Claude Code

…eprint for Unitree Go2

Submission for muShanghai 2026 (Dimensional Robot Hackathon) — Agents track,
with secondary fit in Autonomy & Navigation.

The blueprint adds the **interaction-intelligence layer** on top of dimOS's
existing motion + task layers, turning the Go2 into a zero-prep guide robot
for unfamiliar buildings.

Phases:
  1. Generative priming — operator drives the Go2; at each spot it observes
     the scene via `describe_scene` (OpenAI Vision), proposes a tag name via
     `speak`, and the operator confirms with a single keypress. Every
     confirmation grounds a `tag_location` call.
  2. Guided navigation — visitor says "take me to X"; Claude logs the
     grounding decision (`log_nav_decision`), waves Hello, sets the goal via
     `navigate_with_text`, and auto-waves on arrival when the planner's
     /goal_reached fires (new `ArrivalAnnouncerSkill`).
  3. Reactive Q&A — `list_tagged_places`, `narrate_tour`, `what_did_you_skip`.

New experimental skills:
  - arrival_announcer_skill — subscribes to /goal_reached and injects a
    synthetic [SYSTEM ARRIVAL EVENT] into McpClient's human_input so the
    agent auto-greets on arrival
  - decision_audit_skill — JSONL nav-decision trace for grounded review
  - lead_with_follow_skill — pauses + speaks "I'll wait for you" when the
    visitor lags behind
  - reactive_qa_skills — list/narrate/skip-recall helpers
  - scene_caption_skill — synchronous OpenAI Vision captioning that Claude
    can use directly during priming (vs async `observe`)
  - teleop_velocity_skill — Out[Twist] WASD teleop that bypasses the nav
    planner; useful when costmap refuses pure-rotation or pure-backward
    goals during priming

The blueprint composes from `unitree_go2` (not `unitree_go2_spatial`) to
avoid CUDA-required modules (EdgeTAM/SecurityModule) on Apple Silicon.

NOTE: depends on PR dimensionalOS#2245 (camera fix) for live Go2 hardware demo.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 28, 2026

Greptile Summary

This follow-up commit addresses the P1 and P2 findings from the initial Drop-in Guide review: the teleop blocking-sleep was moved off the agent's tool-call thread, class-level mutable defaults were removed from ReactiveQASkills and DecisionAuditSkill, LeadWithFollowSkill gained a startup grace period, and SceneCaptionSkill's subscription was registered as a disposable.

  • Subscription cleanup gap: SceneCaptionSkill was correctly updated to register_disposable, but ArrivalAnnouncerSkill.start() still calls self.goal_reached.subscribe(...) without wrapping it — a leaked subscriber that accumulates on stop/restart, injecting duplicate arrival prompts into the agent's human_input stream.
  • Teleop background thread: teleop_velocity now launches a daemon thread and returns immediately, keeping the agent's tool-call loop free. teleop_stop correctly cancels any in-flight burst before publishing zeros.
  • Lead-with-follow frame_age log: when no camera frames have arrived yet, now - self._latest_frame_ts computes to the raw Unix epoch (~1.7 billion seconds) and appears in every 5-second status log; _follower_visible() already guards this case but the log does not.

Confidence Score: 3/5

The fix commit resolves most issues from the initial review, but ArrivalAnnouncerSkill still has an unregistered subscription that can inject duplicate arrival prompts on module restart — the exact pattern that was patched in SceneCaptionSkill in the same commit.

Most previous findings are now addressed: the teleop blocking-sleep is gone, class-level mutable defaults are removed, and scene_caption's subscription is properly disposed. However, ArrivalAnnouncerSkill.start() still calls self.goal_reached.subscribe() without register_disposable — on stop/restart the old callback is not unsubscribed. This means every goal_reached pulse fires the callback twice, injecting two [SYSTEM ARRIVAL EVENT] prompts and causing the agent to speak two arrival lines back-to-back. The fix is a one-liner mirroring what was already done for SceneCaptionSkill in this same commit.

dimos/experimental/arrival_announcer_skill.py — subscription registered without register_disposable, leaking on stop/restart

Important Files Changed

Filename Overview
dimos/experimental/arrival_announcer_skill.py Goal-reached subscription not registered as a disposable — leaks on stop/restart, causing duplicate arrival prompts. The identical pattern was fixed in SceneCaptionSkill in the same commit but missed here.
dimos/experimental/teleop_velocity_skill.py Blocking sleep moved to background thread (_publish_burst) — previous P1 addressed. Class-level threading.Lock shared across instances is a latent issue but harmless in single-instance use.
dimos/experimental/lead_with_follow_skill.py Startup grace period added to prevent premature IDLE exit; frame_age log prints unix epoch when no frames received yet.
dimos/experimental/reactive_qa_skills.py Class-level mutable list defaults removed; _tagged and _skipped are now type-annotation-only, initialized per-instance in start(). Fix looks correct.
dimos/experimental/scene_caption_skill.py color_image subscription now wrapped in register_disposable — subscription leak fixed.
dimos/robot/unitree/go2/blueprints/agentic/drop_in_guide.py obstacle_avoidance=False now extensively documented with rationale and follow-up path.
dimos/robot/all_blueprints.py drop-in-guide entry manually added to an auto-generated file — will be lost if regenerated without the new blueprint being discovered by the test suite.

Sequence Diagram

sequenceDiagram
    participant Visitor
    participant McpClient as Claude (McpClient)
    participant ArrivalAnnouncer as ArrivalAnnouncerSkill
    participant NavPlanner as Nav Planner
    participant Speak as SpeakSkill

    Visitor->>McpClient: "take me to the copier"
    McpClient->>McpClient: log_nav_decision(...)
    McpClient->>Speak: speak("Going to the copier — follow me.")
    McpClient->>McpClient: execute_sport_command("Hello")
    McpClient->>NavPlanner: navigate_with_text("copier")
    NavPlanner-->>McpClient: goal accepted

    Note over NavPlanner,ArrivalAnnouncer: Robot navigating...

    alt Visitor says "wait"
        Visitor->>McpClient: "wait, I'm behind"
        McpClient->>NavPlanner: stop_navigation()
        McpClient->>Speak: speak("I'll wait for you here.")
        Visitor->>McpClient: "okay, I'm ready"
        McpClient->>NavPlanner: navigate_with_text("copier")
    end

    NavPlanner->>ArrivalAnnouncer: "goal_reached=True"
    ArrivalAnnouncer->>McpClient: human_input("[SYSTEM ARRIVAL EVENT]")
    McpClient->>Speak: speak("Here we are at the copier. Anything else?")
    McpClient->>McpClient: execute_sport_command("Hello")
Loading

Reviews (4): Last reviewed commit: "fix(drop-in-guide): address Greptile rev..." | Re-trigger Greptile

system_prompt=DROP_IN_GUIDE_SYSTEM_PROMPT,
),
_common_agentic,
).global_config(n_workers=14, obstacle_avoidance=False)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 obstacle_avoidance=False applies to all navigation, including Phase 2

global_config(obstacle_avoidance=False) is a blueprint-wide setting that turns off collision avoidance for every nav call — including the autonomous guided navigation in Phase 2 where the robot is moving through crowds with real visitors. The PR notes this was needed to prevent the robot from trotting in place during teleop priming (Phase 1), but the fix currently disables safety for the entire session. A robot guiding visitors through a building without obstacle avoidance will drive into people, furniture, or walls without stopping if the planner path passes through an obstacle.

Comment on lines +88 to +101
period = 1.0 / PUB_HZ

deadline = time.time() + dur
pubs = 0
while time.time() < deadline:
self.tele_cmd_vel.publish(msg)
pubs += 1
time.sleep(period)

# Stop — publish zero velocity a few times to be sure.
zero = Twist(Vector3(), Vector3())
for _ in range(3):
self.tele_cmd_vel.publish(zero)
time.sleep(period)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Blocking sleep in skill body freezes the agent's tool-call thread

teleop_velocity holds the calling thread in a time.sleep() loop for up to 5 seconds. The agent (McpClient) invokes skills synchronously, so while this loop is running the agent cannot process any incoming message — including the visitor saying "wait, I'm behind", which is the signature interaction of Phase 2b. Any voice-pause command issued while a teleop move is in progress will be silently delayed until the full duration_s expires, making the pause-on-command guarantee unreliable.

Comment on lines +36 to +37
_tagged: list[dict[str, Any]] = []
_skipped: list[dict[str, Any]] = []
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Mutable class-level list defaults shared across instances

_tagged and _skipped are declared as class attributes holding mutable lists. Python class-level mutables are shared by every instance of the class. start() does reinitialize them per-instance, but any code path that appends to these lists before start() is called — or any second ReactiveQASkills instance that misses its start() — will silently share state with other instances. The same pattern exists in DecisionAuditSkill._trace. The idiomatic fix is to initialize them in start() only (remove the class-level assignment).

Comment on lines +110 to +124
while not cancel.is_set():
state = self._navigation.get_state()
if state == NavigationState.IDLE:
if self._navigation.is_goal_reached():
logger.info(f"lead_to: arrived at '{destination}'")
return
if not paused:
# Nav exited idle without success — either failed or no
# goal was ever set by the agent.
logger.info(
f"lead_to: nav exited IDLE before reaching goal "
f"'{destination}'. The agent likely needs to call "
f"navigate_with_text/set_goal first."
)
return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 _lead_loop can exit immediately if nav hasn't transitioned from IDLE yet

lead_to is called right after navigate_with_text, but the loop's first get_state() call may still see NavigationState.IDLE if the planner hasn't had time to transition to NAVIGATING. In that case, since paused starts as False, the branch at line 116–124 fires and the function returns immediately with a "nav exited IDLE before reaching goal" log, abandoning follower monitoring for the whole trip. Adding a brief startup grace period or checking whether a goal is pending would prevent this false early exit.

arome3 and others added 2 commits May 28, 2026 16:21
Single-file HTML deck at assets/drop_in_guide_slides.html, styled to match
the operator-console aesthetic (dark bg, mint accents, italic serif +
monospace). Self-contained — open in any browser, print to PDF for
slides delivery.

Six slides:
  01 cover
  02 the thesis — inverting system integration
  03 the three intelligences — motion / task / interaction
  04 3-phase runtime — priming, guidance, reactive Q&A
  05 defining gesture — voice-triggered pause + auto-arrival wave
  06 submission summary — PR dimensionalOS#2289 + field-test results

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The HTML deck rendered to PDF for static slide delivery during the live
judging session. Stored alongside the HTML source so reviewers can pick
the format that fits their flow.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment on lines +155 to +198
@skill
def lead_to(self, destination: str) -> str:
"""Lead a visitor to a destination, **pausing if they fall behind**.

Call this AFTER `log_nav_decision` and `speak`, in place of
`navigate_with_text`, when there is a visitor being guided (not just
a delivery). The robot will:

1. Begin moving toward the destination using the same nav stack as
`navigate_with_text` (the agent should have already set the goal
via `navigate_with_text` before calling `lead_to`).
2. Continuously check that the perception pipeline is producing
camera frames (proxy for "visitor still in view").
3. If frames stop arriving: cancel the nav goal, call `speak("I'll
wait for you")`, and poll for reacquisition.
4. On reacquisition: return to the agent for a follow-up call.
5. On arrival: return to the agent for a `speak("Here's the X")`.

This is the defining gesture that distinguishes Drop-in Guide from a
delivery bot. Use it whenever a person is following the robot.

Args:
destination: the tagged-location name to lead the visitor to.
Must match a name from `tag_location`.
"""
if not destination or not destination.strip():
return "Error: destination is required."

with self._lead_lock:
if self._lead_thread is not None and self._lead_thread.is_alive():
return f"Already leading somewhere. Call `stop_navigation` first."
self._stop_event = threading.Event()
self._lead_thread = threading.Thread(
target=self._lead_loop,
args=(destination.strip(), self._stop_event),
daemon=True,
name="LeadWithFollow",
)
self._lead_thread.start()

return (
f"Leading to '{destination}'. I'll pause if the perception "
f"pipeline loses sight of you and resume when it recovers."
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 lead_to docstring promises speak call that never happens

The @skill docstring presented to Claude states "If frames stop arriving: cancel the nav goal, call speak("I'll wait for you"), and poll for reacquisition." But _lead_loop only calls self._navigation.cancel_goal() — there is no speak call anywhere in the thread. LeadWithFollowSkill holds no reference to a speak skill, so it physically cannot emit audio. In Phase 2c, the robot silently cancels the nav goal mid-trip with no audio feedback; visitors observe the robot stopping with no explanation, contradicting the "defining gesture" this module is meant to provide.

P1 fixes:
- teleop_velocity: move publish loop to background thread (_publish_burst).
  Previously time.sleep() in the skill body held the McpClient's tool-call
  thread for up to 5s, blocking the agent from processing the visitor's
  "wait, I'm behind" command mid-burst — undermining the Phase 2b
  voice-pause guarantee. Now the skill returns immediately and a single
  burst thread publishes at PUB_HZ until duration or cancel. teleop_stop
  also cancels any in-flight burst before publishing zeros.
- drop_in_guide blueprint: document the obstacle_avoidance=False tradeoff
  as a hackathon-scope decision with a clear path to per-phase toggling
  in production (track as top follow-up).

P2 fixes:
- ReactiveQASkills, DecisionAuditSkill: drop the class-level mutable list
  defaults (_tagged, _skipped, _trace). start() already reinitializes
  per-instance; the class-level assignment was a shared-state footgun.
- LeadWithFollowSkill: add _STARTUP_GRACE_S window so _lead_loop doesn't
  exit immediately on IDLE before the planner transitions to NAVIGATING
  after navigate_with_text. Was silently dropping every guided trip
  where lead_to was called right after navigate_with_text.

P3 fixes:
- LeadWithFollowSkill.lead_to docstring: remove the self-contradiction
  ("in place of navigate_with_text" vs "the agent should have already
  set the goal via navigate_with_text"). Always call AFTER, never instead.
- SceneCaptionSkill: register the color_image subscription as a disposable
  so the _on_image callback is cleaned up on stop().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

arome3 commented May 28, 2026

Thanks @greptile-apps — addressed all 6 in commit 6b59163. Triage:

P1

  1. obstacle_avoidance=False is global — kept the runtime default (the venue had the avoider silently zeroing positive vx during teleop priming), but added a multi-line code comment at drop_in_guide.py:315 explaining the scope tradeoff and naming the production path: expose set_obstacle_avoidance(enabled) as a skill via GO2Connection.publish_request and toggle per phase from the system prompt. Tracked as the top post-hackathon follow-up. Not a behavior change in this PR.
  2. time.sleep() in teleop_velocity blocks agent thread — fully fixed. Moved the publish loop to a background thread (_publish_burst). The @skill now returns immediately ("Teleop scheduled: ..."), so the McpClient tool-call thread stays free to process subsequent messages — including the visitor saying "wait, I'm behind" mid-burst. teleop_stop now also cancels the in-flight burst via a threading.Event before publishing zeros, so they don't race.

P2

  1. Mutable class-level list defaults (_tagged, _skipped, _trace) — fixed in both ReactiveQASkills and DecisionAuditSkill. Replaced _x: list = [] class assignments with bare type annotations; start() already initialized them per-instance.
  2. _lead_loop exits immediately on IDLE — fixed via a _STARTUP_GRACE_S = 2.5 window. The loop now refuses to exit on IDLE until that grace period elapses, giving the planner time to transition to NAVIGATING after navigate_with_text.

P3

  1. lead_to docstring self-contradiction — fixed. The previous wording said both "in place of navigate_with_text" and "the agent should have already set the goal via navigate_with_text before calling lead_to". Updated to be unambiguous: lead_to is a follower-presence monitor for a navigation that is already in progress. Always called AFTER navigate_with_text, never instead of it.
  2. SceneCaptionSkill.color_image subscription leak — fixed. Subscription now registered as a Disposable so the _on_image callback is cleaned up on stop().

The voice-wait flow was end-to-end verified in replay before the PR opened (full transcript in the PR description). With Fix 2 in place, the same flow should be reliable even when a teleop burst is in progress at the moment the visitor speaks.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 28, 2026

All 6 fixes verified in 6b59163. Quick per-item confirmation:

P1 ✅

  • teleop_velocity now returns immediately ("Teleop scheduled: ..."); the _publish_burst loop runs in a daemon threading.Thread with a threading.Event cancel handle. teleop_stop calls _cancel_active_burst() before publishing zeros — no race between the burst loop and the stop pulses.
  • Obstacle-avoidance comment added in drop_in_guide.py with explicit production path noted.

P2 ✅

  • _tagged/_skipped in ReactiveQASkills and _trace in DecisionAuditSkill are now bare type annotations — shared-state footgun removed; start() initializes per-instance as before.
  • _STARTUP_GRACE_S = 2.5 window is clean. The grace logic reads correctly: in_startup_grace = time.time() < startup_deadline gates the IDLE-exit check, so a lead_to called immediately after navigate_with_text won't terminate before the planner transitions.

P3 ✅

  • lead_to docstring is now unambiguous. The "Typical order" block makes the sequencing explicit.
  • SceneCaptionSkill.color_image.subscribe is now wrapped in Disposable and registered via register_disposable — same pattern as LeadWithFollowSkill.

One minor new item worth tracking post-hackathon:

# teleop_velocity_skill.py
_burst_lock: threading.Lock = threading.Lock()   # class-level mutable default
_burst_thread: threading.Thread | None = None
_burst_cancel: threading.Event | None = None

_burst_lock is a class-level threading.Lock() instance — the same mutable-default anti-pattern as the just-fixed lists. start() resets _burst_thread and _burst_cancel but never rebinds the lock, so all instances share one. With a single blueprint instance this is harmless in practice, but it's inconsistent with the fixes in the other skills. Worth a follow-up annotation alongside the obstacle_avoidance note.

Otherwise the commit is clean — the Phase 2b voice-pause correctness guarantee is now structurally sound. LGTM.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

@leshy leshy added the hackaton label May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants