This works but it's quite expensive to run:
WITH Pass1 AS (
SELECT
PoolId = DENSE_RANK() OVER(ORDER BY ResidentId),
HouseId, ResidentId
FROM #HouseResident
),
Pass2 AS (
SELECT
PoolId = MIN(PoolId) OVER (PARTITION BY HouseId),
HouseId, ResidentId
FROM Pass1
),
Pass3 AS (
SELECT
PoolId = MIN(PoolId) OVER (PARTITION BY ResidentId),
HouseId, ResidentId
FROM Pass2
),
Pass4 AS (
SELECT
PoolId = MIN(PoolId) OVER (PARTITION BY HouseId),
HouseId, ResidentId
FROM Pass3
)
SELECT
PoolId = MIN(PoolId) OVER (PARTITION BY ResidentId),
HouseId,
ResidentId
FROM Pass4;
Also, you'd have to add more blocks until no changes are found, in order to "reach the end". Nesting CTE's like this can get very expensive after 3 or 4 blocks. It might be quicker to do the same thing using updates.
For fast, accurate and documented assistance in answering your questions, please read this article.
Understanding and using APPLY, (I) and (II) Paul White
Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden