Using max for each sub group does not seem to work.
You have two right parenthesis more than left ones and a missing alias:

Select *  
from wh_acct a                                 -- alias added
where a.rundate = 
      (select max(a2.rundate)  
       from wh_acct a2                         -- parenthesis removed
       WHERE a2.ORGA = a.ORGA)
UNION
Select *  
from wh_acct a                                 -- alias added 
where a.rundate = 
      (select max(a2.rundate)  
       from wh_acct a2                         -- parenthesis removed
       WHERE a2.PERS = a.PERS) ;


You could also write the query with OR instead of UNION:

Select *  
From wh_acct a
Where a.rundate = 
      (Select max(a2.rundate)  
       From wh_acct a2   
       Where a2.ORGA = a.ORGA)
   Or a.rundate = 
      (Select max(a2.rundate)  
       From wh_acct a2   
       Where a2.PERS = a.PERS) ;


Or using window functions:

WITH cte AS
    ( SELECT ACCT_NBR, MAIL_TY, ORGA, PERS, RUN_DATE, 
             MAX(rundate) OVER (PARTITION BY ORGA) AS max_rundate_orga,
             MAX(rundate) OVER (PARTITION BY PERS) AS max_rundate_pers 
      FROM wh_acct
    ) 
SELECT ACCT_NBR, MAIL_TY, ORGA, PERS, RUN_DATE
FROM cte
WHERE rundate = max_rundate_orga
   OR rundate = max_rundate_pers ;




If, as your sample shows, all rows have NULL in orga where there is a value in pers and vice versa, you can change the UNION to UNION ALL to improve efficiency as there would be no need to check the subresults for duplicates (which is what UNION does). The window-cte version could also be rewritten as a union (ALL) of 2 subqueries.

Indexes on (orga, run_date) and (pers, run_date) would help, too.