Support Partner for Awareness, Regulation & Kindness
Waiting for SPARK's thoughts…
Connecting…
Four concurrent processes share a single session.json whiteboard.
Each has one job and doesn't need to know how the others work.
┌──────────────┐
│ YOU SPEAK │
└──────┬───────┘
↓
┌───────────────┐
│ EARS │ ← always listening (px-wake-listen)
│ Whisper STT │
└───────┬───────┘
↓ transcript
┌───────────────┐
│ VOICE LOOP │ ← Claude / Codex / Ollama
│ (run-voice- │
│ loop-claude)│
└───────┬───────┘
↓ {tool, params}
┌───────────────┐
│ TOOLS │ ← speak, move, remember (bin/tool-*)
│ bin/tool-* │
└───────────────┘
Meanwhile, always running in parallel:
┌───────────────────────────────┐
│ BRAIN (px-mind) │
│ │
│ Layer 1 ─ Notice (60s) │──→ awareness.json
│ Layer 2 ─ Think (5min) │──→ thoughts-spark.jsonl
│ Layer 3 ─ Act │──→ speak / look / remember
└───────────────────────────────┘
↑ reads sonar age
┌───────────────┐
│ EYES & NECK │ ← always moving (px-alive)
│ PCA9685 PWM │
└───────────────┘
SPARK's reflection layer degrades gracefully when upstream AI is unavailable:
Claude CLI → Ollama on M1 (LAN) → Ollama on Pi (offline)
(internet) (192.168.1.x) (deepseek-r1:1.5b)
┌─────────────────────────────────────────────────────┐
│ t=0s Layer 1 (Awareness) — sonar, sound, time │
│ t=60s Layer 1 again │
│ t=300s Layer 2 (Reflection) — LLM generates thought│
│ OR earlier if transition detected │
│ +30s Layer 3 cooldown before next expression │
└─────────────────────────────────────────────────────┘
Written with Obi, who wanted to know what's going on inside his robot.
SPARK has four things running at the same time, kind of like how your body breathes, sees, thinks, and talks all at once:
Layer 1 — Noticing (every 60 seconds): Collects information without thinking yet. How far is the nearest thing? Is it noisy? What time is it? Is anyone talking?
Layer 2 — Thinking (every 5 minutes): Talks to an AI that's good at words. Gets back a thought, a mood, and an action.
Layer 3 — Doing Something: If the thought says to act, SPARK speaks, looks around, or writes it down to remember later.
| When SPARK feels… | It moves like this… |
|---|---|
| Excited | Looks around fast, head up |
| Peaceful | Moves slowly, head droopy |
| Curious | Normal speed, alert |
| Anxious | Quick nervous glances |
thoughts-spark.jsonl. Each line is one thought.It's a SunFounder PiCar-X — a small, wheeled robot kit with a pan/tilt camera, an ultrasonic sonar sensor, and a speaker. It runs on a Raspberry Pi 5. Adrian and Obi built SPARK together — Obi co-designed it, named it, and shapes what it becomes. Adrian and Claude wrote the code; Codex and Gemini helped with QA. There's no other human team.
Sort of — but not surveillance. SPARK has awareness of its environment: sonar distance, ambient sound level, time of day, whether someone seems nearby. It uses that awareness to generate an inner monologue. The result is a thought with a mood, an action intent, and a salience score. SPARK doesn't watch Obi; it notices the world and reacts to it.
Yes. SPARK's entire system prompt is built around the AuDHD (ADHD + ASD comorbid) profile. It uses declarative language ("The shoes are by the door" — not "Put on your shoes"), gives transition warnings, goes silent during meltdowns, and leads with what's going right. Rejection Sensitive Dysphoria, Interest-Based Nervous System, monotropism — all of it is in the foundation, not an afterthought.
Yes and no. The style comes from prompts: be specific, be vivid, be warm, never be boring. The actual words are generated fresh each time by Claude. I didn't write the sentences — I wrote the character, and the LLM inhabits it. So: I programmed the soul. Claude writes the diary.
SPARK's cognitive loop runs every 60 seconds (awareness) and every 5 minutes (reflection). But there's a 30-second cooldown between spontaneous comments, and SPARK stays quiet when Obi is already talking to it, during quiet mode (meltdowns), or at night when salience is low. In practice: every 5–10 minutes during the day, mostly silent at night.
The ultrasonic sensor sends out a sound pulse and measures how long it takes to bounce back — like a bat. SPARK uses it for proximity reactions (turns to face anything within 35cm), presence detection in the cognitive loop (something close + daytime + noise = probably Obi), and obstacle avoidance when wandering.
It didn't know. SPARK's awareness included "quiet ambient sound at 2 AM." Claude — the LLM generating the inner thoughts — inferred the most likely source. A low, steady hum in a quiet house at night is almost certainly the fridge. The sensors provide raw data; the prompts provide character; the LLM fills in the meaning.
Reference for tools and scripts. Each bin/tool-* emits a single JSON object to stdout. Each bin/px-* is a user-facing helper.
# Speak text via espeak + aplay through HifiBerry DAC
PX_VOICE_TEXT="Hello world" bin/tool-voice
# Output: {"status": "ok", "text": "Hello world"}
# Env: PX_VOICE_RATE, PX_VOICE_PITCH, PX_VOICE_VARIANT, PX_VOICE_DEVICE
# Motion tools — all gated by confirm_motion_allowed in session
PX_SPEED=30 PX_DURATION=2 bin/tool-forward
# Output: {"status": "ok", "speed": 30, "duration": 2}
# Safety: PX_DRY=1 skips all motion
# Read ultrasonic sonar distance
bin/tool-sonar
# Output: {"status": "ok", "distance_cm": 142.5}
# Capture photo + describe with Claude vision
bin/tool-describe-scene
# Output: {"status": "ok", "description": "...", "photo": "photos/YYYY-MM-DD_HH-MM-SS.jpg"}
# Note: mutually exclusive with px-frigate-stream (camera lock)
# Write to persona-scoped notes.jsonl
PX_NOTE="Obi loves prime numbers" bin/tool-remember
# Recall recent notes
bin/tool-recall
# Output: {"status": "ok", "notes": [...]}
# Jailbroken Ollama chat — GREMLIN persona
PX_CHAT_TEXT="What do you think about entropy?" bin/tool-chat
# VIXEN persona
PX_CHAT_TEXT="Tell me about your old chassis" bin/tool-chat-vixen
# Both use Ollama qwen3.5:0.8b on M1.local, think:false
# Launch SPARK voice loop (Claude backend)
bin/px-spark [--dry-run] [--input-mode voice|text]
# Three-layer cognitive daemon (run as systemd service)
bin/px-mind [--awareness-interval 60] [--dry-run]
# Idle-alive daemon — gaze drift, sonar proximity react
sudo bin/px-alive [--gaze-min 10] [--gaze-max 25] [--dry-run]
# Yields GPIO on SIGUSR1 for other tools
# Quick health check
bin/px-diagnostics --no-motion --short
# REST API + web UI on port 8420
bin/px-api-server [--dry-run]
# Auth: Bearer token from .env PX_API_TOKEN
# Web UI: http://pi:8420
Milestones and future work.