blog / macos

Reclaim RAM on macOS by disabling launchd services — the measurement harness, with real before numbers from an 8 GB M1

blessed0x · 2026-09-07 · ~8 min read · macos-debloater ↗

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:

ModeServices disabledWhat you lose
1 · Safe65Telemetry, analytics, diagnostics, Siri / Apple Intelligence backends
2 · Balanced105+ iCloud extras, Maps, media, App Store, network-sharing
3 · Aggressive152+ Spotlight, Find My, Messages, FaceTime, AirDrop, Time Machine
4 · Dangerous198+ 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:

MetricBefore
launchd jobs (gui domain)362
Pages free64 MB
Pages active1,876 MB
Pages inactive1,860 MB
Pages wired2,484 MB
Available (free+inactive+purgeable)~1,931 MB
Total resident, all processes4.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

  1. 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.
  2. 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.
  3. 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