# Agent Reach Channel Implementation Contract: Required Methods for Custom Channels

> Implement custom channels in Agent Reach. Learn required methods like can_handle and check for your channel implementation contract.

- Repository: [Pnant/Agent-Reach](https://github.com/Panniantong/Agent-Reach)
- Tags: api-reference
- Published: 2026-06-22

---

**To implement a channel in Agent Reach, inherit from the `Channel` abstract base class in [`agent_reach/channels/base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/base.py), define the class attributes `name`, `description`, `backends`, and `tier`, implement the abstract `can_handle()` method, and override `check()` to probe backends and set `self.active_backend` while returning a status tuple.**

Agent Reach treats every supported Internet platform as a *channel* that discovers and interacts with URLs. By adhering to the Agent Reach channel implementation contract, developers ensure their custom channels integrate seamlessly with the core routing logic and pass the automated validation suite.

## The Channel Base Class and Required Attributes

All channel classes must inherit from the abstract base class `Channel` located at [`agent_reach/channels/base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/base.py). The contract enforces specific class-level and instance-level attributes that describe the channel and its operational state.

### Required Class Attributes

Every concrete channel must define four class attributes (lines 32–36 of [`base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/base.py)):

- **`name`** (`str`): A unique identifier for the channel.
- **`description`** (`str`): A human-readable explanation of the platform.
- **`backends`** (`List[str]`): A list of supported backend names (e.g., `["mycli", "OpenCLI"]`).
- **`tier`** (`int`): An integer in `{0, 1, 2}` indicating setup complexity—`0` for zero-config, `1` for free key required, and `2` for full setup needed.

### The `active_backend` Instance Attribute

Channels must maintain an instance attribute named `active_backend`, initialized to `None` (lines 37–39 of [`base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/base.py)). The `check()` method **must** set this attribute to the string name of the first usable backend, or leave it as `None` if no backends are available. This attribute is consumed by the routing logic in [`agent_reach/core.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/core.py) to determine which tool handles a given URL.

## Required Methods for Channel Implementation

The contract requires implementations for `can_handle()` and `check()`, while `ordered_backends()` is typically inherited unchanged.

### `can_handle(url: str) -> bool`

This **abstract method** (lines 40–44 of [`base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/base.py)) determines whether a given URL belongs to the channel’s platform. It must return `True` if the domain or path matches the platform (e.g., checking if `x.com` is in the URL for Twitter).

Example implementation pattern from [`agent_reach/channels/twitter.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/twitter.py):

```python
from urllib.parse import urlparse

def can_handle(self, url: str) -> bool:
    domain = urlparse(url).netloc.lower()
    return "x.com" in domain or "twitter.com" in domain

```

### `check(config=None) -> Tuple[str, str]`

While the base class provides a concrete implementation (lines 61–70 of [`base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/base.py)), channels **must override** this method to probe their specific backends. The method must:

1. Accept an optional `config` dictionary.
2. Return a tuple of `(status, message)` where `status` is one of `"ok"`, `"warn"`, `"off"`, or `"error"`.
3. Set `self.active_backend` to the first working backend name (or `None`).

The method should iterate over `self.ordered_backends(config)` to respect user overrides.

### `ordered_backends(config=None) -> List[str]`

This helper method (lines 45–60 of [`base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/base.py)) returns the `backends` list reordered according to a user-configured preference (e.g., `<channel>_backend`). Channels typically inherit this implementation without modification. It ensures that if a user specifies a preferred backend, it moves to the front of the list.

## Test Suite Validation

The contract is enforced by [`tests/test_channel_contracts.py`](https://github.com/Panniantong/Agent-Reach/blob/main/tests/test_channel_contracts.py), which contains specific test cases that every channel must pass:

- **`test_channel_registry_contract`**: Verifies that `name`, `description`, `backends` are non-empty and `tier` is valid (lines 15–25).
- **`test_channel_check_contract_with_minimal_runtime`**: Ensures `check()` returns a valid status string and non-empty message even when external tools are missing (lines 28–36).
- **`test_channel_active_backend_attribute_contract`** and **`test_channel_active_backend_set_by_check`**: Confirm that `active_backend` exists, defaults to `None`, and is set to a string or `None` after `check()` runs (lines 39–70).
- **`test_ordered_backends_contract`** and **`test_ordered_backends_override_moves_backend_to_front`**: Validate that `ordered_backends` returns a permutation of `backends` and respects configuration overrides (lines 72–99).

## Practical Implementation Example

Below is a minimal skeleton demonstrating the contract requirements. This pattern mirrors the production implementation in [`agent_reach/channels/twitter.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/twitter.py):

```python
from .base import Channel
from agent_reach.probe import probe_command

class MyPlatformChannel(Channel):
    name = "myplatform"
    description = "MyPlatform – posts & comments"
    backends = ["mycli", "OpenCLI"]
    tier = 1  # 1 = needs free key

    def can_handle(self, url: str) -> bool:
        from urllib.parse import urlparse
        domain = urlparse(url).netloc.lower()
        return "myplatform.com" in domain

    def check(self, config=None):
        self.active_backend = None
        findings = []
        
        for backend in self.ordered_backends(config):
            if backend == "mycli":
                result = self._check_mycli()
            else:
                result = self._check_opencli()
            
            if result:
                findings.append((backend, *result))
        
        # Return first ok/warn, otherwise error/off

        for wanted in ("ok", "warn"):
            for backend, status, msg in findings:
                if status == wanted:
                    self.active_backend = backend
                    return status, msg
        return "off", "No MyPlatform back‑ends installed"
    
    def _check_mycli(self):
        probe = probe_command("mycli", ["status"], timeout=10, retries=1, package="mycli")
        if probe.status == "missing":
            return None
        if probe.ok:
            return "ok", "mycli is ready"
        return "warn", "mycli installed but not authenticated"

```

For complete reference implementations, examine the source files:
- [`agent_reach/channels/twitter.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/twitter.py) for multi-backend probing logic.
- [`agent_reach/channels/reddit.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/reddit.py) for mixing custom and generic OpenCLI backends.
- [`agent_reach/channels/youtube.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/youtube.py) for handling external binaries like `yt-dlp`, `node`, and `deno`.

## Summary

- Inherit from the `Channel` ABC defined in [`agent_reach/channels/base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/base.py).
- Define `name`, `description`, `backends`, and `tier` as class attributes.
- Initialize and set `self.active_backend` within the `check()` method.
- Implement `can_handle(url: str) -> bool` to filter URLs for your platform.
- Override `check(config=None)` to return a tuple of `("ok"|"warn"|"off"|"error", message)` and set the active backend.
- Use `ordered_backends(config)` to iterate backends in priority order.
- Ensure compliance by running [`tests/test_channel_contracts.py`](https://github.com/Panniantong/Agent-Reach/blob/main/tests/test_channel_contracts.py).

## Frequently Asked Questions

### What happens if I don't set `active_backend` in the `check()` method?

The test suite will fail `test_channel_active_backend_set_by_check`, and the core routing logic in [`agent_reach/core.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/core.py) will not be able to select your channel for processing URLs, effectively disabling the channel at runtime.

### Can I add extra methods to my channel class beyond the required contract?

Yes. The contract specifies only the required interface. You may add private helper methods (like `_check_mycli()` in the example) or public utilities, provided you do not override required methods with incompatible signatures.

### How does the `tier` attribute affect channel behavior?

The `tier` attribute is informational and used by diagnostic tools to indicate setup complexity. It does not change runtime logic, but users and the test suite expect valid values of `0`, `1`, or `2`, where `0` requires zero configuration, `1` requires a free API key, and `2` requires full manual setup.

### Where is the channel selection logic that uses `can_handle()`?

The core routing logic resides in [`agent_reach/core.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/core.py). This module iterates over all registered channels (retrieved via `get_all_channels()`), calls `can_handle()` on each to find a matching platform, and then checks `active_backend` to determine if the channel can process the request.