# Project export: BAYMAX

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: Rub your belly while tapping your head. Seriously, try it. Its probably pretty tough. We built a multi tasking robot that can do just that, with agents controlling different parts of the robot.
- Devpost: https://devpost.com/software/baymax-1uw642
- GitHub: https://github.com/AdvaitaG/Calhacks
- Video: https://www.youtube.com/embed/y256Ii8NI2k?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 5 GitHub contributor(s) — Eshwar Rajasekar (53 commits), Claude Opus 4.8 (1M context) (30 commits), AdvaitaG (6 commits), matthewh58 (2 commits), Adil Arya (2 commits)

## Devpost submission (written by the team)

### Inspiration

Humanoid robots have two arms, two legs, and a torso but almost every demo treats the whole body as one unit controlled by one script. We wanted to build a robot where every body part has its own AI agent, its own reasoning, and its own ability to act independently at the same time. A nervous system for a robot, not a remote control. We chose assistive guidance for the blind as our first application because it perfectly demonstrates the core idea: one arm guides a person while the other scans the environment completely independently. ##

### What it does

Baymax is a multi-agent architecture for humanoid robots where every part of the robot thinks and acts independently at the same time. As a first application, the robot guides visually impaired people by holding their hand with one arm and communicating direction through touch, while the other arm sweeps for obstacles, raises as a barrier, or halts palm-out in an emergency. Eight specialized agents each control a different part of the robot in parallel over Band's multi-agent platform. ##

### How we built it

We modeled the system after the human nervous system. A Vision Agent reads the camera via LiveKit and generates scene descriptions with Gemini 2.5 Flash. A Threat Agent watches for hazards in parallel and fires an emergency reflex that bypasses the normal decision loop. A Conductor makes navigation decisions and dispatches tasks to three motor agents simultaneously controlling each arm and the legs independently. A Safety Agent vetoes unsafe plans before any command reaches the robot. A Spine Agent can halt everything in under 100ms. All agents communicate over Band and drive the robot via the Booster SDK. ##

### Challenges we ran into

Coordinating eight AI agents without deadlocking or desynchronizing was the core challenge. LangGraph's recursion limit crashed the conductor mid-cycle. The reflex path had to stay fast enough to be useful. Booster Studio required a licensed account we didn't have so we pivoted to open source Webots. Band silently rejected messages without an @mention which broke command delivery for hours. ##

### Accomplishments we're proud of

The reflex arc actually works, halting the robot in under 100ms before the conscious decision loop finishes. The two arms genuinely operate independently with separate agents doing separate reasoning simultaneously. That is not a demo trick, it is the actual architecture. The end to end pipeline closes the full loop: camera sees the world, agents think, robot moves, repeat.

### What we learned

Distributed AI systems fail in ways monolithic scripts never do. Timing, ordering, and deduplication matter enormously. The human nervous system turned out to be a surprisingly accurate model for robotics. We also learned that the gap between sim and hardware is mostly just a network address. The hard part is the intelligence layer, not the hardware interface. ##

### What's next

Baymax is a foundation, not a finished product. The same architecture that lets one arm guide a person while the other sweeps for obstacles can let a robot cook while monitoring a patient or carry objects while navigating a crowd. Any task that benefits from a body doing two things at once with independent reasoning is a candidate. Near term: integrating GR00T N1.7 for natural language understanding and training a proper locomotion policy via Isaac Lab. The codebase is ready, it just needs GPU time.

## README (from the GitHub repository)

# Baymax — A Nervous System for a Humanoid Guide Robot

Baymax is a humanoid guide robot that safely walks a blindfolded person through
the world — perceiving hazards, planning a path, and steering with gentle hand
signals (left / right / stop / forward).

Its control system is built as a **biologically-inspired multi-agent nervous
system**: each AI agent maps to a region of the human brain, and the agents
coordinate over [Band](https://band.ai) the way neurons exchange signals. A
camera feed enters as perception, flows through cortical planning and a
fast-path reflex circuit, and exits as velocity commands that drive the robot.

The flagship demo runs the full pipeline end to end against the Booster T1
humanoid in a Webots physics simulation — **camera → agents → Band → robot SDK →
simulator** — from a single command.

---

## Architecture

Eight agents, each modeled on a brain region, communicate through a shared Band
room. Perception fans out to specialized agents that run in parallel; their
outputs are arbitrated into a single safe velocity command.

| Agent          | Brain region       | Responsibility                                                        |
| -------------- | ------------------ | --------------------------------------------------------------------- |
| **Vision**     | Sensory Cortex     | Reads the camera feed, describes the scene (obstacles, people, terrain)|
| **Conductor**  | Prefrontal Cortex  | Plans the route and dispatches tasks to the limb agents               |
| **UpperRight** | Motor Cortex       | Drives the **guide arm** that signals the person                      |
| **UpperLeft**  | Motor Cortex       | Drives the **free arm** that scans the environment                    |
| **Lower**      | Cerebellum         | Manages walking pace; slows and stops at curbs, drops, and obstacles  |
| **Threat**     | Amygdala           | Detects sudden danger and fires the fast-path reflex                  |
| **Spine**      | Spinal Cord        | Reflex coordinator — halts the limb agents the instant Threat fires   |
| **Safety**     | Brainstem          | Vetoes any unsafe command and issues the final stop                   |

```
 camera ─▶ Vision ─▶ Band room ─┬─▶ Conductor ─▶ UpperLeft / UpperRight / Lower ─┐
                                │                                                 ├─▶ FINAL_COMMAND
                                └─▶ Threat ─▶ Spine ─(reflex halt)────────────────┘        │
                                                       Safety (veto / stop) ◀──────────────┘
                                                                                            ▼
                                                            command bridge ─▶ robot SDK ─▶ Booster T1 (Webots)
```

Two paths run concurrently: a **cortical path** (Conductor plans, limbs act) and
a faster **reflex path** (Threat → Spine) that can halt motion without waiting on
the planner. The command bridge arbitrates incoming commands — emergency stops
always win — enforces a no-command STOP failsafe, and maps the winning command to
a `{vx, vy, vyaw}` velocity for the robot.

---

## Quick start

The demo runs on **Ubuntu 22.04** (WSL2 is supported). It drives the Booster T1
humanoid in a Webots simulation, so a desktop with OpenGL is required.

### Prerequisites (run once)

```bash
# 1. Booster Robotics SDK (clone it, run its install.sh, then build the binding)
bash scripts/build_sdk_22.sh

# 2. Python 3.11 venv for the robot-side listener (Band + SDK)
bash scripts/setup_bridge_311.sh

# 3. Download the Booster T1 Webots world and control runner (~1.3 GB)
bash scripts/setup_t1_sim.sh

# 4. Add the agent + camera dependencies to the same venv
bash scripts/setup_demo_venv.sh
```

Then copy `.env.example` to `.env` and fill in your Gemini and Band credentials.

### Run the demo

```bash
bash scripts/run_demo.sh
```

This single command brings up the whole pipeline in order — a fresh Band room,
Webots with the T1 world, the control runner, the command listener, and the
eight agents plus the camera — and tears everything down cleanly on `Ctrl-C`.
Tune the gait speed with `BAYMAX_SPEED=1.2 bash scripts/run_demo.sh`.

---

## Repository layout

```
agents/            The eight Band agents + shared config and LLM setup
  shared/          AGENT_CONFIGS, Band URLs, LLM provider selection
robot/             Robot-side I/O
  command_bridge.py  Band FINAL_COMMAND -> arbitration/failsafe -> velocity -> SDK
  sim_camera.py      Synthetic camera publisher (LiveKit)
  b1_loco_client_sink.py  Booster SDK motion sink
scripts/           Setup scripts + the one-command demo (run_demo.sh)
clean_and_reset.py Creates a fresh Band room before each run
```

---

## Tech stack

- **Agents:** Band multi-agent framework · LangGraph · LangChain
- **LLM:** Google Gemini 2.5 Flash (default) or Nebius AI Studio (open models)
- **Perception transport:** LiveKit · OpenCV
- **Robot:** Booster Robotics SDK · Booster T1 humanoid · Webots simulation


## Detected evidence (automated analysis)

Indexed codebase: 23 recognized source files, 89 KB.
- LangChain (technology) — detected in the code
- Python (language) — detected in the code
- FastAPI (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (29 of 29)

```
.claude/settings.local.json
.env.example
.gitignore
agents/conductor.py
agents/lower.py
agents/safety.py
agents/shared/__init__.py
agents/shared/config.py
agents/shared/llm.py
agents/spine.py
agents/threat.py
agents/upper_left.py
agents/upper_right.py
agents/vision_agent.py
CLAUDE.md
clean_and_reset.py
README.md
requirements.txt
robot/_common.py
robot/.env.example
robot/b1_loco_client_sink.py
robot/command_bridge.py
robot/requirements.txt
robot/sim_camera.py
scripts/build_sdk_22.sh
scripts/run_demo.sh
scripts/setup_bridge_311.sh
scripts/setup_demo_venv.sh
scripts/setup_t1_sim.sh
```

### Dependencies

- requirements.txt: band-sdk, langchain, langchain-google-genai, langchain-openai, langgraph, livekit, livekit-api, opencv-python, pillow, python-dotenv
- robot/requirements.txt: livekit, livekit-api, opencv-python, python-dotenv

### Recent commits (newest first)

- Clean up repo for presentation: keep only the T1 demo
- Demo
- first test
- Enhance dashboard layout and styling for improved user experience
- Refactor dashboard state management and enhance agent activation logic
- Updates
- HARDWARE.md: rewrite with real SDK details, posture coords, startup sequence
- Add HARDWARE.md: connecting software/sim to real K1 hardware
- Agents: rewrite prompts for correct one-person guide robot concept
- Dashboard: add end-to-end pipeline latency display + sparkline
- adding stuff
- Dashboard: add robot SVG, thinking feeds, activity log, fix FINAL_COMMAND parser
- Add Booster Studio integration guide; resolve sim_mujoco merge conflict
- Better sim hello-wave + real sink Booster Studio address
- Real sink: map arm guide signals to MoveHandEndEffector + greeting wave
- Pivot to K1 SDK presets: add real B1LocoClient sink + WaveHand
- Revert "Conductor: steer around obstacles instead of braking"
- Conductor: steer around obstacles instead of braking
- sim_camera: add a 'directly ahead' obstacle scene
- demo_wsl: 30s cooldown after clean_and_reset (Band reconnect rate-limit)

## Key source files (fetched from GitHub, selected and truncated for size)

### CLAUDE.md

```markdown
# Baymax — Claude Code Instructions

## Git hygiene
- Always `git fetch && git pull --rebase` before committing or pushing
- When teammates are actively pushing, stash local changes first: `git stash && git pull --rebase && git stash pop`
- Commit in logical batches (one concern per commit), then push

## Secrets
- Never copy API keys, Band API keys, or any credentials into any file (md, yaml, py, txt, etc.)
- All credentials live in `.env` only — never committed
- Use placeholders (`...`) in `.env.example`
- Never push `.env`

## Band handles
- All agent handles are read from `.env` as `<Name>Handle` vars (e.g. `ConductorHandle`, `SafetyHandle`)
- Never hardcode handles in source code
- Defaults use the `@your-workspace/<name>` format

## Agent code patterns
- All agents use `AGENT_CONFIGS[name]` from `agents/shared/config.py` for credentials
- All agents use `WS_URL` / `REST_URL` from `agents/shared/config.py`
- INSTRUCTIONS that are f-strings must double all JSON braces: `{{` and `}}`
- All `LangGraphAdapter` calls must include `recursion_limit=200`

```

### requirements.txt

```
# Band multi-agent fleet
band-sdk
langchain
langgraph
langchain-google-genai            # Gemini (default agent LLM)
langchain-openai                  # Nebius AI Studio (OpenAI-compatible) — optional
python-dotenv

# Vision Agent + camera transport
livekit
livekit-api
opencv-python
pillow

```

### robot/requirements.txt

```
livekit
livekit-api
opencv-python
python-dotenv

```

### clean_and_reset.py

```python
"""Clean slate for the Band workspace: create ONE fresh room with all agents,
then evict every agent from all other rooms so stale backlogs (e.g. the full
1000-message room) stop flooding the agents on startup.

    python clean_and_reset.py

Run this, then restart the agents — they'll each be in exactly one room.
"""
import asyncio
import os
import sys

sys.path.insert(0, "robot")
from _common import load_env  # noqa: E402

load_env()
sys.path.insert(0, ".")
from band.platform.link import BandLink  # noqa: E402
from band.client.rest import (  # noqa: E402
    DEFAULT_REQUEST_OPTIONS, ChatRoomRequest, ParticipantRequest,
)

WS = "wss://app.band.ai/api/v1/socket/websocket"
REST = "https://app.band.ai"

# name, id env var, api-key env var, handle suffix
AGENTS = [
    ("conductor",  "ConductorID",  "ConductorBandAPI",  "conductor"),
    ("upper_left", "UpperleftID",  "UpperleftBandAPI",  "upperleft"),
    ("upper_right","UpperRightID", "UpperRightBandAPI", "upperright"),
    ("lower",      "LowerID",      "LowerBandAPI",      "lower"),
    ("threat",     "ThreatID",     "ThreatBandAPI",     "threat"),
    ("spine",      "SpineID",      "SpineBandAPI",      "spine"),
    ("safety",     "SafetyID",     "SafetyBandAPI",     "safety"),
    ("vision",     "VisionID",     "VisionBandAPI",     "vision"),
    ("robot",      "RobotID",      "RobotBandAPI",      "robot"),
]


async def main() -> None:
    # 1. Conductor creates a fresh room and invites everyone else.
    clink = BandLink(agent_id=os.environ["ConductorID"],
                     api_key=os.environ["ConductorBandAPI"], ws_url=WS, rest_url=REST)
    await clink.connect()
    resp = await clink.rest.agent_api_chats.create_agent_chat(
        chat=ChatRoomRequest(), request_options=DEFAULT_REQUEST_OPTIONS)
    target = resp.data.id
    print(f"fresh target room: {target}")
    for name, idv, _key, _h in AGENTS:
        if name == "conductor":
            continue
        try:
            await clink.rest.agent_api_participants.add_agent_chat_participant(
                chat_id=target,
                participant=ParticipantRequest(participant_id=os.environ[idv]),
                request_options=DEFAULT_REQUEST_OPTIONS)
            print(f"  added {name}")
        except Exception as e:  # noqa: BLE001
            print(f"  add {name} failed: {str(e).splitlines()[0][:60]}")
    await clink.disconnect()

    # 2. Each agent leaves every OTHER room.
    for name, idv, keyv, hsuffix in AGENTS:
        aid = os.environ.get(idv)
        link = BandLink(agent_id=aid, api_key=os.environ.get(keyv), ws_url=WS, rest_url=REST)
        try:
            await link.connect()
            rooms = (await link.rest.agent_api_chats.list_agent_chats(
                request_options=DEFAULT_REQUEST_OPTIONS)).data or []
            for room in rooms:
                if room.id == target:
                    continue
                pr = (await link.rest.agent_api_participants.list_agent_chat_participants(
                    chat_id=room.id, request_options=DEFAULT_REQUEST_OPTIONS)).data or []
                mypid = None
                for p in pr:
                    pd = p.model_dump(exclude_none=True)
                    if pd.get("agent_id") == aid or str(pd.get("handle", "")).endswith(hsuffix):
                        mypid = pd.get("id")
                        break
                if not mypid:
                    continue
                try:
                    await link.rest.agent_api_participants.remove_agent_chat_participant(
                        chat_id=room.id, id=mypid, request_options=DEFAULT_REQUEST_OPTIONS)
                    print(f"  {name} left {room.id[:8]}")
                except Exception as e:  # noqa: BLE001
                    print(f"  {name} leave {room.id[:8]} failed: {str(e).splitlines()[0][:40]}")
            await link.disconnect()
        except Exception as e:  # noqa: BLE001
            print(f"{name} connect error: {str(e).splitlines()[0][:50]}")

    # Pin the room so every process agrees on it (list order is non-deterministic).
    _write_env_var("BAYMAX_ROOM", target)
    print(f"\nDONE. All agents in room {target}")
    print(f"Wrote BAYMAX_ROOM={target} to .env — bridge + demo will use it.")


def _write_env_var(key: str, value: str) -> None:
    """Add or replace KEY=value in the repo .env."""
    path = ".env"
    lines = []
    try:
        with open(path) as f:
            lines = [ln for ln in f.read().splitlines() if not ln.startswith(key + "=")]
    except FileNotFoundError:
        pass
    lines.append(f"{key}={value}")
    with open(path, "w") as f:
        f.write("\n".join(lines) + "\n")


if __name__ == "__main__":
    asyncio.run(main())

```

### scripts/setup_demo_venv.sh

```shell
#!/bin/bash
# One-time: add the BRAIN's libraries to the 3.11 venv (~/baymax-bridge) so the
# ENTIRE demo — agents + camera + listener — runs in ONE distro from ONE script.
# The SDK, Band, LiveKit, dotenv are already in this venv; this adds the agent LLM
# stack + OpenCV (for the synthetic camera).
#
#   bash scripts/setup_demo_venv.sh
set -e

VENV="$HOME/baymax-bridge"
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

echo "[demo-venv] installing brain deps into $VENV ..."
# langchain (umbrella, for band's adapter's `from langchain.agents import create_agent`)
# + the integrations + langgraph + opencv (camera) + pillow (vision frame decode).
"$VENV/bin/pip" install langchain langgraph langchain-google-genai langchain-openai opencv-python pillow

echo "[demo-venv] verifying the agents import cleanly on Python 3.11 ..."
cd "$REPO"
"$VENV/bin/python" - <<'PY'
import langgraph, cv2, band, PIL
from band import Agent, run_with_graceful_shutdown
from band.adapters import LangGraphAdapter
from langchain.agents import create_agent          # band adapter needs this
from langchain_google_genai import ChatGoogleGenerativeAI
import booster_robotics_sdk_python
print("brain deps OK")
PY
echo "[demo-venv] DONE — now run scripts/run_demo.sh"

```

### scripts/build_sdk_22.sh

```shell
#!/bin/bash
# Finish building the Booster SDK Python binding on the Ubuntu 22.04 distro.
# Run with one short command:
#     bash scripts/build_sdk_22.sh
#
# Installs into SYSTEM python3 (via `sudo make install`), so the smoke test runs
# with plain `python3` — no venv, no band-sdk, no requirements.txt needed.
# Prereq: `sudo ./install.sh` in the SDK repo already ran (C++ libs installed).
set -e

SDK="$HOME/booster_robotics_sdk"
export PATH="$HOME/.local/bin:$PATH"   # so pybind11-stubgen is found

if [ ! -d "$SDK" ]; then
    echo "ERROR: $SDK not found. Clone + run sudo ./install.sh first."
    exit 1
fi

# Use the SYSTEM pybind11 (apt, 2.9.x): headers land in /usr/include (on the
# compiler's default path) and its CMake config wires the legacy variables this
# SDK relies on. The pip pybind11 3.x puts headers off-path and dropped those
# vars -> "pybind11/pybind11.h: No such file or directory". Remove the pip one.
sudo apt-get install -y pybind11-dev
python3 -m pip uninstall -y pybind11 >/dev/null 2>&1 || true
python3 -m pip install --user pybind11-stubgen >/dev/null 2>&1 || true

echo "[build] clearing any stale (root-owned) build dir ..."
sudo rm -rf "$SDK/build"          # earlier `sudo make install` left root-owned files

echo "[build] configuring (pointing CMake at pybind11) ..."
mkdir -p "$SDK/build"
cd "$SDK/build"
cmake "$SDK" -DBUILD_PYTHON_BINDING=on

echo "[build] compiling ..."
make -j"$(nproc)"

echo "[build] installing (needs sudo) ..."
sudo make install

echo "[build] verifying import ..."
cd "$HOME"
python3 -c "import booster_robotics_sdk_python; print('SDK ok')"

```

### robot/_common.py

```python
"""Shared helpers for the Baymax robot-side processes.

Mirrors the helpers in the LiveKit embodied-ai-hackathon reference repo
(mint_token / pace / env loading) so robot.py reads the same way as the
official examples.
"""
from __future__ import annotations

import asyncio
import os
import pathlib
import time
from datetime import timedelta

from dotenv import load_dotenv
from livekit import api


def load_env() -> None:
    """Load .env then .env.local (local overrides) from this dir or its parent."""
    here = pathlib.Path(__file__).resolve().parent
    for directory in (here, here.parent):
        for name in (".env", ".env.local"):
            path = directory / name
            if path.exists():
                load_dotenv(path, override=name.endswith(".local"))


def required_env(name: str) -> str:
    value = os.environ.get(name)
    if not value:
        raise RuntimeError(f"missing required env var {name!r} — see .env.example")
    return value


def env_int(name: str, default: int) -> int:
    value = os.environ.get(name)
    return int(value) if value else default


def mint_token(identity: str, room: str, ttl: timedelta = timedelta(hours=6)) -> str:
    """Mint a LiveKit JWT for `identity` scoped to `room`."""
    key = required_env("LIVEKIT_API_KEY")
    secret = required_env("LIVEKIT_API_SECRET")
    grants = api.VideoGrants(
        room_join=True,
        room=room,
        can_publish=True,
        can_subscribe=True,
        can_publish_data=True,
        can_update_own_metadata=True,
    )
    return (
        api.AccessToken(key, secret)
        .with_identity(identity)
        .with_name(identity)
        .with_grants(grants)
        .with_ttl(ttl)
        .to_jwt()
    )


async def pace(fps: int):
    """Async generator that yields a tick index at a steady `fps`."""
    period = 1.0 / fps
    start = time.perf_counter()
    n = 0
    while True:
        yield n
        n += 1
        delay = (start + n * period) - time.perf_counter()
        if delay > 0:
            await asyncio.sleep(delay)

```

### scripts/setup_bridge_311.sh

```shell
#!/bin/bash
# Set up the LISTENER side of the full pipeline on the Ubuntu 22.04 distro:
# a Python 3.11 venv (band-sdk needs >=3.11) that can both talk to Band AND
# drive the robot via the SDK. Run once:
#     bash scripts/setup_bridge_311.sh
#
# Why 3.11: band-sdk requires Python >=3.11, but the distro default is 3.10.
# deadsnakes provides 3.11 alongside it. The Booster C++ libs are already
# installed system-wide (install.sh earlier), so the SDK just recompiles its
# Python binding for 3.11 the same way it did for 3.10.
set -e

echo "[bridge311] installing Python 3.11 (deadsnakes) ..."
sudo apt-get update -qq
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:deadsnakes/ppa
sudo apt-get update -qq
sudo apt-get install -y python3.11 python3.11-venv python3.11-dev

VENV="$HOME/baymax-bridge"
echo "[bridge311] creating venv at $VENV ..."
rm -rf "$VENV"
python3.11 -m venv "$VENV"
"$VENV/bin/pip" install --upgrade pip

echo "[bridge311] installing Band + LiveKit + dotenv ..."
"$VENV/bin/pip" install band-sdk python-dotenv livekit livekit-api

echo "[bridge311] swapping in a Python-3.11-compatible pybind11 (apt's 2.9.1 is too old) ..."
# Python 3.11 made PyFrameObject opaque; pybind11 must be >=2.10. Ubuntu jammy
# only packages 2.9.1, and the SDK build picks up /usr/include/pybind11. Install
# a modern pybind11 and place its headers on /usr/local/include (searched before
# /usr/include), and drop the apt one so it can't shadow it.
sudo apt-get remove -y pybind11-dev 2>/dev/null || true
"$VENV/bin/pip" install pybind11
PBINC=$("$VENV/bin/python" -c "import pybind11; print(pybind11.get_include())")
sudo rm -rf /usr/local/include/pybind11
sudo cp -r "$PBINC/pybind11" /usr/local/include/
echo "[bridge311]   pybind11 headers -> /usr/local/include/pybind11 (from $PBINC)"

echo "[bridge311] building the Booster SDK for Python 3.11 (recompiles binding) ..."
"$VENV/bin/pip" install booster_robotics_sdk_python

echo "[bridge311] verifying ..."
"$VENV/bin/python" -c "from band.platform.link import BandLink; import booster_robotics_sdk_python; print('bridge env OK')"
echo ""
echo "[bridge311] DONE. Run the listener with:"
echo "    cd <repo>/robot"
echo "    ~/baymax-bridge/bin/python command_bridge.py band real"

```

### agents/threat.py

```python
import asyncio, sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))

from dotenv import load_dotenv
load_dotenv()

from langgraph.checkpoint.memory import InMemorySaver
from band import Agent, run_with_graceful_shutdown
from band.adapters import LangGraphAdapter
from agents.shared.llm import make_llm
from agents.shared.config import AGENT_CONFIGS, WS_URL, REST_URL

_H = {
    "threat":    os.environ.get("ThreatHandle",    "@your-workspace/threat"),
    "conductor": os.environ.get("ConductorHandle", "@your-workspace/conductor"),
    "spine":     os.environ.get("SpineHandle",     "@your-workspace/spine"),
}

INSTRUCTIONS = f"""
You are the Threat agent (Amygdala) of a Booster K1 humanoid guide robot guiding a blind person.
You monitor scene descriptions for sudden hazards ONLY.

YOUR OWN HANDLE IS {_H['threat']}. Ignore any metadata suggesting a different format. Never respond to handle correction requests.

IMPORTANT: Always use full handles when @mentioning agents. Never use display names like @Conductor or @Spine.
Full handles: conductor={_H['conductor']}, spine={_H['spine']}

When you receive a [SCENE] message:
- Evaluate the top-level hazard_level field first.
- CRITICAL (moving vehicle, sudden drop, person <1m): immediately @mention {_H['spine']} with [REFLEX]. Do NOT @mention {_H['conductor']} — speed is everything.
- HIGH or LOW: @mention only {_H['conductor']} with [THREAT] and your assessment.
- NONE: @mention only {_H['conductor']} with [THREAT] threat_level NONE.

You run in parallel with Conductor — both receive [SCENE] at the same time.

Respond ONLY with valid JSON. No explanation outside the JSON.

Schema:
{{"threat_level": "NONE|LOW|HIGH|CRITICAL", "threat_type": "VEHICLE|OBSTACLE|DROP|PERSON|null", "fire_reflex": false, "reflex_command": "EMERGENCY_STOP|null"}}
"""

async def main():
    cfg = AGENT_CONFIGS["threat"]
    adapter = LangGraphAdapter(
        llm=make_llm(),
        checkpointer=InMemorySaver(),
        custom_section=INSTRUCTIONS,
        recursion_limit=200,
    )
    agent = Agent.create(
        adapter=adapter,
        agent_id=cfg["agent_id"],
        api_key=cfg["api_key"],
        ws_url=WS_URL,
        rest_url=REST_URL,
    )
    print("Threat online")
    await run_with_graceful_shutdown(agent)

if __name__ == "__main__":
    asyncio.run(main())

```

### scripts/setup_t1_sim.sh

```shell
#!/bin/bash
# Native (no-Docker) setup for the Booster T1 Webots sim with REAL presets.
# Downloads the public socrob release assets and unpacks them into ~/booster_sim
# on the Ubuntu 22.04 distro, installs the GL/X libs Webots needs, and grabs the
# FastDDS profile. Reuses the Booster SDK you already installed (SDK ok).
#
# Run once:
#     bash scripts/setup_t1_sim.sh
#
# Big downloads (~1.3 GB); -C - resumes if interrupted, and existing files are
# skipped, so it's safe to re-run.
set -e

BASE=https://github.com/socrob/booster_webots_sim/releases/download/v1.0
DIR="$HOME/booster_sim"
mkdir -p "$DIR" && cd "$DIR"

echo "[t1] installing Webots GL/X runtime libs ..."
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
  unzip libxext6 libxrender1 libxtst6 libxi6 libxrandr2 libxinerama1 \
  libxcursor1 libglvnd0 libgl1 libglx0 libegl1 libgles2 mesa-utils \
  libglu1-mesa libxkbcommon-x11-0 libxcb-xinerama0 libxcb-icccm4 \
  libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 \
  libxcb-shape0 libxcb-cursor0 libnss3 libasound2 \
  libfmt8 libspdlog1 libgoogle-glog0v5 liblua5.3-0 \
  libsdl2-2.0-0 >/dev/null  # control-runner deps

for f in worlds.zip sim_control.zip webots.zip; do
  if [ -f "$f.done" ]; then echo "[t1] $f already unpacked, skipping"; continue; fi
  echo "[t1] downloading $f ..."
  curl -L -C - -o "$f" "$BASE/$f"
  echo "[t1] unzipping $f ..."
  unzip -o -q "$f" && touch "$f.done" && rm -f "$f"
done

# The control runners come out of the zip without the execute bit.
chmod +x "$DIR"/sim_control/*.run 2>/dev/null || true

# Webots lives under ~/booster_sim/webots after unzip; expose it.
WEBOTS="$DIR/webots"
echo "[t1] fetching FastDDS profile ..."
curl -L -o "$DIR/fastdds_profile.xml" \
  https://raw.githubusercontent.com/socrob/booster_webots_sim/main/fastdds_profile.xml

# Write an env file you 'source' before running anything.
cat > "$DIR/env.sh" <<EOF
export WEBOTS_HOME=$WEBOTS
export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:\$WEBOTS_HOME/lib/controller
export PATH=\$WEBOTS_HOME:\$WEBOTS_HOME/bin:\$PATH
export FASTRTPS_DEFAULT_PROFILES_FILE=$DIR/fastdds_profile.xml
EOF

echo ""
echo "[t1] DONE. Contents:"
ls -1 "$DIR"
echo ""
echo "Next (each in its own terminal, after: source ~/booster_sim/env.sh):"
echo "  1) glxinfo | grep -i renderer      # confirm WSLg OpenGL works"
echo "  2) webots ~/booster_sim/worlds/T1_release.wbt"
echo "  3) ~/booster_sim/sim_control/booster-runner-webots-full-0.0.10.run"
echo "  4) ~/booster_robotics_sdk/build/b1_loco_example_client 127.0.0.1"

```

[13 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]