Bien, encontré la respuesta. Aparentemente, necesito escapar dos veces la barra invertida en el reemplazo. Además, necesito E
-prefijo y barras invertidas de doble escape en el patrón de búsqueda en versiones anteriores de postgres (8.3 en mi caso). El código final se ve así:
regexp_replace('abc [def]', E'([\\[\\]\\(\\)\\\\\?\\|_%])', E'\\\\\\1', 'g')
Sí, se ve horrible, pero funciona :)