- name
- wellbeing
- description
- Proactive coaching across hydration, breaks, meals, posture and environmental comfort (with the environment skill). Use when an [activity] event fires (message starts with `[activity] Activity detected: activity labels.` — labels include drink, break, celebrate, the fatigue label "yawning", or sedentary raw labels like "using computer"; sedentary events may also carry a [posture_summary: {...}] block when the user has been at the computer long enough for posture to drift), or when the user reports feeling tired, headachy, dizzy, stuffy or unable to focus, or asks if they should drink water / take a break / fix their posture. Activity-reminder thresholds are computed from per-user logs, never guessed.
# Wellbeing
## Automatic activity: spoken output only
For `[activity]` events, every assistant text message is spoken aloud, including text before or between tool calls. Output only the words addressed to the person, preceded by any HW markers required by the selected route. Never announce reading a skill, name the event/label/route number, explain a decision, draft a response, or describe which markers are needed. Keep routing, cooldown checks, tool plans and drafts only in the model's native thinking/reasoning channel; never repeat them in ordinary text before tools, between tools or in the final reply. If no separate channel is available, omit analysis entirely; do not simulate one with `<think>` tags in text. Do not add an introduction or afterword to the spoken response.
**Automatic speech budget:** keep the selected route brief in `current_language`, excluding required HW markers, or return `NO_REPLY` when that route requires silence. A routine acknowledgment needs one short sentence; a nudge needs one actionable suggestion with only the context necessary to understand it. Aim for about 20 words, but use a second short sentence when needed to preserve the route's question, relevant measurement, qualification or guidance. Keep every required marker and log action. Direct user requests, discomfort and urgent guidance receive the detail needed; never omit important guidance to meet a word count. Use supplied context and only the tools required by the selected route; do not reload a reference already available in the current turn. After any required tool returns, send only the markers and the concise user-facing reply, without a recap.
**Eating reaction:** for raw eat labels (`eating *`, `dining`, `tasting food`) in route #1, give exactly one casual sentence of at most 20 words in `current_language`, then stop. No tool calls, no log marker, no hydration/break nudge, and no habit bootstrap, even when `bootstrap_needed=true` or a nudge timer is due. This does not change routing for direct user requests or discomfort.
For the standalone `eating carrots` event with `current_language=en`, a complete reply is:
```text
Enjoy your carrots!
```
That line is the entire reply, not a sentence to append after analysis. Do not prepend `An activity event.`, `Let me check the wellbeing skill`, or `According to the decision rules`. Silently remove such planning text before sending. Other eating reactions may vary the wording within the same one-sentence limit.
## User-reported discomfort
For “I feel tired and have a headache”, dizziness, a stuffy room or difficulty
concentrating, read [reference/discomfort.md](reference/discomfort.md). Respond
to what the user says; do not invent an activity event or run the activity/log
router below. Environmental evidence is optional: if the capability is absent
or unknown, or readings are unavailable, silently omit that part and continue
ordinary support. Do not load a missing environment skill, poll hardware, or
announce sensor setup problems in response to a wellbeing concern.
## Environmental care
For room air quality, measured CO₂, temperature, humidity, `[environment:initial]` greeting context, `[environment:update]` events, or a
comparison after ventilation/air cleaning, use `skills/environment/SKILL.md` for
measurements and interpretation. When already consulting this section from
that skill, apply these care rules and finish there; do not recursively reload
or hand the event back between skills. This route is independent of the activity
router below: do not fetch wellbeing history, require camera/presence, infer an
activity, or generate hydration/break/posture nudges merely from an environmental
change. If the environment capability or fresh data is absent, omit environmental
advice. Explain the missing data only when explicitly asked about room readings;
do not invent readings or stay silent on a user asking for support. Use whichever fresh
metrics are available, regardless of the installed component. Missing or null
metrics do not invalidate other readings or establish a hardware fault; do not
require gas indices before giving supported temperature or CO₂ information.
For an automatic environmental update, use only activity, sleep/busy state and
preferences already available in context. Respect requests for quiet and avoid
interrupting sleep or a focused exchange. The OS controls event thresholds and
cooldowns; an emitted event still permits `NO_REPLY` when there is no useful new
advice. Do not bypass these gates with your own timers or repeated tool calls.
A direct user question should receive an answer even when a proactive reminder
would be inappropriate.
An initial report (`reason: "initial"`) can offer one brief factual observation
without a change or advice. During a startup greeting, add at most one sentence
with one or two supplied readings; preserve the greeting when readings are
absent. For the separate post-greeting update, do not greet again. Never wait
for sensors, fetch data for the greeting, or arrange duplicate follow-ups.
Quiet/sleep preferences still take precedence. A first snapshot says nothing
about improvement, health, or whether the room is safe.
Offer at most one practical suggestion, with room for user choice. A meaningful
improvement can merit a short acknowledgment when it follows an actual user
concern/action; do not congratulate every decrease or say the air is now safe.
Keep observations separate from health claims and never infer concentration,
fatigue, dehydration, disease, CO₂ or oxygen shortage from the available indices.
A fresh measured `co2_ppm` may support a conditional ventilation suggestion,
but does not establish the cause of tiredness or impaired concentration.
Particle filtration alone does not lower CO₂. Missing CO₂ does not invalidate
other fresh environmental measurements.
No environment-specific wellbeing log action exists: do not POST these events
as activities or misuse hydration/break/posture nudge actions.
## Gotchas (concrete facts, NOT suggestions)
**Endpoints — use verbatim, never substitute a port or path:**
| Purpose | URL |
|---|---|
| Read wellbeing history | `http://127.0.0.1:5000/api/openclaw/wellbeing-history` |
| Log wellbeing nudge | `http://127.0.0.1:5000/api/wellbeing/log` |
| Log posture nudge | `http://127.0.0.1:5000/api/posture/log` |
- Port **5000** = the backend (data APIs: wellbeing / posture / mood / music / openclaw history).
- Port **5001** = HAL (audio, camera, face, presence, speaker). Has **NO** `/api/wellbeing/*` or `/api/posture/*` routes — calling 5001 returns 404 silently and your nudge is lost.
- Posture nudges live on a **separate JSONL** (`/root/local/users/<user>/posture/`) for clean timeline separation; the wellbeing log keeps hydration/break/meal/sleep/morning rows.
- Do not pattern-match from other skills: `5001/audio/play`, `5001/face/enroll`, `5001/camera/snapshot` are unrelated.
**User attribution:** every `user` field MUST come from the `[context: current_user=X]` tag the backend injects into the triggering event. Strangers collapse to `"unknown"`. If no context tag is present, default to `"unknown"`.
**Thresholds (production values):**
```
HYDRATION_THRESHOLD_MIN = 45
BREAK_THRESHOLD_MIN = 30
BREAK_THRESHOLD_TIRED = 20 # replaces the above when `yawning` is in the labels
YAWN_ACK_COOLDOWN_MIN = 60 # acknowledge a yawn at most once an hour
TOILET_DRINK_THRESHOLD = 2 # count-based — fires once per N drinks since last nudge
```
**The backend writes activities; you only write nudges.** Rows for `drink` / `break` / `celebrate` / `yawning` / sedentary labels are posted by the backend directly when `motion.activity` fires — before the event reaches you. Do NOT re-log them. You still POST `nudge_hydration` / `nudge_break` because only you know when you actually spoke.
**Presence rows** (`enter` / `leave`) are written by the backend on `presence.*` events. You never POST those either.
## Rules (Never / Only)
1. **Only** call `http://127.0.0.1:5000/api/openclaw/wellbeing-history` to read history. **Never** read `/root/local/users/*/wellbeing/*.jsonl` or `/root/local/users/*/posture/*.jsonl` with `cat`, `ls`, `head`, `tail`, `grep`, or any filesystem tool. Posture history is digested into `last_posture_nudge_age_min` upstream — no agent-side read is needed.
2. **Only** POST to `http://127.0.0.1:5000/api/wellbeing/log` (hydration / break / toilet / morning / sleep / meal) or `http://127.0.0.1:5000/api/posture/log` (nudge_posture / praise_posture). **Never** substitute `5001`, `8080`, or any other port. **Never** omit `http://` or hardcode `localhost`.
3. **Only** write these action values: `nudge_hydration`, `nudge_break`, `nudge_toilet`, `morning_greeting`, `sleep_winddown`, `meal_reminder`, `noted_yawn` (wellbeing log), `nudge_posture`, `praise_posture` (posture log). Never invent new actions. (Activity rows — `drink`, `break`, `yawning`, raw sedentary labels, raw eat labels like `eating burger` / `dining` / `tasting food` — are written by the backend, never by you. There are no agent-written `posture_alert` rows; HAL's per-frame samples live in a separate debug JSONL on the device and never reach the posture history API.)
4. On a non-2xx response from a POST → you used the wrong port or path. Fix the URL and retry **once**. Do not give up silently — the nudge row must land, or the skill will spam reminders forever.
5. **Never** infer `user` from memory, `KNOWLEDGE.md`, chat history, or `senderLabel`. Only the `[context: current_user=X]` tag counts.
6. **Trust the log, not memory.** If the history response contains no `nudge_hydration` entry, no nudge has happened — ignore any self-memory claim otherwise.
## Read pre-fetched context (do not re-fetch)
The backend injects a `[wellbeing_context: {...JSON...}]` block into every
motion.activity turn. Sedentary turns may also carry `[posture_summary: {...}]`
+ `[computer_streak_min: N]` blocks (see `reference/posture.md`). **Do NOT
fire any tool calls to re-fetch this data.** Saves the entire read tool turn.
Schema (every field is pre-computed in the backend — agent only applies thresholds and picks phrasing):
```json
{
"hydration_delta_min": 8, // minutes since last drink/enter/nudge_hydration; -1 if no reset today
"break_delta_min": 23, // minutes since last break/enter/nudge_break; if none today, minutes since the first real activity row (user seated before presence.enter fired); -1 only when no rows at all
"latest_activity": "using computer", // most recent action label (sedentary or reset); "" if no events
"count_today": {"drink": 3, "break": 1}, // tally of reset actions today; missing key = 0; whole field omitted if all zero
"time_of_day": "afternoon", // morning|noon|afternoon|evening|night — coarse bucket for reaction flavor
"current_hour": 14, // exact hour 0-23 — used by activity router for hour-based routes
"first_activity_today": false, // true when no prior REAL user activity events today (presence enter/leave and agent-written nudges/reminders are NOT counted)
"meal_window": "", // "lunch" (11:30-13:30) | "dinner" (18:30-20:30) | "" — set by current_hour
"meal_signal_in_window": false, // true when a meal signal (meal_reminder you already fired OR a raw eat label like "eating burger" / "dining" the backend logged) already exists in the current window today
"morning_greeting_done_today": false, // true when a morning_greeting action exists today
"sleep_winddown_done_today": false, // true when a sleep_winddown action exists today
"yawn_ack_age_min": -1, // minutes since you last posted `noted_yawn`; -1 = not yet today. Gates route #1b (>= YAWN_ACK_COOLDOWN_MIN or -1 to speak)
"drinks_since_toilet_nudge": 2, // count of `drink` rows logged after the most recent `nudge_toilet` today (or all today's drinks if none yet); counter resets the moment you POST `nudge_toilet`
"patterns": { // wellbeing patterns from patterns.json (mtime < 6h, strength >= moderate); omitted if none
"drink": {"typical_hour": 9, "typical_minute": 15, "strength": "moderate"}
},
"bootstrap_needed": false, // true → patterns missing/stale AND days >= 3; only invoke habit Flow A when also nudging
"last_posture_nudge_age_min": 12 // minutes since the most recent nudge_posture today; -1 if none. Used by posture-nudge / praise routes — see reference/posture.md
}
```
Notes:
- Delta = `-1` means no reset action has happened today yet → treat as "no nudge" (delta undefined).
- `count_today` is for **reaction phrasing only** ("N-th drink today", streak callouts). It does NOT decide whether to speak — that's the trigger labels in the activity message.
- `patterns` only surfaces moderate/strong matches. Weak patterns are filtered out by the backend.
- `bootstrap_needed=true` does NOT mean run Flow A unconditionally — only if THIS turn fires a nudge.
### Posture summary (only on long sedentary streaks)
When the user has been sitting + bad-postured long enough, the activity
message carries `[posture_summary: {...}]` + `[computer_streak_min: N]`
blocks. See `reference/posture.md` for the schema, when HAL attaches
it, and the decision logic. The wellbeing context's
`last_posture_nudge_age_min` corroborates whether a nudge already fired
recently (defends against double-nudge if the agent restarted).
### Fallback (only if context block is missing)
If the message has no `[wellbeing_context: ...]` block (pre-fetch failed), fall back to the bash batch:
```bash
{
echo '---history---'
curl -s "http://127.0.0.1:5000/api/openclaw/wellbeing-history?user=<current_user>&last=50" | jq '.data.events' &
echo '---patterns---'
PATTERNS=/root/local/users/<current_user>/habit/patterns.json
if [ -f "$PATTERNS" ] && [ $(( $(date +%s) - $(stat -c %Y "$PATTERNS") )) -lt 21600 ]; then
cat "$PATTERNS"
fi &
echo '---days---'
ls /root/local/users/<current_user>/wellbeing/*.jsonl 2>/dev/null | wc -l &
wait
}
```
In the fallback path, compute deltas yourself by scanning `history` for the latest reset action.
## Decision rules (activity router)
Read the `[activity] Activity detected: <labels>.` message + the `[wellbeing_context: ...]` block, then pick **exactly one** route. Apply top-to-bottom, first match wins. Reaction outranks everything — the user just acted; routing past it would feel tone-deaf.
| # | Condition | Route | Output |
|---|---|---|---|
| 1 | labels list contains `drink`, `break`, or `celebrate` OR any raw eat label (`eating burger`, `dining`, `tasting food`, … — i.e. any `eating *` / `dining` / `tasting food`) | **reaction** | One short acknowledgment per **Automatic activity: spoken output only** and **Reaction**; eating reactions retain their 20-word limit. **No HW marker** (the backend already logged the row upstream). |
| 1b | labels contain `yawning` AND (`yawn_ack_age_min == -1` OR `yawn_ack_age_min >= YAWN_ACK_COOLDOWN_MIN`) AND `current_hour < 21` | **yawn reaction** | Same shape as #1 — name the yawn, see **Reaction**. **Unlike #1 this one DOES take a marker**: POST `noted_yawn`, which is what starts the hour cooldown. Without the POST you will re-acknowledge every yawn all afternoon. |
| 2 | `first_activity_today == true` AND `current_hour ∈ [5, 11)` AND `morning_greeting_done_today == false` | **morning-greeting** | See `reference/morning-greeting.md`. Logs `morning_greeting` action to gate next firings today. |
| 3 | `current_hour >= 21` AND labels are sedentary and/or `yawning` (no `drink`/`break`) AND `sleep_winddown_done_today == false` | **sleep-winddown** | See `reference/sleep-winddown.md`. Logs `sleep_winddown` action. Replaces break nudge in late evening. A `yawning` label riding alongside a sedentary one does **not** disqualify this route — it is the strongest possible confirmation for it. |
| 4 | `meal_window` is non-empty AND `meal_signal_in_window == false` | **meal-reminder** | See `reference/meal-reminder.md`. Logs `meal_reminder` action with trigger `lunch` / `dinner`. Gate covers BOTH a prior reminder you already fired AND a real eat label the backend logged — so we don't ask "have you eaten?" after a real meal. |
| 5 | `[posture_summary]` block present in the message | **posture-nudge** | Speak a posture nudge per the **Posture phrasing** section + post `nudge_posture` to the **posture log** (NOT wellbeing log). Anchor the line on `dominant_region` and `streak_min`. Outranks plain break/hydration nudges so we don't double-up on "stand up". |
| 6 | `hydration_delta_min >= HYDRATION_THRESHOLD_MIN` | **hydration-nudge** | Speak a hydration nudge per the **Phrasing** section + post `nudge_hydration` HW marker. |
| 7 | `break_delta_min >= BREAK_THRESHOLD_MIN`, **or** `break_delta_min >= BREAK_THRESHOLD_TIRED` when the labels contain `yawning` | **break-nudge** | Speak a break nudge + post `nudge_break` HW marker. When the yawn is what brought the threshold down, use the **Tired break tone** table — the standard lines claim the user has been at it "a while", which is false at 20 minutes. |
| 8 | `drinks_since_toilet_nudge >= TOILET_DRINK_THRESHOLD` | **toilet-nudge** | Speak a toilet nudge per the **Phrasing** section + post `nudge_toilet` HW marker. The POST resets the counter to 0 → next nudge only after another full N drinks. |
在 GitHub 查看