<reasoning_content>
Thinking Process:

1.  **Analyze the Request:**
    *   Goal: Extend a job queue (implicitly defined or to be defined) with retry logic and exponential backoff.
    *   Requirements:
        1.  Add `process_job(job_id: str, processor: Callable) -> bool` method.
        2.  Retry up to 3 times if an exception occurs.
        3.  Exponential backoff: 1s, 2s, 4s between retries.
        4.  Track retry count for each job.
        5.  Return `True` on success, `False` on exhaustion.
    *   Constraint: Write only Python code, no explanations.
    *   Context: The prompt implies an existing `JobQueue` class (based on the example usage `queue = JobQueue()`, `queue.add_job(...)`, `queue.process_job(...)`). I need to define the class structure to make the code runnable/complete based on the example provided.

2.  **Determine Class Structure:**
    *   The example shows:
        ```python
        queue = JobQueue()
        queue.add_job("job1", {"url": "https://example.com"})
        # ...
        success = queue.process_job("job1", fetch_url)
        ```
    *   I need to define `JobQueue` class with `add_job` (to store jobs) and `process_job`.
    *   I need to store job data, retry count, and potentially backoff delays.
    *   Since the requirement says "The backoff delays can be stored/tracked rather than actually sleeping", I won't use `time.sleep`. I'll just record the delays.

3.  **Drafting the Logic:**
    *   `JobQueue` class.
    *   `__init__`: Initialize storage (dict for jobs, dict for metadata).
    *   `add_job`: Store job data.
    *   `process_job`:
        *   Retrieve job data.
        *   Define max retries (3 attempts total, meaning 2 retries after the first failure? Or 3 attempts including the first? Usually "retry up to 3 times" means 3 retries + initial attempt = 4 attempts, or 3 attempts total. Looking at the example logic "retry up to 3 times", combined with "1s, 2s, 4s", it suggests specific backoff steps. Let's interpret "retry up to 3 times" as 3 *additional* attempts after the first one, OR 3 total attempts.
        *   Standard interpretation of "retry up to N times": Total attempts = N + 1 (initial + N retries). However, often in simple contexts, it means "Total attempts = 3". Let's look at the backoff sequence: 1s, 2s, 4s. That's 3 intervals. This implies:
            *   Attempt 1 (Initial)
            *   Fail -> Wait 1s -> Attempt 2
            *   Fail -> Wait 2s -> Attempt 3
            *   Fail -> Wait 4s -> Attempt 4
            *   Or:
            *   Attempt 1
            *   Fail -> Wait 1s -> Attempt 2
            *   Fail -> Wait 2s -> Attempt 3
            *   Fail -> Done (3 attempts total).
        *   Given "retry up to 3 times", it usually means 3 retries *after* the initial attempt. But the backoff list has 3 items (1, 2, 4). If I have 3 retries, I need 3 backoff intervals.
        *   Let's align with the backoff list provided: 1s, 2s, 4s. That's 3 steps.
        *   Logic:
            *   Run processor.
            *   If success -> return True.
            *   If fail -> increment retry count.
            *   If retry_count < 3 (meaning we have retries left): Wait (backoff[retry_count-1]), repeat.
            *   If retry_count >= 3: return False.
            *   Wait, if "retry up to 3 times", does that mean max retries = 3?
            *   Let's assume max_retries = 3 (total attempts = 4). Backoff steps needed: 3.
            *   Wait, the prompt says "retry up to 3 times". Usually, if I retry 3 times, I make 3 additional calls.
            *   Let's look at the backoff: 1s, 2s, 4s.
            *   Sequence:
                1. Call. Fail.
                2. Wait 1s. Call. Fail.
                3. Wait 2s. Call. Fail.
                4. Wait 4s. Call. Fail.
                5. Stop.
            *   This requires 3 waits.
            *   Let's implement a loop with `max_retries = 3` (meaning 3 retries).
            *   Store `retry_count` in job metadata.

4.  **Refining Logic based on "Retry up to 3 times":**
    *   Interpretation A: 1 initial + 3 retries = 4 attempts.
    *   Interpretation B: 3 total attempts (2 retries).
    *   Given the backoff values (1, 2, 4) are three values, it strongly suggests 3 *waiting periods* are allowed. This aligns with Interpretation A (3 retries).
    *   However, often "retry 3 times" is colloquially used for "Try 3 times total".
    *   Let's stick to the backoff list length matching the retry count. If there are 3 backoff values, there should be 3 retries.
    *   State: `job_metadata = { 'data': ..., 'retry_count': 0 }`
    *   Loop `for attempt in range(4)`: (1 initial + 3 retries)
        *   Try processor.
        *   If success, return True.
        *   If fail:
            *   increment retry count.
            *   if retry_count <= 3:
                *   record backoff delay.
            *   else:
                *   return False.

    *   Wait, simpler logic:
        *   `max_retries = 3`
        *   `retry_count = 0`
        *   Loop `while True`:
            *   try: return True
            *   except:
                *   if retry_count < max_retries:
                    *   record backoff (index based on retry_count)
                    *   retry_count += 1
                *   else:
                    *   return False

5.  **Data Structures:**
    *   `self.jobs`: dict mapping job_id to job data.
    *   `self.job_metadata`: dict mapping job_id to metadata (retry_count, etc.).
    *   `self.backoff_delays`: list [1, 2, 4].

6.  **Code Construction:**
    *   Import `Callable` from `typing`.
    *   Define `JobQueue`.
    *   Implement `add_job`.
    *   Implement `process_job`.
    *   Handle exceptions.
    *   Track retries.
    *   Return bool.

7.  **Edge Case:** What if the job doesn't exist? The example implies it will exist (`queue.add_job` before `process_job`). I'll assume it exists or handle it gracefully (raise or return False). I'll assume it exists based on the example flow.

8.  **Final Check on Constraints:**
    *   "Write only the Python code".
    *   No explanations.
    *   Backoff delays tracked rather than sleeping.
    *   Exponential backoff 1s, 2s, 4s.

9.  **Implementation Details:**
    *   Need to import `Callable`, `Optional`, `Dict`, `Any`.
    *   `process_job` needs to handle the job being processed.
    *   Should I clear the job after processing? The example doesn't specify, but typically jobs are removed after success. However, `process_job` is called explicitly in the example. I will just return the status. I won't auto-delete unless necessary, but to keep state clean for subsequent calls (if any), I might reset retry count. But for this snippet, just tracking is enough.
    *   Actually, usually `process_job` implies execution. If it fails, it stays in queue.
    *   I need to store the `retry_count` inside the queue state so it persists across multiple calls to `process_job` for the same job if it fails.
    *   Wait, the example shows `process_job` being called once. But requirement 4 says "Track retry count for each job". This implies state needs to persist across multiple invocations of `process_job` for the same `job_id` if it keeps failing.
    *   So, `self.job_states` needs to store `retry_count`.

10. **Refining Retry Logic:**
    *   If `retry_count` is stored in `self.job_states`, then `process_job` reads it, increments it if failed, and writes it back.
    *   Max retries = 3.
    *   So if `retry_count` >= 3, return False immediately (don't try again).
    *   Wait, if I call `process_job` multiple times manually for the same job, the state must persist.
    *   Let's assume `process_job` is the entry point for execution logic.
    *   Structure:
        ```python
        class JobQueue:
            def __init__(self):
                self.jobs = {}
                self.retries = {} # job_id -> count
            def add_job(self, job_id, data):
                self.jobs[job_id] = data
                self.retries[job_id] = 0
            def process_job(self, job_id, processor):
                if job_id not in self.jobs:
                    return False
                if self.retries[job_id] >= 3:
                    return False
                try:
                    processor(self.jobs[job_id])
                    return True
                except Exception:
                    self.retries[job_id] += 1
                    # Calculate backoff delay
                    delay = [1, 2, 4][self.retries[job_id] - 1]
                    # Store delay logic or just return False?
                    # Requirement: "The backoff delays can be stored/tracked rather than actually sleeping."
                    # I should probably store the delay in the job state so it's visible.
                    self.jobs[job_id]["next_backoff"] = delay
                    return False
        ```
    *   Wait, the requirement says "If the processor raises an exception, retry up to 3 times".
    *   Does `process_job` handle the retry loop internally?
    *   Example usage:
        ```python
        success = queue.process_job("job1", fetch_url)
        ```
    *   This implies `process_job` does the work (including retries) and returns the final boolean. It shouldn't be called multiple times to simulate retries; it should handle the loop internally.
    *   Requirement 4: "Track retry count for each job". This implies state within the queue object.
    *   If `process_job` handles the loop internally, I don't need to persist `retry_count` between calls to `process_job` for the same job ID, because one call to `process_job` should either succeed or exhaust retries.
    *   However, if `process_job` is called once per attempt (e.g. triggered by a scheduler), then state must persist.
    *   Given the example `success = queue.process_job(...)`, it looks like `process_job` is the synchronous execution method that attempts the job (with internal retries).
    *   If it's synchronous execution with internal retries, `self.retries` inside `process_job` (local variable) is enough?
    *   But requirement 4 says "Track retry count for each job". This suggests persistence.
    *   Let's assume `process_job` executes the job *once* per call, but checks if retries remain. Or `process_job` runs the full retry loop.
    *   "If the processor raises an exception, retry up to 3 times". This sounds like an internal loop within `process_job`.
    *   "Track retry count for each job". If `process_job` handles the loop, the count is local. If I need to track it for visibility/debugging, I should store it in the queue state.
    *   Let's store `retry_count` in the queue's state so it can be inspected later.
    *   If `process_job` runs the loop internally, I just need to make sure I don't reset the count between retries.
    *   Let's interpret "Track retry count" as storing it in the `JobQueue` instance so it reflects the history of that job ID.

    *   Revised Plan:
        1.  `JobQueue` class.
        2.  `__init__`: `self.jobs`, `self.retry_counts`.
        3.  `add_job`: Store data, init retry count to 0.
        4.  `process_job`:
            *   Check if job exists.
            *   Check if max retries reached.
            *   Loop up to 3 retries (meaning 4 total attempts: 1 initial + 3 retries).
            *   Wait, "retry up to 3 times".
            *   Attempt 1: Initial.
            *   Fail -> Retry 1 (Wait 1s).
            *   Fail -> Retry 2 (Wait 2s).
            *   Fail -> Retry 3 (Wait 4s).
            *   Fail -> Stop.
            *   Total attempts: 4.
            *   If "retry up to 3 times" means max retries = 3.
            *   Implementation: `for attempt in range(1, 5):` (1 to 4).
            *   If `attempt > 1`: apply backoff.
            *   Store `retry_count` = `attempt - 1`.
            *   If exception:
                *   Update state `self.retry_counts[job_id] = attempt - 1`.
                *   If `attempt == 4`: return False.
                *   Else: continue.
            *   If success: return True.

    *   Wait, if I process it inside `process_job`, do I need to store the delay? "The backoff delays can be stored/tracked". I'll add a `backoff_delay` field to the job data or a separate metadata dict.
    *   Let's keep it simple. Store `retry_count` and `last_backoff`.

    *   Wait, one more interpretation: `process_job` is called *once* per job attempt. The queue manages the retry logic.
    *   If I run `process_job`, it returns a boolean.
    *   If I want to track retry count, I need to persist it in the `JobQueue` object.
    *   Okay, I will implement the retry loop inside `process_job` and persist the state.

    *   Wait, if `process_job` runs the loop, why would I call it multiple times?
    *   Maybe I want to simulate a long-running process where I call `process_job` and it returns immediately on failure, and I call it again later?
    *   "Retry up to 3 times". Usually, this means the logic handles the waiting/retrying.
    *   I will assume `process_job` handles the full retry logic internally for the given job, but persists the count so it can be queried.
    *   Actually, if `process_job` runs the loop, it returns the final result. The state is updated during the loop.
    *   Let's ensure the code reflects the requirements strictly.

    *   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, int] = {}

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

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

                max_retries = 3
                # Total attempts = 1 initial + max_retries
                for attempt in range(max_retries + 1):
                    try:
                        processor(self.jobs[job_id])
                        return True
                    except Exception:
                        if attempt < max_retries:
                            retry_index = attempt
                            delay = [1, 2, 4][retry_index]
                            self.backoff_delays[job_id] = delay
                            self.retry_counts[job_id] = retry_index + 1
                        else:
                            self.retry_counts[job_id] = max_retries
                            return False
                return False
        ```
    *   Wait, logic check:
        *   Attempt 0 (Initial): Fail.
        *   retry_index = 0. delay = 1. retry_count = 1.
        *   Attempt 1 (Retry 1): Fail.
        *   retry_index = 1. delay = 2. retry_count = 2.
        *   Attempt 2 (Retry 2): Fail.
        *   retry_index = 2. delay = 4. retry_count = 3.
        *   Attempt 3 (Retry 3): Fail.
        *   attempt < 3 is False. retry_count = 3. Return False.
        *   This matches "retry up to 3 times" (3 retries) and the 3 backoff values (1, 2, 4).
        *   Wait, `retry_index` calculation:
            *   Range `range(4)` -> 0, 1, 2, 3.
            *   If fail at 0: retry_index = 0. delay = backoff[0] (1).
            *   If fail at 1: retry_index = 1. delay = backoff[1] (2).
            *   If fail at 2: retry_index = 2. delay = backoff[2] (4).
            *   If fail at 3: No delay (exhausted).
            *   This works.

    *   Wait, `self.retry_counts[job_id] = retry_index + 1`.
        *   At attempt 0 (fail): retry_index = 0. Count = 1.
        *   At attempt 1 (fail): retry_index = 1. Count = 2.
        *   At attempt 2 (fail): retry_index = 2. Count = 3.
        *   At attempt 3 (fail): Count = 3.
    *   This seems correct.

    *   One detail: "The backoff delays can be stored/tracked rather than actually sleeping."
    *   I'm storing `self.backoff_delays[job_id]`.
    *   I should probably not actually `sleep`.
    *   I need to make sure I don't import `time` if I'm not using it, but `time` isn't used.

    *   Wait, I need to check if `process_job` is supposed to handle the loop or just execute once.
    *   "Extend the job queue with retry logic... Add a `process_job` method... If the processor raises an exception, retry up to 3 times".
    *   This implies `process_job` contains the retry logic.
    *   Okay, the plan holds.

    *   One small correction: `retry_counts` update logic.
    *   If I succeed on attempt 2, `retry_counts` should probably reflect how many retries happened before success.
    *   In my code above:
        *   Attempt 0 fail -> count 1.
        *   Attempt 1 success -> returns True. Count stays 1.
    *   This is acceptable.

    *   Wait, `range(max_retries + 1)` is `range(4)` -> 0, 1, 2, 3.
    *   If attempt 0 fails:
        *   `if 0 < 3`: True. `retry_index = 0`. `delay = 1`. `count = 1`.
        *   Loop continues to attempt 1.
    *   If attempt 1 fails:
        *   `if 1 < 3`: True. `retry_index = 1`. `delay = 2`. `count = 2`.
        *   Loop continues to attempt 2.
    *   If attempt 2 fails:
        *   `if 2 < 3`: True. `retry_index = 2`. `delay = 4`. `count = 3`.
        *   Loop continues to attempt 3.
    *   If attempt 3 fails:
        *   `if 3 < 3`: False. `count = 3`. Return False.
    *   This looks correct.

    *   Wait, the requirement says "Track retry count for each job".
    *   In the success case (e.g. attempt 1), the count is 1.
    *   In the fail case (attempt 3), the count is 3.
    *   This works.

    *   Imports: `from typing import Callable, Dict, Any`.

    *   Final check on code style: Clean, no comments needed (mostly), just code.

    *   Wait, I need to make sure `backoff_delays` is updated correctly.
    *   In the loop, `self.backoff_delays[job_id] = delay` happens before the next attempt.
    *   If it succeeds, the last stored delay is the one that would have been used for the next retry.
    *   This seems reasonable for "tracking".

    *   Wait, one edge case: `add_job` is called. Then `process_job`.
    *   If `process_job` is called again for the same job (maybe after fixing something), the retry count should reset?
    *   The example doesn't show a reset mechanism. Usually, a job is processed once.
    *   But if I call `process_job` again, should it reset retries?
    *   Given "Track retry count for each job", it implies persistence. If I call it again, does it mean "try again"?
    *   If I interpret "retry up to 3 times" as a property of the `process_job` execution (i.e., one call to `process_job` handles the retries), then the job is done after that call.
    *   If I interpret it as "The job can be retried up to 3 times across multiple calls", then I need to persist the count.
    *   My code persists the count. If `process_job` is called again, it will see the count might be high.
    *   However, if `process_job` handles the loop internally, calling it twice for the same job is weird (why retry manually if it retries internally?).
    *   But to satisfy "Track retry count for each job", I must store it in the class state.
    *   I will stick to the internal loop implementation because it fits the `process_job` signature best (returns bool).

    *   Wait, I should check if `retry_counts` needs to be initialized to 0 in `add_job`. Yes.
    *   And `backoff_delays` to 0.

    *   One nuance: "If the processor raises an exception, retry up to 3 times".
    *   Does this mean 3 *retries* (4 attempts) or 3 *attempts* (2 retries)?
    *   Backoff list: 1, 2, 4. (3 items).
    *   If I have 3 backoff intervals, I can retry 3 times.
    *   Attempt 1 -> Fail -> Wait 1 -> Attempt 2 -> Fail -> Wait 2 -> Attempt 3 -> Fail -> Wait 4 -> Attempt 4 -> Fail.
    *   This matches "Retry 3 times".
    *   My code `range(4)` (0 to 3) covers 4 attempts.
    *   Wait, `for attempt in range(max_retries + 1)`: `max_retries = 3`. `range(4)`.
    *   This allows 4 attempts.
    *   Wait, if `max_retries` means "retry count", then 3 retries means 3 *additional* attempts.
    *   So Total = 1 + 3 = 4.
    *   My code does 4 attempts.
    *   Backoff logic:
        *   Fail at 0 -> Wait 1 (Retry 1).
        *   Fail at 1 -> Wait 2 (Retry 2).
        *   Fail at 2 -> Wait 4 (Retry 3).
        *   Fail at 3 -> No wait (Exhausted).
    *   This matches the 3 backoff values.
    *   So `max_retries = 3` is correct.

    *   Wait, I should check the `process_job` signature. `job_id: str, processor: Callable`.
    *   Processor takes `data`. `self.jobs[job_id]` is the data.
    *   The example shows `fetch_url(data)`. `data` is `{"url": "..."}`.
    *   So `processor(self.jobs[job_id])` is correct.

    *   Final check on imports. `typing` is needed for type hints.

    *   Wait, `backoff_delays` update:
    *   If I fail at attempt 0, I set delay to 1.
    *   If I then fail at attempt 1, I set delay to 2.
    *   If I succeed at attempt 1, the delay remains 2 (from the previous step). This is fine.
    *   I'll just implement it.

```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, int] = {}

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

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

        max_retries = 3
        for attempt in range(max_retries + 1):
            try:
                processor(self.jobs[job_id])
                return True
            except Exception:
                if attempt < max_retries:
                    retry_index = attempt
                    delay = [1, 2, 4][retry_index]
                    self.backoff_delays[job_id] = delay
                    self.retry_counts[job_id] = retry_index + 1
                else:
                    self.retry_counts[job_id] = max_retries
                    return False
        return False
```
Wait, one logic error in the loop.
If `attempt` is 0 (first try):
`except` block:
`if 0 < 3`: True.
`retry_index` = 0.
`delay` = 1.
`retry_counts` = 1.
Loop continues to `attempt` = 1.
If `attempt` is 1:
`except` block:
`if 1 < 3`: True.
`retry_index` = 1.
`delay` = 2.
`retry_counts` = 2.
Loop continues to `attempt` = 2.
If `attempt` is 2:
`except` block:
`if 2 < 3`: True.
`retry_index` = 2.
`delay` = 4.
`retry_counts` = 3.
Loop continues to `attempt` = 3.
If `attempt` is 3:
`except` block:
`if 3 < 3`: False.
`retry_counts` = 3.
Return False.

This logic is correct for "3 retries" (4 total attempts).
The `backoff_delays` will store the delay that *would* be applied before the next attempt.
This satisfies "backoff delays can be stored/tracked".

One thing: `process_job` returns `False` if all retries exhausted.
The example `success = queue.process_job(...)` expects a boolean.
The code looks solid.

One small detail: `self.retry_counts[job_id] = retry_index + 1`.
If `attempt` is 0 (initial fail), `retry_index` is 0. `retry_counts` becomes 1.
This means "1 retry happened".
Wait, if I fail on the first try, I am about to retry.
So "1 retry" is pending.
If I fail on the second try (`attempt` 1), I am about to retry again.
So "2 retries" are pending/used.
This seems semantically okay. The requirement says "Track retry count".
Usually, retry count means how many retries have been performed.
If I succeed on the 2nd attempt (`attempt` 1), `retry_counts` is 1. This means 1 retry was needed.
If I fail on the 4th attempt (`attempt` 3), `retry_counts` is 3. This means 3 retries were needed.
This seems consistent.

One optimization: `delay` list is hardcoded.
`backoff_delays` stores the value.
Looks good.

One final check on imports. `Callable` is needed.
I will ensure the code is complete and runnable.

One detail: `from typing import ...`.
The prompt asks for "Write only the Python code".
I should include imports.

```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, int] = {}

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

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

        max_retries = 3
        for attempt in range(max_retries + 1):
            try:
                processor(self.jobs[job_id])
                return True
            except Exception:
                if attempt < max_retries:
                    retry_index = attempt
                    delay = [1, 2, 4][retry_index]
                    self.backoff_delays[job_id] = delay
                    self.retry_counts[job_id] = retry_index + 1
                else:
                    self.retry_counts[job_id] = max_retries
                    return False
        return False
```
Double check `range(max_retries + 1)`.
`max_retries` = 3. `range(4)` -> 0, 1, 2, 3.
If `attempt` = 3 (4th attempt), `if 3 < 3` is False.
It sets `retry_counts` to 3 and returns False.
Correct.

Wait, if I succeed on `attempt` 0.
It returns True. `retry_counts` remains 0 (initialized).
This implies 0 retries used.
If I succeed on `attempt` 1.
It returns True. `retry_counts` is 1 (set in `attempt` 0 except block).
This implies 1 retry used.
This is correct.

Wait, `retry_counts` update happens in the `except` block.
If `attempt` 0 fails:
`retry_counts` set to 1.
Next iter `attempt` 1.
If `attempt` 1 succeeds:
Returns True.
`retry_counts` is 1.
This indicates 1 retry was attempted before success.
This is accurate.

One detail: `self.jobs[job_id]` access.
If `job_id` is not in `self.jobs`, returns False.
The example `queue.add_job(...)` ensures it exists.
Code handles missing job gracefully.

Final check on "no explanations". Just code.
I will remove comments to be safe, or keep them minimal if they explain logic.
The prompt says "Write only the Python code, no explanations."
I will provide the code block.

One thing: `delay` list indexing.
`[1, 2, 4][retry_index]`
`retry_index` comes from `attempt`.
Attempt 0 -> index 0 -> 1.
Attempt 1 -> index 1 -> 2.
Attempt 2 -> index 2 -> 4.
Attempt 3 -> `if` fails, no delay stored.
This matches "1s, 2s, 4s" for the 3 retries.
Correct.

Wait, `retry_index` is `attempt` inside the loop.
In the first iteration (attempt 0):
Fail -> `retry_index` = 0.
`delay` = 1.
This is the backoff *before* the next attempt (attempt 1).
Correct.

Wait, `self.retry_counts[job_id] = retry_index + 1`.
If attempt 0 fails: `retry_counts` = 1.
If attempt 1 fails: `retry_counts` = 2.
If attempt 2 fails: `retry_counts` = 3.
This tracks how many retries have been triggered.
Correct.

One minor thing: `processor` signature.
The example `def fetch_url(data):`.
So `processor` takes the job data.
`processor(self.jobs[job_id])` matches.

Okay, ready to write.

```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, int] = {}

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

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

        max_retries = 3
        for attempt in range(max_retries + 1):
            try:
                processor(self.jobs[job_id])
                return True
            except Exception:
                if attempt < max_retries:
                    retry_index = attempt
                    delay = [1, 2, 4][retry_index]
                    self.backoff_delays[job_id] = delay
                    self.retry_counts[job_id] = retry_index + 1
                else:
                    self.retry_counts[job_id] = max_retries
                    return False
        return False
```
Wait, I need to make sure I don't use `time.sleep` since requirement says "backoff delays can be stored/tracked rather than actually sleeping".
My code doesn't use `time`.
It stores delays in `self.backoff_delays`.
This meets the requirement.

One check on "Write only the Python code".
I should include the imports.
I should not include markdown code block markers if the instruction implies raw text, but usually markdown code blocks are expected for "Python code". I will use standard markdown code block.
Wait, "Write only the Python code, no explanations." usually means no text outside the code block.
I will output the code block.

One more check: `retry_counts` initialization.
In `add_job`, I init to 0.
If `process_job` is called multiple times for the same job (e.g. manual retry), the count persists.
But `process_job` handles the loop.
If I call `process_job` again, it starts from `attempt` 0.
But `retry_counts` is already > 0.
Does `process_job` check `retry_counts` before starting?
My code does `for attempt in range(max_retries + 1)`. It doesn't check `self.retry_counts[job_id]` to skip attempts.
It just runs the loop.
If I call `process_job` again after a full failure, `attempt` goes 0..3 again.
It will overwrite `backoff_delays` and `retry_counts` with the new run's history.
This is acceptable behavior for "process_job" as a single execution unit.
If the requirement meant "Resume from last retry", I'd need to track `last_attempt`.
But "retry up to 3 times" usually implies the logic handles it in one go.
Given the example `success = queue.process_job(...)`, it implies a single call executes the logic.
So the state reset inside `process_job` (via loop) is fine.
The state tracking (`self.retry_counts`) is for visibility.
The code is good.

Wait, I should check if `self.retry_counts[job_id] = retry_index + 1` is correct for the final failure case.
In the `else` block (attempt 3):
`self.retry_counts[job_id] = max_retries` (3).
This is consistent with the `retry_index + 1` logic (attempt 2 -> index 2 -> count 3).
So the final count is 3.
This is consistent.

I'll finalize the code.

</reasoning_content>

<content>
```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, int] = {}

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

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

        max_retries = 3
        for attempt in range(max_retries + 1):
            try:
                processor(self.jobs[job_id])
                return True
            except Exception:
                if attempt < max_retries:
                    retry_index = attempt
                    delay = [1, 2, 4][retry_index]
                    self.backoff_delays[job_id] = delay
                    self.retry_counts[job_id] = retry_index + 1
                else:
                    self.retry_counts[job_id] = max_retries
                    return False
        return False
```
</content>