• Depending upon existing indexes, this could be a significant improvement:

    DROP TABLE #fact_sales

    CREATE TABLE #fact_sales (agent_id INT, product_code INT, product_id VARCHAR(5))

    INSERT INTO #fact_sales (agent_id, product_code, product_id)

    VALUES

    (1,1,'1'),

    (1,1,'2'),

    (1,1,'3'),

    (1,1,'4'),

    (1,1,'5'),

    (2,3,'1'),

    (2,3,'1'),

    (3,2,'1'),

    (3,2,'1'),

    (4,1,'1'),

    (4,1,'2')

    CREATE CLUSTERED INDEX cx_Everything ON #fact_sales (Product_ID, agent_id, product_code)

    -- existing version

    SELECT product_id

    FROM #fact_sales

    WHERE (agent_id=1 AND product_code in (1))

    AND product_id IN (

    SELECT product_id

    FROM #fact_sales

    WHERE (agent_id=2 AND product_code in (3))

    AND product_id IN (

    SELECT product_id

    FROM #fact_sales

    WHERE (agent_id=3 AND product_code in (2))

    AND product_id IN (

    SELECT product_id

    FROM #fact_sales

    WHERE (agent_id=4 AND product_code in (1))

    )

    )

    )

    -- alternative version

    SELECT Product_ID

    FROM (

    SELECT f.Product_ID, agent_id, product_code

    FROM #fact_sales f

    WHERE (agent_id = 1 AND product_code = 1)

    OR (agent_id = 2 AND product_code = 3)

    OR (agent_id = 3 AND product_code = 2)

    OR (agent_id = 4 AND product_code = 1)

    GROUP BY f.Product_ID, agent_id, product_code

    ) d

    GROUP BY Product_ID

    HAVING COUNT(*) = 4

    “Write the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    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