Here's the complete refactored package structure. I've used **relative imports** to avoid circular dependencies and shadowing Python's built-in `queue` module, while strictly adhering to your target layout and single-responsibility principle.

### `queue/__init__.py`
```python
"""
Job Queue Package
Public API: Job, JobQueue
"""
from .core import Job, JobQueue
from .retry import process_with_retry

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

### `queue/core.py`
```python
"""
Core module: Job data model and main queue orchestration.
"""
from __future__ import annotations
import uuid
from dataclasses import dataclass, field
from typing import Any, Optional
from datetime import datetime

from .priority import PriorityQueue

@dataclass
class Job:
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    payload: Any = None
    priority: int = 0
    status: str = "pending"
    created_at: datetime = field(default_factory=datetime.now)
    retries: int = 0

class JobQueue:
    """Orchestrates job lifecycle and delegates ordering to PriorityQueue."""
    def __init__(self) -> None:
        self._queue: PriorityQueue = PriorityQueue()
        self._jobs_by_id: dict[str, Job] = {}

    def add(self, job: Job) -> None:
        job.status = "pending"
        self._queue.push(job, job.priority)
        self._jobs_by_id[job.id] = job

    def get(self) -> Optional[Job]:
        job = self._queue.pop()
        if job:
            job.status = "processing"
        return job

    def complete(self, job: Job) -> None:
        if job.id in self._jobs_by_id:
            job.status = "completed"

    def fail(self, job: Job) -> None:
        if job.id in self._jobs_by_id:
            job.status = "failed"

    def __len__(self) -> int:
        return len(self._queue)

    def is_empty(self) -> bool:
        return len(self._queue) == 0
```

### `queue/priority.py`
```python
"""
Priority queue implementation using heapq with FIFO tie-breaking.
"""
import heapq
from typing import Any, Optional

class PriorityQueue:
    def __init__(self) -> None:
        # Tuple structure: (priority, insertion_counter, item)
        self._heap: list[tuple[int, int, Any]] = []
        self._counter = 0

    def push(self, item: Any, priority: int) -> None:
        """Add item with given priority. Lower number = higher priority."""
        entry = (priority, self._counter, item)
        self._counter += 1
        heapq.heappush(self._heap, entry)

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

    def peek(self) -> Optional[Any]:
        """Return highest priority item without removing it."""
        if self._heap:
            return self._heap[0][2]
        return None

    def __len__(self) -> int:
        return len(self._heap)

    def __bool__(self) -> bool:
        return bool(self._heap)
```

### `queue/retry.py`
```python
"""
Retry logic with exponential backoff.
"""
import time
from typing import Callable, Any
from .core import Job

def process_with_retry(
    job: Job,
    processor: Callable[[Job], Any],
    max_retries: int = 3,
    base_delay: float = 1.0
) -> Any:
    """
    Execute a job processor with exponential backoff retry logic.
    Updates job status and retry count accordingly.
    """
    last_exception = None

    for attempt in range(max_retries + 1):
        try:
            result = processor(job)
            job.status = "completed"
            job.retries = attempt
            return result
        except Exception as e:
            last_exception = e
            job.retries = attempt + 1

            if attempt < max_retries:
                delay = base_delay * (2 ** attempt)
                time.sleep(delay)
            else:
                job.status = "failed"

    raise last_exception
```

### Key Design Decisions:
1. **Relative Imports**: Using `from .module import ...` prevents circular dependencies and avoids name collisions with Python's built-in `queue` module.
2. **Single Responsibility**: 
   - `core.py` handles domain model (`Job`) and high-level queue orchestration
   - `priority.py` is a generic, reusable heap-based data structure
   - `retry.py` encapsulates only the retry/backoff policy
3. **FIFO Tie-Breaking**: `PriorityQueue` uses a monotonic counter to ensure jobs with equal priority are processed in insertion order.
4. **Clean Public API**: `__init__.py` exposes exactly what consumers need while keeping internal implementations hidden.

### Usage Example:
```python
from queue import Job, JobQueue, process_with_retry

q = JobQueue()
q.add(Job(payload="task_1", priority=1))
q.add(Job(payload="task_2", priority=0))  # Higher priority

job = q.get()
process_with_retry(job, lambda j: print(f"Processing {j.payload}"))
q.complete(job)
```