Presence-aware AI agents powered by WiFi sensing. No cameras. No wearables. Just physics.
Your agents detect whether you're physically present using WiFi Channel State Information (CSI). When you leave, they queue non-urgent messages. When you return, they greet you with a digest of everything that happened while you were away.
| State | What Happens |
|---|---|
| Present | Agents operate normally |
| Away | Non-urgent messages are queued; urgent ones sent immediately |
| Returned | Agents deliver a welcome-back digest, then resume normal operation |
docker run -d -p 3000:3000 --name ruview ruvnet/wifi-densepose:latestVerify it's running:
curl -s http://localhost:3000/health/live
# {"status":"alive","uptime":4}Works in simulation mode out of the box — no WiFi hardware needed for testing. The
sourcefield in API responses will show"simulate"(synthetic data) vs"csi"(real hardware).
# From local clone
git clone https://github.com/DevvGwardo/openclaw-ruview-presence.git
openclaw plugins install ./openclaw-ruview-presence
# Or link for development
openclaw plugins install -l ./openclaw-ruview-presenceAdd to your openclaw.json:
cp -r skills/ruview-presence ~/.openclaw/skills/ruview-presenceThat's it. Your agents will start checking presence on their next heartbeat. You'll see this in the logs:
config change detected; evaluating reload (skills)
config change applied (dynamic reads: skills)
| Option | Type | Default | Description |
|---|---|---|---|
ruviewUrl |
string |
http://localhost:3000 |
RuView API base URL |
pollIntervalMs |
number |
10000 |
How often to poll RuView (ms) |
confidenceThreshold |
number |
0.3 |
Minimum detection confidence to count as "present" |
debounceCount |
number |
2 |
Consecutive empty readings before marking as "away" |
enableDigest |
boolean |
true |
Show a summary digest when the user returns |
enableZoneAwareness |
boolean |
false |
Track which room/zone the user is in |
All options can also be set via environment variables:
| Environment Variable | Maps To |
|---|---|
RUVIEW_API_URL |
ruviewUrl |
RUVIEW_API_KEY |
Auth token (if RuView auth is enabled) |
These are the actual responses from RuView that the plugin works with.
The primary endpoint used for presence detection.
{
"timestamp": 1773088911.824,
"source": "simulate",
"total_persons": 1,
"persons": [
{
"id": 1,
"confidence": 0.78,
"zone": "zone_1",
"bbox": { "x": 270.4, "y": 133.2, "width": 136.6, "height": 235.1 },
"keypoints": [
{ "name": "nose", "confidence": 0.59, "x": 336.5, "y": 151.8, "z": -0.22 },
{ "name": "left_eye", "confidence": 0.61, "x": 326.1, "y": 146.0, "z": -0.22 },
{ "name": "right_eye", "confidence": 0.66, "x": 346.1, "y": 143.2, "z": -0.22 },
{ "name": "left_shoulder", "confidence": 0.77, "x": 311.3, "y": 185.6, "z": -0.22 },
{ "name": "right_shoulder", "confidence": 0.74, "x": 360.6, "y": 186.9, "z": -0.22 }
]
}
]
}Each person includes 17 DensePose-compatible keypoints: nose, left/right eye, left/right ear, left/right shoulder, left/right elbow, left/right wrist, left/right hip, left/right knee, left/right ankle.
{
"zones": {
"zone_1": { "person_count": 1, "status": "monitored" },
"zone_2": { "person_count": 0, "status": "clear" },
"zone_3": { "person_count": 0, "status": "clear" },
"zone_4": { "person_count": 0, "status": "clear" }
}
}{
"vital_signs": {
"breathing_rate_bpm": 9.4,
"breathing_confidence": 0.83,
"heart_rate_bpm": 44.4,
"heartbeat_confidence": 0.67,
"signal_quality": 0.52
},
"source": "simulate",
"tick": 19612
}Returns everything above plus raw signal features (mean RSSI, spectral power, motion/breathing band power), per-sensor subcarrier amplitudes, RF tomography voxel grid, and classification.
{
"classification": {
"presence": true,
"motion_level": "present_still",
"confidence": 0.78
},
"features": {
"mean_rssi": -37.0,
"variance": 15.1,
"spectral_power": 249.0,
"dominant_freq_hz": 1.85,
"breathing_band_power": 16.4,
"motion_band_power": 13.7,
"change_points": 8
}
}{ "status": "alive", "uptime": 1961 }Hooks into OpenClaw's before_prompt_build lifecycle event to poll RuView and manage state transitions. When the user returns after being away, the plugin prepends a digest summary to the agent's context.
Types match the actual RuView API response format — RuViewPoseResponse, RuViewZoneSummary, RuViewVitalSigns, and RuViewSensingLatest are all typed to the real payloads.
Provides standing orders that agents follow during heartbeat cycles. Gives agents explicit instructions for presence-aware behavior — checking the API, interpreting results, and acting on state changes. Includes the actual JSON response format so agents can parse responses correctly.
The plugin exposes two methods on the OpenClaw gateway for programmatic access:
| Method | Returns |
|---|---|
ruview.presence |
{ state, zone, awaySince, queuedEvents, detectedPersons, source } |
ruview.queueEvent |
{ queued: true, total: <count> } |
RuView runs in simulation mode by default (source: "simulate" in responses). For real-world presence detection (source: "csi"):
| Option | Hardware | Cost | Capability |
|---|---|---|---|
| No hardware | Any computer | $0 | Simulation mode (synthetic data) |
| Basic | Any WiFi laptop | $0 | RSSI-only presence (coarse) |
| Recommended | 3-6x ESP32-S3 + router | ~$54 | Full CSI: pose, breathing, heartbeat, motion |
| Research | Intel 5300 / Atheros AR9580 | ~$50-100 | Full CSI with 3x3 MIMO |
- If RuView is unreachable, the plugin keeps the last known state and retries on the next heartbeat
- The debounce mechanism prevents false "away" triggers from momentary signal drops (requires 2 consecutive empty readings by default)
- Urgent messages are always delivered immediately, regardless of presence state
- The
sourcefield in RuView responses distinguishes simulation from real hardware data
| Problem | Solution |
|---|---|
| Agent says "rate limit reached" | Your model provider is rate-limited. Switch to a different model with openclaw models set <model> |
Agent tries to use read() tools |
The Chat Completions API doesn't support tool calls. Send the agent the data directly or use the Responses API |
| Port 3000 is already in use | Map to a different port: docker run -d -p 3002:3000 --name ruview ruvnet/wifi-densepose:latest and update ruviewUrl in config |
| Skill not detected | Check openclaw logs for config change detected; evaluating reload (skills) — if missing, restart the gateway |
| Always shows "present" | In simulation mode, RuView always returns a synthetic person. Use real CSI hardware for actual presence/absence detection |
openclaw-ruview-presence/
index.ts Plugin entry point (state machine, polling, digest)
openclaw.plugin.json Plugin manifest and config schema
package.json Package definition
docs/
hero-banner.png README hero banner
architecture.png System architecture diagram
statemachine.png State machine diagram
skills/
ruview-presence/
SKILL.md Agent standing orders with API response formats
HEARTBEAT.md Heartbeat trigger
MIT



{ // Plugin configuration (runtime logic) "plugins": { "entries": { "ruview-presence": { "enabled": true, "config": { "ruviewUrl": "http://localhost:3000", "confidenceThreshold": 0.3, "debounceCount": 2, "enableDigest": true } } } }, // Skill configuration (agent instructions) "skills": { "entries": { "ruview-presence": { "enabled": true, "env": { "RUVIEW_API_URL": "http://localhost:3000" } } } } }