A desk + laptop companion for coding agents on the Arduino UNO Q.
Gemma 3 1B runs locally on the board as the triage brain. A physical Allow/Deny button on your desk guards your machine.
When coding agents (Claude Code, Cursor, Codex, Antigravity) run in the background, you face a constant dilemma: babysit terminal windows or tab away and stall out waiting for an approval prompt.
Nudge moves your agent's permission gate from a hidden terminal tab to your physical desk:
- Desk (Arduino UNO Q): Meets Pip, a vigilant little desk creature. His 13x8 LED-matrix face reacts to your agent's state in real-time. Gemma 3 1B runs directly on the board's Linux MPU via the App Lab
arduino:llmbrick, triaging incoming events and keeping the gate closed until you press the physical green Allow button. - Laptop (Sidecar & Island): A top-edge island for Linux (Wayland / X11) and a universal hook watcher. It intercepts agent actions, strips confidential environment variables, compresses commands into safe 1-liners, and binds approvals strictly to unique request IDs.
Gemma 3 1B is the brain on the board. Your laptop is the hands with an allowlist.
flowchart LR
subgraph laptop [Laptop / Workstation]
Hooks["Agent Hooks (Cursor / Claude / Codex)"]
Island["Top-Edge Island (GtkLayerShell / Dock)"]
Sidecar["Nudge Sidecar (Port 7474)"]
Hooks -->|"JSON hook event"| Sidecar
Sidecar <-->|"Allow / Deny UI"| Island
end
subgraph board [Arduino UNO Q]
Py["Python Agent (Port 7000 / 8766)"]
Gemma["Local Gemma 3 1B (llama.cpp)"]
MCU["MCU Sketch (Bridge RPC)"]
Matrix["13x8 LED Matrix Face"]
Buttons["Physical Allow / Deny Pins"]
Web["Web UI (Canvas 2D Pip)"]
Py -->|"Triage prompt"| Gemma
Gemma -->|"Validated Tool JSON"| Py
Py <-->|"RouterBridge"| MCU
MCU --> Matrix
Buttons --> MCU
Py --> Web
end
Sidecar -->|"Sanitized Event (POST /api/event)"| Py
MCU -->|"Physical Button Press"| Py
Py -->|"Bound Decision (POST /api/decision)"| Sidecar
Sidecar -->|"Exit 0 (Allow) or 1 (Deny)"| Hooks
Pip's brain is Gemma 3 1B, running locally on the Arduino UNO Q via the App Lab arduino:llm brick.
RAW AGENT HOOK
"git push origin main"
│
▼
┌────────────────────────┐
│ Laptop Sidecar (Hands)│ ── Strips secrets, truncates to 200 chars
└───────────┬────────────┘
│ POST /api/event
▼
┌────────────────────────┐
│ Arduino Linux MPU │
│ ┌──────────────────┐ │
│ │ Gemma 3 1B │ │ ── Runs locally on board via llama.cpp
│ │ (arduino:llm) │ │
│ └────────┬─────────┘ │
│ │ Strictly Validated Tool JSON
│ ┌────────▼─────────┐ │
│ │ Python Triage │ │ ── Drops shell commands, validates schema
│ └────────┬─────────┘ │
└───────────┼────────────┘
│ RouterBridge RPC
▼
┌────────────────────────┐
│ Zephyr MCU │
│ • 13x8 LED Matrix │ ── Lights up animated emoji faces & blinks
│ • Physical Buttons │ ── Tactile Allow / Deny approval
└────────────────────────┘
- Model Declaration:
applab/app.yamlbindsarduino:llm(llamacpp:gemma-3-1b-it-Q4_0) andarduino:web_uion port 7000. - Triage & Validation Engine:
applab/python/main.pyhosts theGemmaTriageclass. It prompts Gemma 3 1B with Pip's character persona and parses tool JSON. - Strict Allowlist: The model can only emit
request_approval,notify,focus_session, andopen_island. It has no shell execution tool. - Local Testing: You can test with any local LLM server on your laptop (e.g. Ollama via
export LLM_BASE_URL="http://localhost:11434/v1"andexport LLM_MODEL="gemma2:2b").
The Arduino UNO Q has a built-in 13×8 LED Matrix (104 LEDs on the board). Pip's facial expressions light up dynamically on the physical hardware via applab/sketch/sketch.ino.
Here is how the physical LEDs light up for each mood:
Pip stands guard on your desk. Every 3–5 seconds, his eyelids drop for 180ms to blink naturally:
Open Eyes: Blink (180ms):
. . . . . . . . . . . . . . . . . . . . . . . . .
. . ■ ■ . . . . . ■ ■ . . . . . . . . . . . . . .
. . ■ ■ . . . . . ■ ■ . . . . ■ ■ ■ . . . ■ ■ ■ .
. . . . . . . . . . . . . . . . . . . . . . . . .
. ■ . . . . . . . . . ■ . . ■ . . . . . . . . ■ .
. . ■ ■ ■ ■ ■ ■ ■ ■ ■ . . . . ■ ■ ■ ■ ■ ■ ■ ■ ■ .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
When your agent asks for permission (e.g. git push), the LED matrix alternates every 350ms between two frames, flashing an urgent attention signal across your desk:
Frame A (Pulsing Eyes): Frame B (Flash):
. . . . . . . . . . . . . ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
. ■ ■ ■ ■ . . . ■ ■ ■ ■ . ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
. ■ ■ ■ ■ . . . ■ ■ ■ ■ . ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
. . . . . . . . . . . . . ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
. . . . . . . . . . . . . ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
. ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ . ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
. . . . . . . . . . . . . ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
. . . . . . . . . . . . . ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
When you press the green Allow button or your agent completes its work:
. . . . . . . . . . . . .
. . ■ ■ . . . . . ■ ■ . .
. ■ . . ■ . . . ■ . . ■ .
■ . . . . ■ ■ . . . . . ■
. ■ . . . . . . . . . ■ .
. . ■ ■ ■ ■ ■ ■ ■ ■ ■ . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
When a command is denied or Pip is poked while working:
. . . . . . . . . . . . .
. ■ ■ ■ ■ . . . ■ ■ ■ ■ .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
Triggered when you poke Pip 3+ times in rapid succession:
. ■ . . . ■ . ■ . . . ■ .
. . ■ . ■ . . . ■ . ■ . .
. ■ . . . ■ . ■ . . . ■ .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . ■ . ■ . ■ . ■ . ■ . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
When no agent sessions have been active:
. . . . . . . . . . ■ ■ ■
. . . . . . . . . . . . ■
. . . . . . . . . . ■ ■ ■
. . ■ ■ ■ . . . ■ ■ ■ . .
. . . . . . . . . . . . .
. ■ . . . . . . . . . ■ .
. . ■ ■ ■ ■ ■ ■ ■ ■ ■ . .
. . . . . . . . . . . . .
| Mood | Expression | Behavior |
|---|---|---|
idle |
Gentle blinking, curved smile | Waiting for coding agent sessions. |
peek |
Shifted pupils, perked ears | Detects an agent session initializing. |
watching |
Focused wide pupils | Tracking steps (running tests, reading files). |
needs_you |
Pulsing alert, wide pleading eyes | Permission requested. LED matrix pulses, waiting for physical Allow button. |
happy |
Cheerful curved eyes ^^, bouncing |
Permission granted or session successfully completed! |
annoyed |
Flat skeptical brows - - |
Permission denied or invalid action. |
dizzy |
Spinning crossed eyes X X |
Triggered by 3+ rapid physical or virtual pokes! |
asleep |
Closed eyelids with floating z Z |
Idle timeout when no sessions have run. |
- Download
dist/nudge-applab.zip(or build withpython3 scripts/build_applab_zip.py). - Open Arduino App Lab on your UNO Q.
- Click Apps → Import Apps and select
nudge-applab.zip. - Click Run. Pip's Web UI will be live at
http://<board-ip>:7000.
On your Linux laptop:
# Start the background sidecar daemon
python3 laptop/sidecar.py
# (Optional) Open the top-of-screen island
python3 laptop/island.pyThe sidecar will display a 6-digit pairing PIN (e.g. [ 482910 ]).
Open Pip's Web UI at http://localhost:7000 (or your UNO Q IP), click Pair Laptop, and enter the 6-digit PIN. The board and laptop exchange a cryptographic session token over your local LAN.
Want to see the full flow without configuring agent hooks?
python3 laptop/demo_session.pyOr click Try 60s Demo in Pip's Web UI. Watch Pip detect the agent, pulse when git push is requested, and jump with joy when you tap Allow!
To link your real coding agents to Nudge, point their permission hooks to laptop/hooks/nudge-hook.
Add to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "/absolute/path/to/nudge/laptop/hooks/nudge-hook"
}
]
}
}In your Cursor custom hook or CLI runner:
echo '{"kind":"approval","agent":"cursor","command":"npm publish","request_id":"uuid"}' | ./laptop/hooks/nudge-hookFail-Open Guarantee: If Nudge is not running, nudge-hook exits immediately with code 0 so your work is never blocked.
- Request ID Binding: Every approval request is bound to a unique
request_id. Stale decisions or late button presses are dropped. - Sanitized Payloads: The laptop sidecar strips secrets, tokens, and truncates commands to 200 characters before transmitting to the board.
- No Remote Shell: Gemma 3 1B has no execution shell tool. It can only request approvals or dispatch allowlisted UI actions (
notify,focus_session,open_island). - LAN-Only Pairing: The sidecar only listens locally. Pairing is protected by a rotating 6-digit PIN and token authentication.
├── applab/ # Importable Arduino App Lab Package
│ ├── app.yaml # App definition (bricks: arduino:llm, arduino:web_ui)
│ ├── python/main.py # Python agent: Gemma 3 1B triage, Bridge RPC, Web UI
│ ├── sketch/ # MCU code: 13x8 LED Matrix face & button debouncing
│ │ ├── sketch.ino
│ │ └── sketch.yaml
│ └── assets/ # Web UI (Liquid blob Pip: 16-point Bezier jelly, dark CSS)
│ ├── index.html
│ ├── style.css
│ └── pip.js
├── laptop/ # Laptop Sidecar & Top-Edge Island
│ ├── sidecar.py # Local daemon: hook sanitizer & LAN router
│ ├── island.py # Top-edge desktop island (Wayland / X11)
│ ├── demo_session.py # Standalone 60s demo simulator
│ └── hooks/
│ └── nudge-hook # CLI hook script for Claude Code / Cursor / Codex
├── scripts/
│ └── build_applab_zip.py # Packaging script generating dist/nudge-applab.zip
├── dist/ # Packaged release archives
│ ├── nudge-applab.zip
│ └── nudge-applab.zip.sha256
├── protocol.md # Wire protocol specifications
├── DEMO.md # 60-second judge demo script
├── DEV_POST.md # DEV Community Hacktoberfest submission article
├── LICENSE # MIT License
└── README.md
- Concept Inspiration: Inspired by Coucou by Louis Raillé. Nudge is a clean-room, original implementation built specifically for physical Arduino hardware, local Gemma 3 1B inference, and the Arduino App Lab ecosystem.
- Character: Pip is an original procedural Canvas 2D character created for Nudge.
- Author: Built for Hacktoberfest 2026 (Week 2: Side Hustle).
MIT © 2026 Nudge Contributors. See LICENSE for details.
