blog / macos
Reclaim RAM on macOS by disabling launchd services — the measurement harness, with real before numbers from an 8 GB M1
Every debloat thread on the internet makes the same claim — "disable background services, get your RAM back" — and almost none of them publish a measurement. A before number you can reproduce and an after number tied to a named machine. This post ships the harness, a real before snapshot from an 8 GB Apple M1 running macOS Sequoia 15.7.4, and the actual dry-run plan macos-debloater resolves on this exact machine. What it deliberately doesn't ship is an invented after number — the harness below is how you produce yours.
The catalog, first
macOS boots roughly 200 background services. The macos-debloater catalog carries 201 entries across four risk tiers, and every entry must resolve to a plist that actually exists on your macOS before it gets touched. The resolved plan shifts by OS version — on this Sequoia 15.x machine, Mode 1 resolves to 65 services:
| Mode | Services disabled | What you lose |
|---|---|---|
| 1 · Safe | 65 | Telemetry, analytics, diagnostics, Siri / Apple Intelligence backends |
| 2 · Balanced | 105 | + iCloud extras, Maps, media, App Store, network-sharing |
| 3 · Aggressive | 152 | + Spotlight, Find My, Messages, FaceTime, AirDrop, Time Machine |
| 4 · Dangerous | 198 | + thermal daemon, Mail, Contacts, crash reporting — real risk |
The harness
This is the exact script used for the numbers below — page-accurate vm_stat parsing (Apple Silicon pages are 16 KB, and getting that wrong fakes your results by 4x), launchd job counts for the gui domain, and total resident memory. Save it, run it before, run it after, diff.
#!/usr/bin/env bash
# ram-baseline.sh — snapshot macOS memory + launchd state
echo "== machine =="
sw_vers -productVersion
sysctl -n machdep.cpu.brand_string
echo "RAM total: $(( $(sysctl -n hw.memsize) / 1073741824 )) GB"
echo "== launchd jobs (gui domain) =="
launchctl list | wc -l | tr -d ' '
echo "== memory pages =="
PSIZE=$(pagesize)
vm_stat | awk -v ps="$PSIZE" '
/Pages free/ {free=$NF}
/Pages active/ {active=$NF}
/Pages inactive/ {inactive=$NF}
/Pages speculative/ {spec=$NF}
/Pages purgeable/ {purge=$NF}
/Pages wired down/ {wired=$NF}
/Pages compressed/ {comp=$NF}
END {
gsub(/\./,"",free); gsub(/\./,"",active); gsub(/\./,"",inactive)
gsub(/\./,"",spec); gsub(/\./,"",purge); gsub(/\./,"",wired); gsub(/\./,"",comp)
printf "free %6.0f MB\n", free*ps/1048576
printf "active %6.0f MB\n", active*ps/1048576
printf "inactive %6.0f MB\n", inactive*ps/1048576
printf "speculative %6.0f MB\n", spec*ps/1048576
printf "purgeable %6.0f MB\n", purge*ps/1048576
printf "wired %6.0f MB\n", wired*ps/1048576
printf "compressed %6.0f MB\n", comp*ps/1048576
printf "available~ %6.0f MB\n", (free+inactive+purge)*ps/1048576
}'
echo "== resident memory, all processes =="
ps -axo rss= | awk '{s+=$1} END {printf "%.2f GB\n", s/1048576}'
Before: the real snapshot
MacBook Air (M1, 2020), 8 GB RAM, macOS Sequoia 15.7.4, mid-session with browsers, editors and an agent runtime open — a realistic "my Mac feels tight" state, not a freshly-rebooted showroom floor:
| Metric | Before |
|---|---|
| launchd jobs (gui domain) | 362 |
| Pages free | 64 MB |
| Pages active | 1,876 MB |
| Pages inactive | 1,860 MB |
| Pages wired | 2,484 MB |
| Available (free+inactive+purgeable) | ~1,931 MB |
| Total resident, all processes | 4.33 GB |
64 MB of free pages on an 8 GB machine. That's the "why" in one line.
What Mode 1 resolves to on this machine
Dry-run first — it works with SIP enabled, changes nothing, and prints the resolved plan. On this machine, Mode 1 resolved to 65 services and 142 commands (each service gets a launchctl bootout to stop the running instance now, plus a launchctl disable to keep it from coming back at boot). The head of the plan tells you exactly which families go:
Plan - 65 service(s) to disable:
gui safe com.apple.generativeexperiencesd Generative AI experiences
gui safe com.apple.intelligencecontextd Apple Intelligence context runtime
gui safe com.apple.intelligenceflowd Apple Intelligence flow runtime
gui safe com.apple.intelligenceplatformd Apple Intelligence platform daemon
gui safe com.apple.knowledge-agent Knowledge / suggestion agent
gui safe com.apple.knowledgeconstructiond Knowledge graph construction
gui safe com.apple.mlruntimed Machine learning runtime
gui safe com.apple.naturallanguaged Natural language framework daemon
gui safe com.apple.analyticsagent Analytics agent
gui safe com.apple.ap.adprivacyd Ad privacy daemon
gui safe com.apple.ap.promotedcontentd Promoted content / ads
gui safe com.apple.appleseed.seedusaged AppleSeed usage reporter
...
That's the whole Apple Intelligence / on-device-ML family plus the analytics and ads daemons — the exact services that spin up models you never asked to run. This is also the honest caveat: these daemons are idle most of the time, so their contribution is a handful of resident MB each plus wakeup traffic. The bigger levers for raw RAM are the higher modes (and the optimization tweaks: Spotlight indexing, animations). Measure, don't guess.
Measuring after — properly
- Re-run the harness at a comparable moment — same apps open, similar workload. Memory numbers move with what you're doing; comparing a rebooted quiet machine against a mid-session one measures nothing.
- Compare four numbers: launchd job count (the cleanest delta — disabled services stop booting and get booted out), total resident, pages free + available, and the top-10 RSS table.
- Let it settle a few minutes after the run — some XPC services respawn lazily before they notice they're disabled.
We kept this run read-only (dry-run, no changes applied on the test machine), so we're publishing no after number here — the harness above is what produces yours, and we'd rather teach the measurement than invent the result.
Doing it yourself, safely
macos-debloater writes the same launchd disabled database launchctl disable writes — nothing under /System, no plists deleted, and every run snapshots the previous state to /Library/Application Support/macos-debloater/backups/ before touching anything. A hard deny list refuses boot-critical services like WindowServer even if you type them by hand. Real runs need root and SIP disabled; dry-run, list, and restore work without it. Restore is one menu option — undo the last run or re-enable the entire catalog.
Preview first, then apply:
# preview (no changes, works with SIP on):
MODE=1 DRY_RUN=1 AUTO_APPLY=1 # in the config, or pick Dry-run in the menu
sudo ./macos-debloater.sh
# apply for real (root + SIP off):
MODE=1 DRY_RUN=0 AUTO_APPLY=1
sudo ./macos-debloater.sh
Mode 4 exists, includes the thermal daemon, and is double-confirmed for a reason — read the README's warning section before you go anywhere near it. On a laptop, removing thermal throttling is hardware-damage territory.
The honest summary
Telemetry daemons are cheap; Apple Intelligence runtimes and Spotlight indexing are not. Whether Mode 1 frees 80 MB or 800 MB depends on your machine, your workload, and which services you actually had running — which is precisely why the number belongs to the harness and not to a blog post. Run it before, run it after, and you'll have the only RAM claim that matters: yours.
→ macos-debloater on GitHub · related: pyzule-rw vs xkvm · back to the arsenal