Ok this was a Big Frustrating Bug that I believe is fixed now.
Given everything in the character graph (both AIMoveTo nodes route OnSuccess and OnFail to the same cleanup, so a normal fail wouldn't leave it stuck), and given the bug only shows up with high AI counts, my strongest remaining hypothesis is:
The async pathfinding request itself never resolves.
AIMoveTo is a latent async node (AIAsyncTaskBlueprintProxy). It works by handing a path request to the nav system and waiting for a delegate callback. Unreal's nav system queues these requests and services a limited number per frame. Under normal load that's invisible and everyone's request gets serviced within a frame or two. But with a lot of AI all requesting paths to a shared target (the Keep, or the player) at overlapping times, that queue can back up hard enough that:
some requests get delayed for a very long time (looks like "frozen" even though it will eventually resolve), or
in some UE versions/configurations, a request that gets superseded or invalidated (e.g. the actor issues a new move request before the old one's callback fires. since the gates re-trigger frequently it can have its original delegate silently dropped, and if nothing else re-triggers movement afterward, the actor just sits there, gated by whatever bool was waiting on that callback.
That fits everything I've seen: no collision, no visible cause, isolated individuals, only under load. It's not something wired wrong in the Blueprint. It's the async contract ("something will call me back eventually") breaking down under queue pressure, and this codebase has no timeout/watchdog to recover when that promise doesn't get kept.
Now there are timeout/watchdog event triggers in place to resolve these.
