Below is the refactored package structure. This design ensures a single responsibility per module, avoids circular imports by keeping `PriorityQueue` generic (it doesn't need to know about the `Job` class, just the priority attribute), and provides a clean public API.

```text
queue/
├── __init__.py
├── core.py
├── priority.py
└── retry.py
```

### 1. `queue/__init__.py`
This file defines the public API. It exposes the main classes and functions users need while hiding internal implementation details.

```python
from .core import Job, JobQueue
from .retry import process_with_retry

__all__ = ["Job", "JobQueue", "process_with_retry"]
```

### 2. `queue/core.py`
This module contains the `Job` dataclass and the main `JobQueue` logic. It imports `PriorityQueue` from the sibling module to handle ordering.

```python
from dataclasses import dataclass, field
from typing import Any, Optional
from .priority import PriorityQueue

@dataclass
class Job:
    """Represents a task to be executed."""
    id: str
    payload: Any
    priority: int = 0
    status: str = "pending"
    created_at: float = field(default_factory=field)

    def __post_init__(self):
        if self.created_at is field:
            import time
            self.created_at = time.time()

class JobQueue:
    """High-level interface for managing jobs using a priority queue."""
    
    def __init__(self):
        # Initialize the underlying priority storage
        self._queue: PriorityQueue = PriorityQueue()

    def enqueue(self, job: Job) -> None:
        """Add a job to the queue."""
        self._queue.push(job)

    def dequeue(self) -> Optional[Job]:
        """Remove and return the highest priority job."""
        return self._queue.pop()

    def peek(self) -> Optional[Job]:
        """Return the highest priority job without removing it."""
        return self._queue.peek()

    def is_empty(self) -> bool:
        """Check if the queue has no jobs."""
        return self._queue.is_empty()

    def size(self) -> int:
        """Return the number of jobs in the queue."""
        return self._queue.size()
```

### 3. `queue/priority.py`
This module implements the heap-based logic. It is generic and does not import `Job` to prevent circular dependencies with `core.py`. It relies on a `key` function to determine order.

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

class PriorityQueue:
    """Generic priority queue implementation using a min-heap."""
    
    def __init__(self):
        self._heap: list = []
        self._counter: int = 0  # For stable sorting of equal priorities

    def push(self, item: Any, key: Callable = lambda x: x) -> None:
        """Push an item with the given key priority."""
        # Tuple: (priority, counter, item) to ensure stability and correct ordering
        heapq.heappush(self._heap, (key(item), self._counter, item))
        self._counter += 1

    def pop(self) -> Optional[Any]:
        """Pop the item with the highest priority."""
        if not self._heap:
            return None
        _, _, item = heapq.heappop(self._heap)
        return item

    def peek(self) -> Optional[Any]:
        """Peek at the item with the highest priority."""
        if not self._heap:
            return None
        return self._heap[0][2]

    def is_empty(self) -> bool:
        """Check if the queue is empty."""
        return len(self._heap) == 0

    def size(self) -> int:
        """Return the number of items in the queue."""
        return len(self._heap)
```

### 4. `queue/retry.py`
This module handles the execution logic with exponential backoff. It imports `Job` from `core` to access job metadata for logging.

```python
import time
from typing import Callable, Any, Optional
from .core import Job

def process_with_retry(
    job: Job,
    handler: Callable[[Job], Any],
    max_retries: int = 3,
    base_delay: float = 1.0,
    max_delay: float = 30.0
) -> bool:
    """
    Process a job with exponential backoff retry logic.
    
    Args:
        job: The job to process.
        handler: The function to execute the job payload.
        max_retries: Maximum number of retry attempts.
        base_delay: Initial delay in seconds.
        max_delay: Maximum delay cap in seconds.
        
    Returns:
        True if successful, False if all retries exhausted.
    """
    last_error = None
    
    for attempt in range(max_retries + 1):
        try:
            return handler(job)
        except Exception as e:
            last_error = e
            if attempt == max_retries:
                break
            
            # Calculate exponential backoff
            delay = min(base_delay * (2 ** attempt), max_delay)
            time.sleep(delay)
            print(f"Job {job.id} failed (attempt {attempt + 1}): {e}. Retrying in {delay}s...")
            
    return False
```

### Key Refactoring Decisions

1.  **Circular Import Prevention**: `core.py` imports `PriorityQueue` from `priority.py`, but `priority.py` does *not* import `Job`. Instead, `PriorityQueue` accepts a generic `key` function. This allows `JobQueue` (in `core`) to pass `lambda x: x.priority` without `priority.py` needing to know the internal structure of `Job`.
2.  **Single Responsibility**:
    *   `core.py`: Defines data (`Job`) and orchestration (`JobQueue`).
    *   `priority.py`: Defines storage mechanics (Heap).
    *   `retry.py`: Defines execution logic (Backoff).
3.  **Clean API**: `__init__.py` only exposes what a user typically needs (`Job`, `JobQueue`, `process_with_retry`), keeping `PriorityQueue` available for advanced internal use if necessary but not cluttering the top-level namespace.
4.  **Data Class**: `Job` uses `@dataclass` for readability and standard attribute access.