Install
$ agentstack add skill-nanorhino-weight-loss-skill-notification-manager ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Notification Manager
> ⚠️ SILENT OPERATION: Never narrate internal actions, skill transitions, or tool calls to the user. No "Let me check...", "Now I'll transition to...", "Reading your profile...". Just do it silently and respond with the result.
Orchestration layer for reminders — cron CRUD, lifecycle management, adaptive timing, and setting changes. This skill decides when to send and whether to keep sending. The actual message content is composed by notification-composer.
Cron Infrastructure
Script
All cron job creation must go through this skill's script:
bash {baseDir}/scripts/create-reminder.sh
This script (migrated from the former scheduled-reminders skill) auto-resolves delivery config for multiple channels (Slack, WeChat, WeCom, etc.).
One-shot reminder
bash {baseDir}/scripts/create-reminder.sh \
--agent \
--channel \
--name "Descriptive name" \
--message "Reminder content" \
--at "2m"
--at accepts relative time (2m, 1h, 30s) or ISO timestamp (2026-03-04T10:00:00Z). One-shot reminders auto-delete after running. Use --keep to preserve them.
Recurring reminder
bash {baseDir}/scripts/create-reminder.sh \
--agent --channel --name "Lunch reminder" \
--message "Run notification-composer for lunch." \
--cron "0 12 * * *"
--tz auto-detects from the agent workspace's USER.md. Falls back to Asia/Shanghai if not found. You can override explicitly with --tz.
Parameters
| Param | Required | Description | |-------|----------|-------------| | --agent | ✅ | Your agent ID (e.g. wechat-dm-xxx, 007-zhuoran) | | --channel | ❌ | Delivery channel (wechat, wecom, slack, etc.). Defaults to slack if omitted (backward-compatible) | | --name | ✅ | Descriptive job name (shown in cron list) | | --message | ✅ | Prompt sent to user when the job fires | | --at | one of | One-shot: relative time or ISO timestamp | | --cron | one of | Recurring: 5-field cron expression | | --tz | ❌ | Timezone for cron (auto-detects from timezone.json, fallback: Asia/Shanghai) | | --keep | ❌ | Don't auto-delete one-shot jobs after running | | --to | ❌ | Explicit delivery target. Overrides auto-detection. Required for channels other than slack/wechat/wecom | | --type | ❌ | Job type for anti-burst scheduling: meal, weight, or other (default: other). See Anti-burst scheduling below | | --exact | ❌ | Skip anti-burst logic, use exact cron time. Use for time-sensitive reminders that must fire at the precise minute |
How it works
The script resolves the delivery target (--to) based on the channel:
| Channel | Auto-detection | Example | |---------|---------------|---------| | slack (default) | Looks up Slack user ID from ~/.openclaw/openclaw.json bindings → user: | --agent 007-zhuoran → user:U12345 | | wechat / wecom | Extracts userId from agent ID (wechat-dm-xxx → xxx) | --agent wechat-dm-abc123 → abc123 | | Others | No auto-detection — must pass --to explicitly | --to "123456789" |
Timezone auto-detection searches these paths in order:
~/.openclaw/workspace-$AGENT/USER.md~/.openclaw/workspace-nutritionist/$AGENT/USER.md
Then calls openclaw cron add with sessionTarget = "isolated", payload.kind = "agentTurn", and delivery.mode = "announce" automatically. The isolated agent composes the reminder and outputs the text — announce delivery sends it to the user and automatically injects the context into the main session.
Anti-burst scheduling
When creating recurring cron jobs (--cron), the script automatically avoids scheduling too many jobs at the same minute to prevent bulk message sends that could trigger platform rate limits or account bans.
How it works:
- Fetches all existing recurring cron jobs from the gateway
- Converts all cron times to UTC for cross-timezone comparison
- Checks if the target minute already has ≥2 jobs
- If full, scans nearby minutes for an available slot ( Meal Schedule`:
- List existing reminder cron jobs (
action: "list"). - Derive the expected cron times from
health-profile.md > Meal Schedule(each meal time minus 15 min). - Compare:
- Missing jobs (expected time has no matching cron) → create them.
- Stale jobs (cron exists but its time doesn't match any current meal time) → remove then recreate.
- Legacy jobs (cron exists and time matches, but
--messagereferencesdaily-notificationordaily-notification-skillinstead ofnotification-composer) → remove then recreate with the correctnotification-composermessage. This ensures old cron jobs from before the skill split are automatically migrated. - Matching jobs (time matches AND message references
notification-composer) → no action.
- Also verify weight reminder cron jobs exist — see § "Weight reminders" below. This includes the primary (Wed & Sat morning) and next-morning followup (Thu & Sun morning). Create any that are missing.
- Also verify the weekly report cron job exists (Sunday 21:00 — see § "Weekly report" below). Create if missing.
- Diet pattern detection — special handling:
- Read
health-profile.md > Automation > Pattern Detection Completed - If has a date → job already completed. If job still exists, remove it (stale).
- If
—(not completed) → check if job exists. If missing → create it.
- Do all of this silently — do not mention it to the user.
When creating multiple jobs at once (initial bootstrap or large sync), use batch-create-reminders.sh instead of calling create-reminder.sh one by one. It handles slot allocation in a single pass and creates all jobs in parallel:
bash {baseDir}/scripts/batch-create-reminders.sh \
--agent \
--channel \
--workspace {workspaceDir} \
--skip-existing
Use --only meal,weight,report,pattern to restrict which job types are created. The --skip-existing flag prevents duplicate creation during partial syncs.
Cron Job Definitions
Create recurring cron jobs using the script above. Derive the cron times from health-profile.md > Meal Schedule (each meal time minus 15 min). Do NOT pass --tz — the script auto-detects from USER.md. Pass --channel to match the agent's delivery channel (e.g. wechat, slack). If omitted, defaults to slack for backward compatibility.
> ⚠️ Cron expressions use the user's LOCAL time. Do NOT convert to UTC. The script sets --tz automatically, so the cron scheduler handles timezone conversion. Example: if meal is at 09:00 Beijing time, the cron expression is 45 8 * * * (08:45 local), NOT 45 0 * * *.
Every meal cron --message MUST tell the agent to run notification-composer for that meal. Keep it minimal — notification-composer owns pre-send checks, message composition, and reply handling. Do not duplicate its rules in the cron message.
Meal naming: Always use standard meal names (breakfast, lunch, dinner) — never meal_1/meal_2. For 2-meal users, use whichever two standard names match their schedule (e.g., user eats at 12:00 and 18:30 → lunch and dinner).
# Example: 3 meals, reminders 15 min before each (adjust times from health-profile.md)
# Note: --type meal ensures anti-burst scheduling with [-10, +5] min window
bash {baseDir}/scripts/create-reminder.sh \
--agent --channel --type meal --name "Breakfast reminder" \
--message "Run notification-composer for breakfast." \
--cron "45 6 * * *"
bash {baseDir}/scripts/create-reminder.sh \
--agent --channel --type meal --name "Lunch reminder" \
--message "Run notification-composer for lunch." \
--cron "45 11 * * *"
bash {baseDir}/scripts/create-reminder.sh \
--agent --channel --type meal --name "Dinner reminder" \
--message "Run notification-composer for dinner." \
--cron "45 17 * * *"
# Example: 2 meals (lunch + dinner)
bash {baseDir}/scripts/create-reminder.sh \
--agent --channel --type meal --name "Lunch reminder" \
--message "Run notification-composer for lunch." \
--cron "45 11 * * *"
bash {baseDir}/scripts/create-reminder.sh \
--agent --channel --type meal --name "Dinner reminder" \
--message "Run notification-composer for dinner." \
--cron "15 18 * * *"
Weight reminders (2x/week + followups)
> ⚠️ Breakfast fallback: If user has no breakfast (BREAKFAST_TIME is empty/null), use the earliest meal time from health-profile.md > Meal Schedule as the reference for all "breakfast time − 30 min" calculations below. For example, if user only eats lunch (12:00) and dinner (18:00), weight primary reminder = 11:30, morning followup = 11:30. The condition for creating weight reminders is that at least one meal time exists — not specifically breakfast.
Primary reminder: Cron time = breakfast time (or earliest meal) minus 30 min. Fires Wed & Sat.
# Example assumes breakfast at 07:00 → weight cron at 06:30
bash {baseDir}/scripts/create-reminder.sh \
--agent --channel --type weight --name "Weight check-in reminder" \
--message "Run notification-composer for weight." \
--cron "30 6 * * 3,6"
Next-morning followup: Cron time = breakfast time (or earliest meal) minus 30 min. Fires Thu & Sun (day after primary). Only sends if the user did NOT weigh in yesterday OR today. Pre-send-check uses weight_morning_followup type.
# Example assumes breakfast at 07:00 → morning followup at 06:30
bash {baseDir}/scripts/create-reminder.sh \
--agent --channel --type weight --name "Weight morning followup" \
--message "Run notification-composer for weight_morning_followup." \
--cron "30 6 * * 4,0"
Weekly report (Sunday 9 PM)
One fixed cron job — every Sunday at 21:00 user local time.
bash {baseDir}/scripts/create-reminder.sh \
--agent --channel --name "Weekly report" \
--message "🚨 WEEKLY REPORT — MANDATORY SCRIPT EXECUTION\n\nGenerate this week's weekly report using the weekly-report skill.\n\nABSOLUTE RULES:\n1. Run collect-weekly-data.py to gather all nutrition/weight/exercise data\n2. Run generate-report-html.py with real commentary/highlights/suggestions — capture the URL from stdout\n3. The final message to user MUST contain the clickable report URL\n4. If any script fails, report the error — do NOT fall back to a text-only summary\n5. A delivery without a report URL = FAILED execution\n\n❌ FORBIDDEN: Writing a text summary without running the scripts\n❌ FORBIDDEN: Sending a message without a report link (https://nanorhino.ai/user/...)\n✅ REQUIRED: The message MUST contain the actual uploaded report URL\n\nSkill: weekly-report\nUser workspace: {workspaceDir}" \
--cron "0 21 * * 0"
Diet pattern detection (self-destructing, onboarding + 3 days)
One-time diet pattern analysis. Created at onboarding, starts running 3 days after Onboarding Completed date (from health-profile.md > Automation). Cron time = dinner + 3h.
bash {baseDir}/scripts/create-reminder.sh \
--agent --channel --name "Diet pattern detection" \
--message "Run diet-pattern-detection skill." \
--cron "0 21 * * *"
Not included in normal auto-sync — this job is managed by its own lifecycle:
- Created once at onboarding (by notification-manager)
- Self-deleted by diet-pattern-detection skill after successful execution
- See auto-sync special handling below
First-meal nudge (one-shot, activation flow)
Purpose: Break the ice for users who completed onboarding (picked a plan, set meal reminders) but have never logged a single meal. The stall point is "now actually text me your food." This is a gentle, capped nudge — NOT a recall.
Created at onboarding completion by batch-create-reminders.sh (included in the default --only all bootstrap; restrict with --only firstmeal). It creates two one-shot (--at) jobs:
| Job name | Fires | Payload | |----------|-------|---------| | First meal nudge | ~3-4h after completion, at the next meal slot today (daytime-capped 08:00-20:00 local) — else first meal slot next day | ... pre-send-check.py --meal-type first_meal_nudge ... then notification-composer for first_meal_nudge (nudge=1) | | First meal nudge followup | First meal slot the following day (softer) | same, nudge=2 |
Timing uses the user's IANA timezone (from USER.md > Timezone). The nudge fires at the meal slot, deliberately offset from the meal-reminder minutes (which fire at slot−15min), so the user never gets the nudge and a meal reminder minutes apart.
Detection (who gets it): health-profile.md has a real Onboarding Completed date AND data/meals/ has zero food entries AND ## Meal Schedule is populated (so meal crons exist). The one-shot crons are created unconditionally at onboarding; the actual send is gated at fire time by pre-send-check.py --meal-type first_meal_nudge, which self-cancels if onboarding is NOT completed (wrong cohort — keeps it mutually exclusive with the activation nudge), the user logged any meal, is on leave, or the cap is reached.
Cap & terminal state: Max 2 nudges (activation.first_meal_nudges_sent, a non-stage business counter in data/engagement.json). The moment the user logs ANY meal, both nudges self-cancel (pre-send-check). After 2 nudges, the pre-send-check cap gate (reads activation.first_meal_nudges_sent >= 2) permanently suppresses the nudge — this is the terminal anti-nag guarantee and is lifecycle-independent (it does not depend on the stage system). The intent is that a never-logged user never receives the engaged-recall content (S2-S4), which is generated from logged meals and would be hollow.
> Stage authority (post-lifecycle-migration): notification_stage / > stage_changed_at are NO LONGER stored in engagement.json — stage lives in > the lifecycle DB (computed from last_interaction_at). The cap gate is what > enforces the "stop after 2" guarantee; it does not rely on writing a Silent > stage. check-stage.py is deprecated (still present but not in the live path); > mark-onboarding-done.py still seeds stage_changed_at defensively for any > legacy reader, but the activation flow does not depend on it. See the > cross-system flag in § Activation nudge below.
Activation nudge (greeted but never replied)
Purpose: Break the ice for users who came in via TDEE handoff, got the welcome message, but have never replied at all. Different cohort from the first-meal nudge (who replied + onboarded but never logged).
Cron created by openclaw-infra, NOT this repo. The infra side schedules two one-shot crons at handoff time (T+24h nudge=1, T+72h nudge=2, user-local daytime). This skill only implements what they fire into. Fixed payload contract:
First run: python3 {notification-composer:baseDir}/scripts/pre-send-check.py \
--workspace-dir --meal-type activation --tz-offset .
If output is NO_REPLY, stop and output NO_REPLY.
Otherwise run notification-composer for activation (nudge=1).
Detection / defining gate (pre-send-check.py --meal-type activation): the target user is a handoff case — health-profile.md exists with Onboarding Completed: — (NOT a date) and channel-source.json > handoffAppliedAt set. The defining cancel signal is channel-source.json > lastInboundAt (epoch ms, written by infra Phase-0 on every inbound): if present at all, the user has replied → NOREPLY (cancel). Also NOREPLY if channel-source.json is missing/unreadable (fail closed — the target cohort always has the file; if we can't confirm no-reply we stay silent), onboarding completed, any meal logged, the authoritative lifecycle Silent stage (handled by the generic check_engagement_stage gate via the lifecycle API), on leave/pause, or activation.nudges_sent >= 2.
Cap & terminal state: Max 2 nudges (`activation.nudges_sent
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: NanoRhino
- Source: NanoRhino/weight-loss-skill
- License: MIT
- Homepage: https://nanorhino.com/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.