feat(uno-q): ship the arduino-uno-q expert skill on every node by default

Vendors the comprehensive UNO Q skill (SKILL.md + 7 references/*.md) and installs
it into EVERY agent's workspace on each board, so agents know this board's
specifics (dual-brain arch, Bridge/RPC, pin tables, LED matrix + the
ArduinoGraphics-not-installed gotcha) instead of guessing generic Arduino.

Why per-agent workspace: ZeroClaw's read_skill returns only SKILL.md; the agent
reads references/*.md via the workspace-sandboxed file_read tool, so references
are only reachable under ~/.zeroclaw/agents/<alias>/workspace/skills/. A
shared/skills bundle surfaces the skill but its references get sandbox-blocked.

- push-skill.sh installs a SKILL.md+references skill into every agent workspace
  (discovers aliases from the board); provision-fleet runs it per board.
- config.template risk profile now allows + auto-approves read_skill + file_read
  so agents load skills without a human approver (webhook path is non-interactive).
- Flattened the folded 'description: >-' to single-line (ZeroClaw's frontmatter
  parser is a flat scanner, not full YAML).

Verified on board 65301572 with cloud/Sonnet-5: discovered arduino-uno-q →
read_skill(SKILL.md) → file_read references/04-bridge-rpc.md → correct
board-specific answer citing the file.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-07-16 09:14:00 -07:00
co-authored by Claude Opus 4.8
parent 7233a4b1c0
commit d2135a1938
12 changed files with 877 additions and 2 deletions
@@ -0,0 +1,74 @@
# UNO Q — Workshop Playbook
Practical guidance for running a hands-on session with the board. Assumes the reader already understands the two-processor model (see SKILL.md).
## Pre-flight checklist (per station)
- [ ] Board + a **known-good USB-C cable** rated for **5 V / 3 A** (or a multiport dongle with external PD — **not Apple's**, it's incompatible).
- [ ] For SBC stations: HDMI monitor, USB keyboard/mouse via dongle, power to the dongle.
- [ ] Decide the mode up front: **SBC** (self-contained, needs 4 GB variant recommended) vs **Network Mode** (students drive it from their laptops).
- [ ] Each board: set a **board name** (becomes `<name>.local`), a password, and Wi-Fi during first-run. SSH turns on automatically after this.
- [ ] If any facilitator laptop runs **Linux**, install the **udev rules** first (see setup reference) — otherwise App Lab connects silently to nothing.
- [ ] Network allows **mDNS / UDP 5353** (needed for Network Mode discovery). On locked-down campus Wi-Fi this is the usual blocker.
- [ ] For enterprise/eduroam Wi-Fi, pre-stage the `nmcli` 802.1X command (see wireless reference).
## Common failure modes → fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| App Lab shows no board (Linux PC, USB) | Missing udev rules | Install rules, reconnect board, restart App Lab |
| Board absent in Network Mode | mDNS blocked | Open UDP 5353; approve `mdns-discovery.exe` on Windows; confirm same subnet |
| Flash/upload "permission denied" (Linux) | udev not applied | Re-run rules, `udevadm control --reload-rules && udevadm trigger`, replug |
| Python can't reach the sketch | router down / wrong function name | `systemctl status arduino-router`; check the `provide()` name matches the `call()` string; verbose logs |
| Handler crashes / hangs | Arduino API called from `provide()` (RPC thread) | Use `provide_safe()`; never `Serial.print`/`Bridge.call` inside a handler |
| Nothing on a pin | wrong logic level or wrong processor | MCU I/O is 3.3 V; onboard RGB LEDs are active-low; confirm the sketch (not Python) owns the pin |
| `Serial1` acting weird | confusing header UART with router link | `Serial1` on D0/D1 is fine for external UART; never open the internal reserved transport / `/dev/ttyHS1` |
| Can't SSH | no IP / SSH not set up | `ip addr show` (wlan0) on the board; SSH is enabled once first-run setup completes |
## The canonical teaching example: "Python button decides, Arduino acts"
This one App ties the entire board together and is the best first exercise because it forces students to touch **both** processors and the **Bridge**.
**Concept:** the sketch owns an LED (a physical pin); Python owns the *logic* and calls across the Bridge to actuate it. Swap the LED for a relay/motor driver and you have real device control; swap the Python timer for a camera/AI Brick and you have an intelligent actuator.
**MCU sketch (`sketch.ino`):**
```cpp
#include "Arduino_RouterBridge.h"
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Bridge.begin();
Bridge.provide_safe("set_led_state", set_led_state);
}
void loop() {}
void set_led_state(bool state) {
digitalWrite(LED_BUILTIN, state ? LOW : HIGH); // built-in LED active-low
}
```
**Linux Python (`main.py`):**
```python
from arduino.app_utils import *
import time
state = False
def loop():
global state
time.sleep(1)
state = not state
Bridge.call("set_led_state", state)
App.run(user_loop=loop)
```
**Teaching beats:**
1. Run it → LED blinks, but emphasize *the decision happened in Python on Linux, the action happened on the MCU.*
2. Break it on purpose: rename `"set_led_state"` on one side → show the router log error → fix. Cements the RPC contract idea.
3. Extend: have Python read something real (time of day, a web request, a camera Brick) and gate the LED on it.
4. Reverse it: MCU reads a real sensor and `Bridge.call()`s a Python function that logs/uploads it.
## Progression for a full session
1. **Blink** (App Lab example) — proves the MCU toolchain.
2. **Read a sensor in a sketch**, print with `Serial` → App Lab console — proves I/O + debugging.
3. **Bridge example above** — proves Linux↔MCU.
4. **Add a Brick** (vision/audio) on the Python side — shows the payoff of the Linux half.
5. **Deploy:** set the App to **Run at startup** and unplug the keyboard → it's now an appliance. Manage it over **SSH** + `arduino-app-cli`.
## Deploying student projects headlessly
- Copy the App to the board: `scp -r * arduino@<IP>:~/ArduinoApps/<name>`.
- Start/stop/log via `arduino-app-cli app start|stop|logs ~/ArduinoApps/<name>`.
- Autostart the finished one: `arduino-app-cli properties set default user:<name>`.