Thanks again everyone for the feedback. Continuing a few of the items that were brought up:
AsyncMessage and Synchronous, Non-MainActor Posting
It's true that Message & Sendable is enough to have an observer run synchronously on the thread of the poster, but this isn't too different from the status quo today: addObserver() with Notification will run an observer in a synchronous, non-isolated context, requiring a developer who wishes to mutate state in response to a Notification to either only interact with other non-isolated variables, or spin up a Task to mutate state. For messages and observers that run on MainActor, MainActorMessage is a good solution. For background posters, it's a little trickier.
We're constrained by existing uses of post() which may be on a background thread and which expect post() to be a synchronous function, looping through all observers synchronously to call them.
To account for this, we synchronously enqueue the observers to be processed by a single Task, maintained by NotificationCenter. This results in post() remaining synchronous, while the Task can run each observer.
If we inherited the isolation of the observer declaration site, we would need a way to enqueue the observer call onto the observer declaration site's isolation synchronously (to ensure post() remains synchronous), or we would need to continue to use our existing Task solution, but make a second isolation hop onto the observer declaration site's isolation.
makeNotification()/makeMessage() and Subject
Message and Notification are not quite equivalent to each other - while Notification packages userInfo and object together, many use cases of Notification have a nil object, or only ever use object to filter notifications, but don't otherwise use the object in other logic. This is one of the reasons we've designed Message to consider Subject as something separate from the Message. Developers are free to re-use Subject in Message if it makes sense.
At the moment, makeNotification() is not expected to fill in notification.object, opting instead to favor the subject passed in to post(). We'll have to consider this behavior further, particularly for the use cases where a developer is ferrying their subject within Message. Perhaps it would make more sense to have makeNotification(from: message, with: subject) but keep makeMessage(from: notification)?
MainActorMessage vs @MainActor
@MainActor does not participate in Swift generics, so we're unable to offer an overload of addObserver() based on a type with this annotation alone.
ObservationToken and non-copyable
If ObservationToken was non-copyable, we would likely want its deinit to remove the observer registration, but that would mean letting the token go out of scope would result in observers being removed "implicitly", which could cause bugs. I think that unfortunate behavior may be worse than the benefit of having a non-copyable ObservationToken.