Now I'm trying to remember if I ever had a single epiphanic a-ha moment.

I don't think I did.  I remember being introduced to Unix the summer before my senior year of high school, when I was a research intern at a physics lab at UT Austin.  I learned very little about Unix, but learned how to drive Emacs inexpertly (I remain pretty mediocre at it thirty years later) and how to read and post to Usenet.

At Rice, although by my sophomore year I was making beer money as a student systems programmer on our VM/CMS system, I'd gravitated to the Sun3/60 workstations we had around the place.  And it would have been, I guess, in the fall of my sophomore year that I started playing around with Linux.  By the spring, IIRC, I had gotten 4 more MB of discrete DIP RAM (for my Gateway 2000 386DX/25) and repartitioned my hard disk (65MB RLL, IIRC).

The rest of undergrad life and graduate school I multibooted between DOS-Windows, OS/2, and Linux, but my primary environment became Linux/X by, oh, 1997 or so.  It became OS X in the early 2000s (too bad BeOS didn't make it) but that is (from my perspective as not-a-kernel-developer) just a BSD with a nice GUI; that's where I've stayed in terms of my favorite interactive environment.

As far back as 1998 I remember someone saying to me "I've never seen someone use a GUI as just a collection of terminal windows before."

My graduate advisor was Mike Mahoney, so I strongly suspect I absorbed a lot of the Way Things Ought To Be from him.  Which is why I guess I feel like the following principles are basically axioms of how you should design an operating system:

1) everything's a file, and a file is just an unstructured stream of bytes.  Device drivers look like files and give you byte streams
2) input on stdin, output on stdout, errors to stderr
3) pipe things that do #2 together to compose complex functionality.  Record semantics are the responsibility of the communicating applications to know, not the pipeline layer, which just transmits bytes
4) fork() and exec()

I am willing to grudgingly recognize that #4 in particular is not a great way to create UI-intensive user-facing apps, and that you do need some sort of threading/lightweight process model to deal with a bunch of asynchronous interrupt-triggered user interactions.  But for something operating as a classic transforming filter, which really *is* most computing problems, those four things do nicely.

Adam