Note that what I actually want here is for promise_class() to live directly on UserAgent and for start_p to directly do ->promise_class->new rather than Mojo::Promise->new; this module is essentially a way to try that out.
And, yes, this is providing rope for people to choose a stupid class that doesn't implement promises correctly, which is arguably fragile (and part of why I provide ->promise_roles(); it's not strictly necessary but it makes this kind of mistake less likely, though a badly written role can screw things up, too..), but, if so, then large parts of Mojolicious are similarly fragile (e.g., someone could just as easily do ua->transactor(something that doesn't implement Transactor right) and likewise lose).
... and since I can't/shouldn't edit UserAgent directly, the next best thing is to intercept promise creation as early as possible. And as far as I can tell this implementation is as close as it gets: start_p() creates the promise, then calls start() but doesn't actually pass the promise itself (it only shows up in the callback which won't get invoked until after start_p returns). The reblessing is happening before any possibility of method calls on the object.
Also, this way, if someone wants to customize Transactor::promisify, the additional promise behaviours/methods will be available there. Your solution of returning a fresh promise out of start_p wouldn't allow that.
(yes, I understand start_p could get entirely rewritten, but I'm using an around() hook which is already asking for trouble on that front; I guess I'm not clear on why reblessing is such a huge sin on top of that...)