Personal OS · v1.0

The morning routine is a deployment target.

macOS is the platform. Raycast, Hammerspoon, Hazel, launchd, and the rest are the runtime. Build the personal OS that runs your day — without you operating it.

01 · The layers

Five layers between you and a day that runs itself.

The personal OS isn't one app. It's a stack. Capture flows in. Automation shapes it. Memory holds it. Triggers fire it. Output ships it. Each layer is one or two tools, no more.

1
Capture · the inbox for everything
Make friction zero between thought and record.

If capturing costs a click, you won't capture. The capture layer has to be reachable in under a second, from anywhere, with zero modal flow.

Raycast— launcher + snippets Karabiner-Elements— key remap
2
Automation · the things you do every day, scripted
If you do it twice, script it. If you do it weekly, schedule it.

The automation layer is the difference between a productivity stack and a job. Every repeated action is a workflow that should run without you.

Hammerspoon— Lua + macOS Apple Shortcuts— cross-app glue n8n— self-hosted workflows
3
Memory · your second brain, on disk
Plain-text files. Backlinks. Yours forever.

Notion is a database someone else owns. The memory layer is plain markdown, on your filesystem, queryable with rg, port-out in 60 seconds.

Obsidian— vault + graph Logseq— block outlining
4
Trigger · fires on its own
The time, the file, the event — and the thing runs.

A trigger is anything that wakes a workflow. A clock. A file landing. A USB plug. The trigger layer is the wiring between the world and your automations.

launchd— macOS cron Hazel— file watcher Keyboard Maestro— event-based
5
Output · publish, deploy, share
From local file to public URL, no manual deploy.

The output layer is the part most people skip. They capture, they automate, they forget to ship. The output layer is a one-way function: local artifact → deployed URL → indexed by Google.

mkdocs— docs site Cloudflare Pages— push-to-deploy
02 · Why

The OS is a bottleneck only if you let it be.

Most "productivity advice" optimizes the wrong layer. They teach you to plan better, journal harder, wake up earlier. None of it scales. The personal OS scales because each script runs forever.

Signal: "I keep doing the same setup every morning" · "I forget to back up the notes folder" · "I lose 30 minutes a week moving files around" · "my weekly review takes 4 hours"
↓
Action: Each one is a workflow. Each workflow has a trigger, a script, an output. The morning routine is a launchd job that runs at 6:30. The backup is a launchd job that runs hourly. The file move is a Hazel rule. The review is an Obsidian template + a cron job that drafts the agenda.
Signal: "But I want flexibility" · "What if I change my mind?" · "I don't want to be a robot"
↓
Action: The OS handles the boring 80%. You handle the 20% that actually requires taste. The morning routine doesn't need your taste — it needs the lights on, the playlist queued, the inbox triaged. The 4-hour review becomes a 20-minute review because the system pre-drafted it.
03 · The artifact

launchd: cron that survives sleep.

cron on macOS is technically there, but launchd is the right answer. It respects sleep/wake, runs missed jobs on resume, and lives at ~/Library/LaunchAgents/ (the tilde expands to $HOME, the standard Unix convention). Three plist files, and your day runs itself.

# ~/Library/LaunchAgents/com.kai.morning.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>com.kai.morning</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/zsh</string>
    <string>-c</string>
    <string>~/bin/morning.sh</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict>
    <key>Hour</key><integer>6</integer>
    <key>Minute</key><integer>30</integer>
  </dict>
  <key>RunAtLoad</key><true/>
</dict>
</plist>

# ~/bin/morning.sh
#!/bin/zsh
# 6:30am. Lights, music, inbox triaged, day planned.

osascript -e 'tell application "System Events" to set volume 5'  # gentle wake volume
osascript -e 'tell application "Music" to play playlist "Morning"'
hs -A -c "lightMode('sunrise')"  # Hammerspoon: warm lights via Hue
~/bin/triage-inbox.sh  # labels, archives, surfaces 3 must-reads
~/bin/draft-daily-note.sh  # Obsidian daily note pre-populated
terminal-notifier -message "Day is set. Top 3 ready."  # desktop notification
04 · In practice

The Monday morning audit.

A worked example: the weekly review that used to take 4 hours and now takes 20 minutes, because the system pre-drafts the agenda and ships the report.

The trigger · Sunday 8pm

A launchd job fires. ~/bin/weekly-review.sh runs. It pulls last week's git activity, last week's calendar, last week's inbox labels, and last week's Obsidian daily notes into one markdown file.

The output · 8:04pm

An Obsidian weekly note appears with: shipped-this-week, parked-this-week, replied-this-week, replied-but-stuck, meetings-that-shipped, meetings-that-didn't. Plus three open threads the LLM thinks need attention.

# ~/bin/weekly-review.sh (excerpt)

# 1. Pull the last 7 days of git
gh pr list --state merged --search "merged:>=$(date -v-7d +%Y-%m-%d)" \
  --json number,title,mergedAt \
  | jq -r '.[] | "- #\(.number) \(.title)"' >> /tmp/shipped.md

# 2. Pull the last 7 days of calendar
icalBuddy -n -ec "Meetings" -ps "/Time Zone/⏰/🔁" -b "" \
  -eep "notes,location" eventsFrom:$(date -v-7d +%Y-%m-%d) to:$(date +%Y-%m-%d) \
  >> /tmp/meetings.md

# 3. Pull the last 7 days of Obsidian daily notes
rg -l '^# Daily' ~/Notes/daily/*.md -t markdown | tail -7 \
  | xargs cat >> /tmp/journal.md

# 4. LLM synthesizes the threads
cat /tmp/shipped.md /tmp/meetings.md /tmp/journal.md \
  | llm -m claude-haiku-4-5 \
  -s "Pull 3 open threads from this week, 3 dropped balls, 3 wins." \
  > ~/Notes/weekly/$(date +%Y-%m-%d)-review.md

# 5. Open the note in Obsidian, ready for the user to scan
open -a Obsidian "~/Notes/weekly/$(date +%Y-%m-%d)-review.md"

Total runtime: 38 seconds. The user reads, adds 2 minutes of edits, ships.
05 · Forking the OS

Same 5 layers, your 12 tools.

Cross-platform picks for the same architecture. The layers don't change. The bindings do.

Linux / WSL
Replace Raycast with rofi/wofi. Replace Hammerspoon with sxhkd + xdotool. Replace Hazel with inotifywait. Everything else maps cleanly.
Windows / PowerToys
Replace Raycast with PowerToys Run. Replace Hammerspoon with AutoHotkey. Replace Hazel with File History + a watcher script. launchd → Task Scheduler.
iOS-first
Replace Raycast with Shortcuts + a single home-screen widget. Replace Obsidian with Working Copy + a git-based vault. Trigger layer: Shortcuts automations.
Team-shared OS
Drop the personal bias. Add Linear, Notion, Slack automations. The triggers become "when Linear ticket moves" and "when Stripe webhook fires."

Nine stacks. One philosophy. Zero babysitting.