If both circles are headed to the same destination, but not in exactly the same line, then your logic for pushing them apart will actually already route the smaller circle around the larger one, slowly.
Here you can see three iterations of advancing the circles along their velocities, computing penetrations, and adding separating offsets. In the process, the smaller circle climbs from about the 10:00 position on the larger circle's clock face up toward the 12:00 position, even though we never "told" it to.
So, that might be why you're having trouble finding code in Ogar or Agar.io-clone that handles this routing-around behaviour: they might not code that behaviour explicitly. It can arise as an emergent result of applying offsets along the line separating the circles.
That said, you might find this effect is a bit too gradual on its own - especially when the circles are travelling directly in line with one another. There, pushing the small circle back along the line between the circles just reduces its net velocity along the same line, rather than displacing it above or below the obstacle. (You can think of it like all the little one's effort is going into pushing against the big circle, with no component sliding along the surface)
So we can give the small circle a little extra nudge to help it in these situations. I do something similar in this answer, though there the obstacles are stationary so we can plan the movement of the ball around them without order-of-update considerations. For many co-navigating balls, we'll want some type of iterative solution instead.
I don't know exactly what you have in mind with the proposal to 'remove the magnitude of the "chase the mouse" velocity in the direction of the normal', but here's how you can do that after you've resolved the collision between two circles:
toTarget = targetPosition - centerPosition
collisionNormal = normalize(centerPosition - obstacle.centerPosition)
// This is the component of the toTarget vector pointing into the obstacle.
// The min(, 0) discards cases where our toTarget actually points out of the collision.
intoCollision = min(dot(toTarget, normal), 0)
toTargetNonColliding = toTarget - normal * intoCollision
A few cautionary notes about doing this type of adjustment:
This subtraction can produce a zero vector when the collision normal is pointing exactly opposite the direction we want to go. In such a situation, you can fall back on a perpendicular to the collision normal
(-normal.y, normal.x) to avoid a deadlock.
A circle might find itself wedged between two or more obstacles, in which case considering only one normal might not tell you enough about the nearby constraints, and considering all normals might leave you with nowhere to move at all. You'll need to decide what kind of behaviour you want in that scenario.
You may want to apply this type of adjustment a little at a time, as a nudge or persistent spring force over a range of nearby distances, rather than an all-or-nothing instantaneous velocity change.
If you completely eliminate movement into collision on frames with collision, you'll tend to not encounter a collision in the next frame (if you're dealing with just the one obstacle), falling back on your default behaviour, which sends the velocity back into a colliding direction. This can make your following object appear to stutter or zig-zag on alternating frames as it switches between collision-avoiding and normal velocity calculations.