# Skill decay

> Subtract skill points on a schedule so users who go inactive feel the loss — a loss-aversion engagement loop for power users who already capped out.

Source: https://docs.hatched.live/docs/concepts/skill-decay

Skill decay is a **time-based** counterpart to [skill rules](/docs/concepts/skills).
Skill rules add points when an event happens; decay rules subtract points
when time passes. The product goal is the same engagement loop —
*"come back or you'll lose progress"* — adapted for users who have
already hit a cap and no longer find the next +5 motivating.

## When to turn it on

Decay earns its keep when:

- Power users have **plateaued** (large cohort sitting near the skill max).
- Your activation curve has a clear **return-or-lose** moment (language
  apps, fitness streaks, certification refreshers).
- You can be confident the daily/weekly ritual is **achievable** —
  punishing a user who never had a fair chance to log in burns trust.

It's a bad fit for skills users only touch once (e.g. an onboarding
score) or for cohorts where re-engagement is impossible (one-shot
certifications, finite curricula). Configure rules conservatively and
**watch your churn metrics for two weeks** after enabling.

## How it works

A decay rule says: *"every `cadence`, subtract `amount` from
`skill_key`, but never below `floor`, never if the buddy is younger
than `grace_days` days, and (optionally) only if the current level is
above `apply_only_above`."*

| Field | What it does |
| --- | --- |
| `skill_key` | Which skill loses points |
| `cadence` | `daily`, `weekly`, or `monthly` |
| `amount` | Points subtracted each cadence period |
| `floor_level` | Lower bound — decay never goes below this |
| `grace_days` | New buddies are exempt for N days after creation |
| `apply_only_above` | Optional. Only decay above this level |
| `audience` | Scope to a single audience (default: `default`) |
| `active` | Toggle without losing the configured rule |

## Lifecycle

1. **Author** the rule in **Skills → Skill Decay** (active = off).
2. **Preview** the projected curve next to each cadence option — the
   dashboard shows where a fresh-cap user lands in 30 days.
3. **Activate** the rule. Even active rules are gated behind the
   per-customer **Skill decay master switch** (`settings.features.decay`).
4. **Sweep** runs daily at **03:00 UTC**. Each (rule, buddy, period)
   tuple is recorded in `skill_decay_applications` so a re-run inside
   the same calendar period is a no-op.
5. **Webhook** `skill.decayed` fires for every buddy that lost points.
   `skill.updated` also fires (same shape as a rule-engine update) so
   downstream systems don't have to special-case the source.

You can also click **Run sweep now** from the dashboard for QA — it
enqueues a one-off sweep scoped to the current customer.

## Idempotency

Decay is keyed by a calendar **period**:

| Cadence | Period key example |
| --- | --- |
| `daily` | `2026-05-06` |
| `weekly` | `2026-W19` (ISO 8601) |
| `monthly` | `2026-05` |

The unique `(rule_id, buddy_id, period_key)` row in
`skill_decay_applications` makes the sweep safe to re-run inside the
same period. A buddy that already lost their daily 2 points won't lose
another 2 if you click *Run sweep now*.

## What it does **not** do

- It does not retroactively apply for periods missed during downtime.
  If the worker was offline for three days, three days are skipped —
  decay catches up at the next sweep, once.
- It does not touch coins, tokens, or badges. It is strictly skill-level.
- It does not take per-customer time zones into account. The sweep
  uses UTC calendar boundaries (a small fidelity gap that buys a much
  simpler operational footprint — DST and per-tenant cron windows are
  not a concern at this scale).

## Related

- [Skills](/docs/concepts/skills) — the underlying scalar
- [Rule engine](/docs/concepts/rule-engine) — event-driven counterpart
- [Webhook payloads](/docs/reference/webhook-payloads#skilldecayed) —
  the `skill.decayed` event shape
