# How to Add a Custom Channel or Backend to Agent Reach: A Complete Developer Guide

> Learn to add a custom channel or backend to Agent Reach by subclassing 'Channel', implementing key methods, and registering your integration. A complete developer guide.

- Repository: [Pnant/Agent-Reach](https://github.com/Panniantong/Agent-Reach)
- Tags: how-to-guide
- Published: 2026-06-21

---

**To add a custom channel or backend to Agent Reach, subclass the abstract `Channel` base class in `agent_reach/channels/`, implement the `can_handle()` and `check()` methods, register the instance in `ALL_CHANNELS`, and optionally extend the `backends` list to support additional data retrieval tools.**

Agent Reach discovers internet platforms through pluggable channel classes that live in the `Panniantong/Agent-Reach` repository. Whether you need to integrate a new social media site or extend an existing channel with alternative CLI tools, the framework provides a consistent architecture based on abstract base classes and a central registry. This guide walks through the exact file locations, method signatures, and code patterns required to integrate custom platforms.

## Understanding the Channel Architecture

Agent Reach identifies platforms using three core components: the abstract base class, the channel registry, and contract tests.

### The Channel Base Class

The file [`agent_reach/channels/base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/base.py) defines the abstract **`Channel`** class that enforces a consistent interface across all platforms. Every subclass must declare the class attributes `name`, `description`, `backends`, and `tier`, plus implement **`can_handle(url: str) -> bool`** to identify platform-specific URLs and **`check(config=None) -> Tuple[str, str]`** to probe available backends and set `self.active_backend`.

### The Channel Registry

The [`agent_reach/channels/__init__.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/__init__.py) file maintains **`ALL_CHANNELS`**, a list containing every channel instance. This registry powers the `agent_reach.doctor` diagnostic tool and public API functions like `get_channel()` and `get_all_channels()`. The registry automatically imports every channel file in the directory, making manual registration mandatory for new additions.

### Contract Tests

The file [`tests/test_channel_contracts.py`](https://github.com/Panniantong/Agent-Reach/blob/main/tests/test_channel_contracts.py) enforces mandatory attributes and methods. Any new channel must pass these assertions; otherwise, the `agent-reach doctor` command will raise an assertion error indicating which required property is missing.

## Adding a New Custom Channel

Creating a custom channel follows four concrete steps: file creation, class implementation, registry registration, and contract validation.

### Step 1: Create the Channel File

Create a new Python file at `agent_reach/channels/<your_platform>.py`. Import the base class and define your channel subclass with the required attributes.

### Step 2: Implement Required Methods

Implement **`can_handle()`** to recognize your platform's URLs by inspecting the netloc or path. Implement **`check()`** to validate backend availability, set `self.active_backend` to the working backend name (or `None` for builtin channels), and return a tuple of `(status, message)`.

For builtin channels that require no external tools, set `backends = []` and return `"ok"` from `check()`:

```python

# agent_reach/channels/examplesite.py

from urllib.parse import urlparse
from .base import Channel

class ExampleSiteChannel(Channel):
    name = "examplesite"
    description = "ExampleSite – demo read-only platform"
    backends = []  # Builtin channel requires no external tools

    tier = 0       # Zero-config platform

    def can_handle(self, url: str) -> bool:
        return urlparse(url).netloc.lower().endswith("example.com")

    def check(self, config=None):
        self.active_backend = None
        return "ok", "built-in – no external tools required"

```

### Step 3: Register in the Channel Registry

Import the new class in [`agent_reach/channels/__init__.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/__init__.py) and append an instance to `ALL_CHANNELS`:

```python

# agent_reach/channels/__init__.py

from .examplesite import ExampleSiteChannel

ALL_CHANNELS: List[Channel] = [
    # ... existing channels ...

    ExampleSiteChannel(),
]

```

### Step 4: Validate with Contract Tests

Run the test suite to ensure your channel satisfies the contract:

```bash
pytest tests/test_channel_contracts.py -q

```

All assertions should pass, confirming that `name`, `description`, `backends`, `tier`, and `active_backend` are properly defined.

## Adding a New Backend to an Existing Channel

Many platforms support multiple data retrieval methods. Extend existing channels by modifying the `backends` list and updating the probing logic in `check()`.

### Extending the Backends List

Append the new backend name to the channel's `backends` attribute, preserving order of preference (preferred first). The **`ordered_backends()`** method in the base class automatically respects user overrides from config files using the pattern `<channel_name>_backend: <backend_name>`.

### Implementing Backend Probing

Update the `check()` method to probe the new backend before falling back to existing ones. Use **`probe_command()`** from `agent_reach.probe` to test CLI availability, and set **`self.active_backend`** to the first successful candidate.

Here is an example extending the YouTube channel with a hypothetical `myyt` CLI:

```python

# agent_reach/channels/youtube.py

from agent_reach.probe import probe_command
from .base import Channel

class YouTubeChannel(Channel):
    name = "youtube"
    description = "YouTube videos and subtitles"
    backends = ["myyt", "yt-dlp"]  # New preferred backend first

    tier = 0

    def can_handle(self, url: str) -> bool:
        # Existing URL matching logic...

        return "youtube.com" in url or "youtu.be" in url

    def check(self, config=None):
        # Probe new backend first

        probe = probe_command("myyt", ["--version"], timeout=10, package="myyt")
        if probe.status == "ok":
            self.active_backend = "myyt"
            return "ok", "myyt CLI available"
        
        # Fallback to yt-dlp

        probe = probe_command("yt-dlp", ["--version"], timeout=10, package="yt-dlp")
        if probe.status != "ok":
            self.active_backend = None
            return "off", "yt-dlp not installed"
        
        self.active_backend = "yt-dlp"
        return "ok", "yt-dlp available"

```

## Full Implementation Example: Multi-Backend Platform

For a complete real-world scenario, consider adding "FooTalk", a platform supporting both a native CLI and a generic browser-based backend:

```python

# agent_reach/channels/footalk.py

from agent_reach.probe import probe_command
from agent_reach.backends import opencli_status, opencli_summary
from .base import Channel

class FooTalkChannel(Channel):
    name = "footalk"
    description = "FooTalk – micro-blogging platform"
    backends = ["footalk-cli", "opencli"]  # Native CLI preferred, fallback to OpenCLI

    tier = 1  # Requires login/key handled by CLI

    def can_handle(self, url: str) -> bool:
        from urllib.parse import urlparse
        return urlparse(url).netloc.lower().endswith("footalk.com")

    def check(self, config=None):
        # Try native CLI first

        probe = probe_command("footalk-cli", ["--version"], timeout=10, package="footalk-cli")
        if probe.status == "ok":
            self.active_backend = "footalk-cli"
            return "ok", "footalk-cli available"
        
        # Fallback to OpenCLI

        st = opencli_status()
        if st.ready:
            self.active_backend = "opencli"
            return "ok", opencli_summary(st)
        
        # Nothing available

        self.active_backend = None
        return "off", "Neither footalk-cli nor OpenCLI detected"

```

Register this channel in [`agent_reach/channels/__init__.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/__init__.py) as shown previously. The `agent-reach doctor` command will now display the active backend, and agents can invoke the appropriate CLI directly.

## Testing and Validation

After implementation, verify your integration:

1. **Run contract tests**: `pytest tests/test_channel_contracts.py -q` ensures all required attributes exist.
2. **Check doctor output**: `agent-reach doctor` should list your channel with the correct active backend.
3. **Test URL handling**: Verify `can_handle()` returns `True` for your platform URLs and `False` for others.

## Summary

- **Subclass `Channel`** from [`agent_reach/channels/base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/base.py) and implement `can_handle()` for URL recognition and `check()` for backend probing.
- **Define required attributes**: `name`, `description`, `backends` (ordered list), and `tier` (configuration complexity level).
- **Register the channel** by importing the class in [`agent_reach/channels/__init__.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/__init__.py) and appending an instance to `ALL_CHANNELS`.
- **Add new backends** by extending the `backends` list and probing each candidate in `check()`, setting `self.active_backend` to the first successful option.
- **Validate** using `pytest tests/test_channel_contracts.py` before running `agent-reach doctor`.

## Frequently Asked Questions

### What is the minimum required code to add a custom channel to Agent Reach?

At minimum, create a file in `agent_reach/channels/`, subclass `Channel`, define `name`, `description`, `backends`, and `tier`, implement `can_handle()` to return `True` for your platform URLs, and implement `check()` to set `self.active_backend` and return a status tuple. Finally, import and instantiate the class in [`agent_reach/channels/__init__.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/__init__.py) and append it to `ALL_CHANNELS`.

### How does Agent Reach choose which backend to use for a channel?

The `check()` method probes backends in order of preference and sets `self.active_backend` to the first successful candidate. Users can override this via `~/.agent-reach/config.yaml` using the `<channel_name>_backend` key, which the `ordered_backends()` method respects when reordering the `backends` list.

### Can I add a backend to an existing channel without modifying the original source files?

While you must edit the channel's Python file to add backend logic, you should extend the existing channel class in your fork or modify the existing one directly. There is currently no plugin system for backends independent of channel files; all backend logic must be registered within the channel's `check()` method and `backends` list.

### What happens if my custom channel fails the contract tests?

The `agent-reach doctor` command will raise an assertion error indicating which required attribute or method is missing. Common failures include missing `name`, `description`, `backends`, or `tier` attributes, or failing to set `active_backend` in the `check()` method before returning.