<reasoning_content>
Thinking Process:

1.  **Analyze the Request:**
    *   Goal: Extend a job queue with retry logic and exponential backoff.
    *   Requirements:
        1.  Add `process_job(job_id: str, processor: Callable) -> bool` method.
        2.  Retry up to 3 times on exception.
        3.  Exponential backoff: 1s, 2s, 4s (simulated/stored).
        4.  Track retry count per job.
        5.  Return `True` on success, `False` on exhaustion.
    *   Context: The user provides an example snippet but doesn't provide the base `JobQueue` class. I need to infer or create a minimal base class structure that makes sense given the requirements, or just implement the class assuming it's a new implementation based on the prompt "Extend the job queue". Since the prompt says "Extend the job queue", I should probably provide a complete class that embodies this functionality, assuming a basic `JobQueue` structure might exist but isn't fully defined. However, to make the code runnable and complete based *only* on the prompt's requirements, I should implement the `JobQueue` class itself with the necessary internal state management.
    *   Constraint: Write *only* the Python code, no explanations.

2.  **Determine Class Structure:**
    *   Class name: `JobQueue`.
    *   Internal State:
        *   Needs to store jobs (data).
        *   Needs to track retry counts per job.
        *   Needs to store backoff delays (optional, but good for tracking).
    *   Methods:
        *   `__init__`: Initialize storage.
        *   `add_job`: Store job data.
        *   `process_job`: The core logic requested.
    *   Logic for `process_job`:
        *   Check if job exists.
        *   Loop max retries (3 times).
        *   Inside loop:
            *   Call `processor`.
            *   If success: return `True`.
            *   If exception:
                *   Increment retry count.
                *   Calculate backoff delay ($2^{attempt}$ or specific list [1, 2, 4]).
                *   Store backoff info.
                *   Continue.
        *   If loop finishes: return `False`.

3.  **Refining Backoff Logic:**
    *   Requirement: "1s, 2s, 4s".
    *   Retry limit: "up to 3 times".
    *   Interpretation:
        *   Attempt 1: Execute. Fail? -> Retry 1 (Backoff 1s).
        *   Attempt 2: Execute. Fail? -> Retry 2 (Backoff 2s).
        *   Attempt 3: Execute. Fail? -> Retry 3 (Backoff 4s).
        *   Actually, usually "retry up to 3 times" means 1 initial + 3 retries = 4 attempts, OR 3 total attempts.
        *   Standard interpretation in such contexts often implies: Try once. If fail, retry up to 2 more times (total 3). Or Try once. If fail, retry up to 3 more times (total 4).
        *   Let's look at the backoff list: 1s, 2s, 4s. That's 3 values. This suggests 3 *retries*.
        *   So: Initial attempt + 3 retries = 4 total executions.
        *   Wait, "retry up to 3 times" usually means max 3 *additional* attempts.
        *   Let's align the backoff sequence to the retry index.
        *   Retry 1 -> 1s.
        *   Retry 2 -> 2s.
        *   Retry 3 -> 4s.
        *   So if it fails on the 4th try, return False.
        *   Let's stick to the specific backoff values provided: [1, 2, 4].
        *   Implementation detail: `backoff_delays = [1, 2, 4]`.

4.  **Drafting the Code:**
    *   Need to import `Callable`, `Optional`, `Dict`, `Any` for type hinting.
    *   Need to handle exceptions generically (Exception).
    *   Need to store state (`jobs`, `retry_counts`, `backoff_delays`).

5.  **Handling "Extend":**
    *   Since I don't have the original base class, I will define the `JobQueue` class from scratch to ensure it meets the specific requirements provided in the prompt, acting as the extended version.

6.  **Code Structure:**
    ```python
    from typing import Callable, Dict, Any, Optional

    class JobQueue:
        def __init__(self):
            self.jobs: Dict[str, Dict[str, Any]] = {}
            self.retry_counts: Dict[str, int] = {}
            self.backoff_delays: Dict[str, float] = {}
            self.max_retries = 3
            self.backoff_sequence = [1, 2, 4] # seconds

        def add_job(self, job_id: str, data: Dict[str, Any]) -> None:
            self.jobs[job_id] = data
            self.retry_counts[job_id] = 0
            self.backoff_delays[job_id] = 0.0

        def process_job(self, job_id: str, processor: Callable) -> bool:
            if job_id not in self.jobs:
                return False

            job_data = self.jobs[job_id]
            retry_count = self.retry_counts.get(job_id, 0)
            max_attempts = self.max_retries + 1  # Initial + 3 retries? Or just 3 total?
            # Requirement says "retry up to 3 times". Usually means 1 initial + 3 retries.
            # Let's assume 1 initial + 3 retries = 4 calls.
            # Backoff sequence has 3 items.
            # Retry 1 -> index 0 (1s)
            # Retry 2 -> index 1 (2s)
            # Retry 3 -> index 2 (4s)

            attempts = 0
            while attempts < self.max_retries + 1:
                try:
                    processor(job_data)
                    self.retry_counts[job_id] = attempts
                    return True
                except Exception:
                    attempts += 1
                    if attempts <= self.max_retries:
                        # Store backoff delay for next attempt
                        delay_idx = attempts - 1
                        if delay_idx < len(self.backoff_sequence):
                            delay = self.backoff_sequence[delay_idx]
                            self.backoff_delays[job_id] = delay
                            self.retry_counts[job_id] = attempts
                            # Simulate wait
                    else:
                        # Max retries reached
                        break
            return False
    ```
    *Correction on Logic:*
    If `retry up to 3 times`, does that mean 3 total attempts or 3 retries (4 total)?
    Common parlance: "Retry 3 times" implies 3 attempts *after* the first failure.
    However, the backoff list has 3 elements (1, 2, 4). This fits perfectly with 3 retry attempts.
    So:
    1. Try (Initial)
    2. Fail -> Retry 1 (Backoff 1s)
    3. Fail -> Retry 2 (Backoff 2s)
    4. Fail -> Retry 3 (Backoff 4s)
    5. Fail -> Give up.

    Let's refine the loop to match this strictly.
    Max retries = 3.
    Retries happen after failure.
    Backoff is applied *before* the next retry.
    
    Revised Loop:
    1. Call processor.
    2. If success -> Return True.
    3. If fail:
       Increment retry count.
       If retry count <= max_retries:
           Get backoff delay.
           Store it.
           (Simulate sleep)
           Repeat loop.
       Else:
           Return False.

    Wait, `backoff_delays` requirement: "The backoff delays can be stored/tracked rather than actually sleeping."
    So I just need to update `self.backoff_delays[job_id]` with the value of the next wait time.

    Let's check the `add_job` method. The example shows `queue.add_job("job1", {"url": ...})`. I should include this method to make the example valid.

    Let's ensure type hints are correct.

    Final check on "retry up to 3 times".
    If I fail immediately:
    Retry 1 (Wait 1s)
    Retry 2 (Wait 2s)
    Retry 3 (Wait 4s)
    Retry 4 (Fail) -> Return False.
    Wait, if I retry 3 times, that means 3 *additional* calls.
    Total calls = 1 + 3 = 4.
    Backoff list has 3 items. This implies 3 backoffs.
    This matches 3 retries.
    
    Logic:
    attempt 0 (initial): No backoff.
    attempt 1 (retry 1): Wait 1s.
    attempt 2 (retry 2): Wait 2s.
    attempt 3 (retry 3): Wait 4s.
    If attempt 3 fails: Return False.

    Wait, the prompt says "If the processor raises an exception, retry up to 3 times".
    This implies the retry logic happens *after* the exception.
    So:
    1. Call. Fail.
    2. Check retry count (0).
    3. Increment to 1.
    4. Wait 1s.
    5. Call. Fail.
    6. Increment to 2.
    7. Wait 2s.
    8. Call. Fail.
    9. Increment to 3.
    10. Wait 4s.
    11. Call. Fail.
    12. Increment to 4.
    13. Stop. Return False.

    This seems consistent with "retry up to 3 times" (3 retries) and backoff list of 3 items.

    Wait, "Track retry count for each job".
    I need to store this in the class state.

    Let's write the code.

```python
from typing import Callable, Dict, Any

class JobQueue:
    def __init__(self):
        self.jobs: Dict[str, Dict[str, Any]] = {}
        self.retry_counts: Dict[str, int] = {}
        self.backoff_delays: Dict[str, float] = {}
        self.max_retries = 3
        self.backoff_sequence = [1, 2, 4]

    def add_job(self, job_id: str, data: Dict[str, Any]) -> None:
        self.jobs[job_id] = data
        self.retry_counts[job_id] = 0
        self.backoff_delays[job_id] = 0.0

    def process_job(self, job_id: str, processor: Callable) -> bool:
        if job_id not in self.jobs:
            return False

        retry_count = self.retry_counts.get(job_id, 0)
        job_data = self.jobs[job_id]
        
        # Attempt initial run
        try:
            processor(job_data)
            return True
        except Exception:
            pass

        # Retry logic
        for attempt in range(1, self.max_retries + 1):
            if attempt > 1:
                self.backoff_delays[job_id] = self.backoff_sequence[attempt - 2]
            
            # Wait logic simulated by storing
            # In a real scenario: time.sleep(self.backoff_delays[job_id])
            
            try:
                processor(job_data)
                self.retry_counts[job_id] = attempt
                return True
            except Exception:
                self.retry_counts[job_id] = attempt
                if attempt == self.max_retries:
                    break
                    
        return False
```
    Wait, re-reading: "Use exponential backoff between retries: 1s, 2s, 4s".
    If I fail on first try, I wait 1s, then try again.
    If I fail on second try, I wait 2s, then try again.
    If I fail on third try, I wait 4s, then try again.
    If I fail on fourth try (after 3 retries), I give up.

    My loop logic above:
    Initial try.
    Loop range(1, 4) -> 1, 2, 3.
    Inside loop:
    If attempt > 1: store backoff.
    Wait, for attempt 1 (Retry 1), we should wait 1s.
    For attempt 2 (Retry 2), we should wait 2s.
    For attempt 3 (Retry 3), we should wait 4s.
    So for attempt `i`, backoff is `backoff_sequence[i-1]`.

    Revised Logic:
    1. Try. Fail.
    2. Retry 1 (attempt 1): Wait 1s. Try. Fail.
    3. Retry 2 (attempt 2): Wait 2s. Try. Fail.
    4. Retry 3 (attempt 3): Wait 4s. Try. Fail.
    5. Return False.

    Correct mapping:
    Retry #1 -> Index 0 (1s)
    Retry #2 -> Index 1 (2s)
    Retry #3 -> Index 2 (4s)

    So in loop `for retry_idx in range(self.max_retries):`
    Wait `backoff_sequence[retry_idx]`.
    Call.
    If success -> Return True.

    Let's refine the state tracking.
    `retry_counts` should reflect how many times we've retried.
    
    Code Structure:
    ```python
    from typing import Callable, Dict, Any

    class JobQueue:
        def __init__(self):
            self.jobs: Dict[str, Any] = {}
            self.retry_counts: Dict[str, int] = {}
            self.backoff_delays: Dict[str, float] = {}
            self.max_retries = 3
            self.backoff_sequence = [1, 2, 4]

        def add_job(self, job_id: str, data: Dict[str, Any]) -> None:
            self.jobs[job_id] = data
            self.retry_counts[job_id] = 0
            self.backoff_delays[job_id] = 0.0

        def process_job(self, job_id: str, processor: Callable) -> bool:
            if job_id not in self.jobs:
                return False

            job_data = self.jobs[job_id]
            current_retry = 0
            
            # Initial attempt
            try:
                processor(job_data)
                return True
            except Exception:
                current_retry += 1

            # Retries
            for i in range(self.max_retries):
                # Store backoff delay for this retry attempt
                if i < len(self.backoff_sequence):
                    delay = self.backoff_sequence[i]
                    self.backoff_delays[job_id] = delay
                
                # Simulate sleep
                # time.sleep(delay)
                
                try:
                    processor(job_data)
                    self.retry_counts[job_id] = current_retry
                    return True
                except Exception:
                    current_retry += 1
                    self.retry_counts[job_id] = current_retry
                    
                    if current_retry >= self.max_retries:
                        break
            
            return False
    ```
    Wait, `current_retry` logic check.
    Initial attempt -> Fail. `current_retry` becomes 1.
    Loop `i` from 0 to 2 (max_retries=3).
    i=0: Wait 1s. Call. Fail. `current_retry` becomes 2.
    i=1: Wait 2s. Call. Fail. `current_retry` becomes 3.
    i=2: Wait 4s. Call. Fail. `current_retry` becomes 4.
    If I break when `current_retry >= self.max_retries` (3), I stop before the 3rd retry (i=2) if the condition is checked inside.
    Actually, `max_retries` usually defines the *number* of retries allowed.
    If I fail initial, I have 3 retries available.
    So I should loop 3 times.
    `for _ in range(self.max_retries):`
    Inside:
    Wait backoff.
    Try.
    If fail, continue.
    If success, return True.
    If loop finishes, return False.

    Let's trace:
    Initial: Fail.
    Retry 1 (i=0): Wait 1s. Fail.
    Retry 2 (i=1): Wait 2s. Fail.
    Retry 3 (i=2): Wait 4s. Fail.
    End loop. Return False.
    This matches "retry up to 3 times".

    State tracking:
    `retry_counts` should probably track the number of retries performed.
    If I succeed on Retry 1, count is 1.
    If I succeed on Initial, count is 0.
    The prompt says "Track retry count for each job".
    I will store the count of retries attempted.

    One detail: "The backoff delays can be stored/tracked rather than actually sleeping."
    I will update `self.backoff_delays[job_id]` before each retry.

    Final check on imports. `Callable` is needed.
    Final check on return types. `bool`.

    Wait, `add_job` in the example uses `{"url": "..."}`.
    My `process_job` calls `processor(job_data)`.
    The example: `def fetch_url(data): ... requests.get(data["url"])`.
    So `processor` receives the job data.
    My code does `processor(job_data)`. This matches.

    One small detail: `retry_counts` should probably be incremented only on failure.
    If I succeed on the first try, `retry_counts` should be 0.
    If I succeed on retry 1, `retry_counts` should be 1.
    My logic above:
    Initial: Fail -> `current_retry` = 1.
    Loop i=0 (Retry 1):
       Wait.
       Try.
       If Success: `self.retry_counts[job_id] = current_retry` (1). Return True.
       If Fail: `current_retry` (2).
    This seems correct.

    Wait, if I succeed on Initial, `retry_counts` stays 0.
    If I fail Initial, `retry_counts` becomes 1 (conceptually).
    My code sets `self.retry_counts[job_id] = current_retry` on success in the loop.
    What about the initial success?
    If initial succeeds, return True. `retry_counts` is 0 (from init). Correct.
    If initial fails, `current_retry` increments to 1.
    Inside loop, if success, I set `retry_counts` to `current_retry`.
    If loop finishes without success, `retry_counts` is 3 (max).
    This logic holds.

    One edge case: `max_retries` is 3.
    Loop range(3) -> 0, 1, 2.
    i=0: Retry 1.
    i=1: Retry 2.
    i=2: Retry 3.
    Total 3 retries.
    This matches requirement "retry up to 3 times".

    Wait, backoff sequence: 1s, 2s, 4s.
    i=0 -> 1s.
    i=1 -> 2s.
    i=2 -> 4s.
    Matches `self.backoff_sequence[i]`.

    Okay, ready to write.

    Wait, I need to check if `job_id` exists in `process_job`.
    The example calls `queue.process_job("job1", fetch_url)`.
    `queue.add_job` must have been called.
    So `process_job` should check existence.

    Also, `from typing import Callable` might be needed depending on Python version, but standard Python 3 allows `from typing import Callable`.
    I will include imports.

    One more check: "Track retry count for each job".
    I'll ensure `self.retry_counts` is updated appropriately.

    Final check on constraints: "Write only the Python code, no explanations."

    Wait, the prompt says "Extend the job queue". It implies there is an existing `JobQueue`.
    However, without the existing code, I must provide the class that embodies the extension.
    I will provide the full class definition.

    One detail on `add_job`. The example passes `{"url": ...}`.
    So `data` is a dict.
    My `__init__` and `add_job` should handle this.

    Let's refine the retry count tracking.
    If the job succeeds on the first try, retry count is 0.
    If it fails first, retries 1 (count 1), then succeeds. Count 1.
    If it fails 1, retries 2 (count 2), then succeeds. Count 2.
    If it fails 1, 2, 3, then gives up. Count 3.
    
    My code:
    Initial:
      Try.
      If success: return True. (retry_counts remains 0).
      If fail: current_retry = 1.
    Loop:
      Wait.
      Try.
      If success:
        set retry_counts = current_retry.
        return True.
      If fail:
        current_retry += 1.
    Return False.

    This seems robust.

    Wait, I should check if `job_id` is in `self.jobs`.
    If not, return `False`.

    Okay, constructing the final code block.

```python
from typing import Callable, Dict, Any, Optional

class JobQueue:
    def __init__(self):
        self.jobs: Dict[str, Dict[str, Any]] = {}
        self.retry_counts: Dict[str, int] = {}
        self.backoff_delays: Dict[str, float] = {}
        self.max_retries = 3
        self.backoff_sequence = [1, 2, 4]

    def add_job(self, job_id: str, data: Dict[str, Any]) -> None:
        self.jobs[job_id] = data
        self.retry_counts[job_id] = 0
        self.backoff_delays[job_id] = 0.0

    def process_job(self, job_id: str, processor: Callable) -> bool:
        if job_id not in self.jobs:
            return False

        job_data = self.jobs[job_id]
        current_retry_count = 0

        # Initial attempt
        try:
            processor(job_data)
            return True
        except Exception:
            current_retry_count += 1

        # Retry loop
        for i in range(self.max_retries):
            # Apply backoff delay logic
            if i < len(self.backoff_sequence):
                delay = self.backoff_sequence[i]
                self.backoff_delays[job_id] = delay

            try:
                processor(job_data)
                self.retry_counts[job_id] = current_retry_count
                return True
            except Exception:
                current_retry_count += 1
                self.retry_counts[job_id] = current_retry_count
                
                if current_retry_count > self.max_retries:
                    break
        
        return False
```
    Wait, `current_retry_count > self.max_retries` check inside the loop.
    If `max_retries` is 3.
    i=0 (Retry 1): Fail. `current` -> 2.
    i=1 (Retry 2): Fail. `current` -> 3.
    i=2 (Retry 3): Fail. `current` -> 4.
    If `current > 3` (4 > 3), break.
    So it breaks after Retry 3 fails.
    This means it attempted Retry 3.
    Loop range(3) -> 0, 1, 2.
    i=0: Retry 1.
    i=1: Retry 2.
    i=2: Retry 3.
    If Retry 3 fails, `current` becomes 4. Break.
    Return False.
    Correct.

    Wait, `self.retry_counts[job_id] = current_retry_count` inside the `except` block.
    If I succeed in Retry 1 (i=0), `current_retry_count` is 1. I set count to 1. Return True.
    If I succeed in Initial, count is 0. Return True.
    If I fail Retry 3 (i=2), `current` becomes 4. Set count to 4. Loop ends. Return False.
    Seems okay. Although "retry count" usually implies number of retries, not attempts.
    If I retry 1 time, count is 1.
    If I retry 3 times, count is 3.
    My code sets count to `current_retry_count`.
    Initial fail -> current 1.
    Retry 1 fail -> current 2.
    Retry 2 fail -> current 3.
    Retry 3 fail -> current 4.
    So if I succeed on Retry 1, count is 1.
    If I succeed on Retry 2, count is 2.
    If I succeed on Retry 3, count is 3.
    If I fail all, count is 3 (before incrementing to 4) or 4?
    In the last iteration (i=2), if fail:
    `current` becomes 4.
    Set count to 4.
    Break.
    Return False.
    Is "retry count" supposed to be 3 or 4?
    "retry up to 3 times".
    If I fail 3 times (after initial), I retried 3 times.
    So count should be 3.
    My code sets it to 4.
    Let's adjust. `current_retry_count` should represent the number of retries performed so far.
    Initial attempt = 0 retries.
    If fail: 1 retry.
    If fail: 2 retries.
    If fail: 3 retries.
    So `current_retry_count` should be the loop index + 1?
    Or just use `i`.
    If I succeed on `i=0` (1st retry), retries = 1.
    If I succeed on `i=2` (3rd retry), retries = 3.
    If I fail all, retries = 3.
    
    Let's align `current_retry_count` with `i + 1` inside the loop.
    Initial fail -> `retry_count` = 1.
    Loop `i` from 0 to 2.
    If success: `self.retry_counts[job_id] = i + 1`.
    If fail: continue.
    If loop ends: `self.retry_counts[job_id] = 3`.

    Refined logic:
    ```python
        initial_fail = False
        try:
            processor(job_data)
            return True
        except Exception:
            initial_fail = True

        for i in range(self.max_retries):
            if i < len(self.backoff_sequence):
                self.backoff_delays[job_id] = self.backoff_sequence[i]
            
            try:
                processor(job_data)
                self.retry_counts[job_id] = i + 1
                return True
            except Exception:
                pass
        
        self.retry_counts[job_id] = self.max_retries
        return False
    ```
    This tracks retries 1, 2, 3 correctly.
    If initial success: 0.
    If Retry 1 success: 1.
    If Retry 2 success: 2.
    If Retry 3 success: 3.
    If Retry 3 fail: 3.
    
    Wait, if initial fail, and Retry 1 success.
    Retry count is 1.
    If initial fail, Retry 1 fail, Retry 2 success.
    Retry count is 2.
    This makes sense.

    Wait, `self.retry_counts` init is 0.
    If I succeed initially, 0.
    If I fail initially, I enter loop.
    If I succeed in loop `i=0` (Retry 1), I set 1.
    If I succeed in loop `i=1` (Retry 2), I set 2.
    If I succeed in loop `i=2` (Retry 3), I set 3.
    If I fail loop `i=2`, I set 3 (after loop).
    This looks consistent.

    Wait, one detail: "Use exponential backoff between retries: 1s, 2s, 4s".
    This means:
    Retry 1 -> Wait 1s.
    Retry 2 -> Wait 2s.
    Retry 3 -> Wait 4s.
    My loop `i=0, 1, 2` maps to `self.backoff_sequence[0, 1, 2]`.
    This is correct.

    Final check on imports. `Callable` from `typing`.
    I will provide the full code.

</reasoning_content>

<content>
```python
from typing import Callable, Dict, Any

class JobQueue:
    def __init__(self):
        self.jobs: Dict[str, Dict[str, Any]] = {}
        self.retry_counts: Dict[str, int] = {}
        self.backoff_delays: Dict[str, float] = {}
        self.max_retries = 3
        self.backoff_sequence = [1, 2, 4]

    def add_job(self, job_id: str, data: Dict[str, Any]) -> None:
        self.jobs[job_id] = data
        self.retry_counts[job_id] = 0
        self.backoff_delays[job_id] = 0.0

    def process_job(self, job_id: str, processor: Callable) -> bool:
        if job_id not in self.jobs:
            return False

        job_data = self.jobs[job_id]

        # Initial attempt
        try:
            processor(job_data)
            return True
        except Exception:
            pass

        # Retry loop
        for i in range(self.max_retries):
            # Set backoff delay for this retry attempt
            if i < len(self.backoff_sequence):
                self.backoff_delays[job_id] = self.backoff_sequence[i]

            try:
                processor(job_data)
                self.retry_counts[job_id] = i + 1
                return True
            except Exception:
                pass
        
        # If all retries exhausted
        self.retry_counts[job_id] = self.max_retries
        return False
```
</content>