How to Add a New Platform Channel for an Unsupported Website to Agent Reach

Adding a new platform channel to Agent Reach requires creating a Python subclass in agent_reach/channels/, implementing URL detection and health-check logic, and registering the class in the channel registry.

Agent Reach treats every supported platform as a channel—a thin wrapper that routes calls to upstream CLI tools or APIs. If you need to add support for a website that is not yet covered, you will extend the base architecture without modifying core upstream logic. This guide walks through the exact implementation pattern used in the Panniantong/Agent-Reach repository.

Understanding the Channel Architecture

Agent Reach channels follow a backend-agnostic pattern. Each channel declares a list of potential backends (such as platform-specific CLIs or generic OpenCLI), then probes them at runtime to find the first functional option. The base class in agent_reach/channels/base.py provides the abstract Channel class and the ordered_backends() helper method, while the global registry in agent_reach/channels/__init__.py exposes all channels via the ALL_CHANNELS list.

Key architectural constraints to remember:

  • Tier classification: Set tier = 0 for zero-config channels, tier = 1 for channels requiring a free API key, and tier = 2 for channels needing complex setup.
  • Backend delegation: Keep operational methods (read(), search(), transcribe()) thin—they should only route to the selected backend rather than implementing platform logic directly.
  • Health-check priority: The check() method iterates through self.ordered_backends() and selects the first backend reporting "ok" or "warn" status.

Step-by-Step Implementation Guide

Step 1: Create a Channel Subclass

Create a new file in agent_reach/channels/ (e.g., mysite.py). Subclass Channel from agent_reach/channels/base.py and declare the mandatory class attributes:

from agent_reach.channels.base import Channel

class MySiteChannel(Channel):
    name = "mysite"               # Short identifier used in CLI and config files

    description = "MySite – read and search content"
    backends = ["mytool-cli", "OpenCLI"]   # Ordered candidate backends

    tier = 1                      # Requires free API key

Step 2: Implement URL Detection with can_handle

Implement the can_handle(self, url: str) -> bool method to recognize URLs belonging to your platform. Use urllib.parse.urlparse to inspect the network location:

from urllib.parse import urlparse

def can_handle(self, url: str) -> bool:
    """Return True for URLs that belong to MySite."""
    netloc = urlparse(url).netloc.lower()
    return "mysite.com" in netloc or "mys.site" in netloc

Step 3: Configure Backend Health Checks

Override check(self, config=None) to probe each backend candidate. Follow the two-stage pattern used by existing multi-backend channels like Twitter or Reddit in agent_reach/channels/twitter.py:

  1. Iterate over self.ordered_backends(config).
  2. Probe each candidate using agent_reach.probe.probe_command or shutil.which combined with subprocess.run.
  3. Collect (backend, status, message) tuples.
  4. Set self.active_backend to the first backend with "ok" or "warn" status.
import shutil
from agent_reach.probe import probe_command

def check(self, config=None):
    """Probe backends and pick the first usable one."""
    self.active_backend = None
    findings = []

    for backend in self.ordered_backends(config):
        if backend == "mytool-cli":
            if not shutil.which("mytool-cli"):
                continue
            probe = probe_command("mytool-cli", ["--version"], package="mytool-cli")
            if probe.ok:
                findings.append((backend, "ok", "mytool-cli ready"))
            else:
                findings.append((backend, "warn", "installed but may need config"))
            continue

        if backend == "OpenCLI":
            from agent_reach.backends import opencli_status
            st = opencli_status()
            if not st.installed:
                continue
            status = "error" if st.broken else ("ok" if st.ready else "warn")
            findings.append((backend, status, st.hint))
            continue

    # Select best candidate

    for wanted in ("ok", "warn"):
        for backend, status, msg in findings:
            if status == wanted:
                self.active_backend = backend
                return status, msg

    return "off", "mytool-cli not installed. Install with `pip install mytool-cli`"

Step 4: Register the Channel

Import the new class in agent_reach/channels/__init__.py and append an instance to the ALL_CHANNELS list. This makes the channel discoverable via get_all_channels() and get_channel(name):

from .mysite import MySiteChannel          # Add this import

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

    MySiteChannel(),                       # Add the instance

]

Step 5: Add Tests and Documentation

Write unit tests in tests/test_channels.py verifying can_handle URL matching and check status reporting. Use the same monkey-patching style as the existing TwitterChannel test suite:

def test_mysite_can_handle():
    ch = MySiteChannel()
    assert ch.can_handle("https://www.mysite.com/article/123")
    assert not ch.can_handle("https://github.com/user/repo")

def test_mysite_check_off_when_missing(monkeypatch):
    monkeypatch.setattr(shutil, "which", lambda _: None)
    ch = MySiteChannel()
    status, msg = ch.check()
    assert status == "off"
    assert "mytool-cli not installed" in msg

Update docs/install.md to include the new channel name in the Supported channel names table.

Complete Working Example

Here is the full implementation for a fictional MySite channel following the Agent Reach patterns:

agent_reach/channels/mysite.py


# -*- coding: utf-8 -*-

"""MySite – example of adding a new platform channel."""

from urllib.parse import urlparse
import shutil
from agent_reach.probe import probe_command
from .base import Channel


class MySiteChannel(Channel):
    """Channel for the fictional MySite platform."""

    name = "mysite"
    description = "MySite – read and search content"
    backends = ["mytool-cli", "OpenCLI"]
    tier = 1

    def can_handle(self, url: str) -> bool:
        """Return True for URLs that belong to MySite."""
        netloc = urlparse(url).netloc.lower()
        return "mysite.com" in netloc or "mys.site" in netloc

    def check(self, config=None):
        """Probe backends and pick the first usable one."""
        self.active_backend = None
        findings = []

        for backend in self.ordered_backends(config):
            if backend == "mytool-cli":
                if not shutil.which("mytool-cli"):
                    continue
                probe = probe_command("mytool-cli", ["--version"], package="mytool-cli")
                if probe.status == "missing":
                    continue
                if probe.ok:
                    findings.append((backend, "ok", "mytool-cli ready"))
                else:
                    findings.append((backend, "warn", "mytool-cli installed but may need config"))
                continue

            if backend == "OpenCLI":
                from agent_reach.backends import opencli_status
                st = opencli_status()
                if not st.installed:
                    continue
                if st.broken:
                    findings.append((backend, "error", st.hint))
                elif st.ready:
                    findings.append((backend, "ok", st.hint))
                else:
                    findings.append((backend, "warn", st.hint))
                continue

        for wanted in ("ok", "warn"):
            for backend, status, msg in findings:
                if status == wanted:
                    self.active_backend = backend
                    return status, msg

        return "off", "mytool-cli not installed. Install with `pip install mytool-cli`"

Key Files and Reference Architecture

When adding a new platform channel, reference these source files in the Panniantong/Agent-Reach repository:

Summary

  • Create a subclass of Channel in agent_reach/channels/ with name, description, backends, and tier attributes.
  • Implement can_handle() to filter URLs using urllib.parse.urlparse network location matching.
  • Implement check() to iterate through self.ordered_backends(), probe each with probe_command or shutil.which, and set self.active_backend to the first healthy candidate.
  • Register the channel by importing the class and adding an instance to ALL_CHANNELS in agent_reach/channels/__init__.py.
  • Test thoroughly using monkey-patching to simulate missing or broken backends, and update docs/install.md to reflect the new supported platform.

Frequently Asked Questions

What is the minimum code required to add a new platform channel?

You need a Python file in agent_reach/channels/ containing a class that inherits from Channel (from agent_reach.channels.base), defines the four mandatory attributes (name, description, backends, tier), implements can_handle() for URL detection, implements check() for backend health validation, and is registered in agent_reach/channels/__init__.py. Without registration in ALL_CHANNELS, the CLI will not discover your channel.

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

The check() method iterates over the backends list in order, probing each candidate using agent_reach.probe.probe_command or similar utilities. It selects the first backend reporting "ok" or "warn" status and assigns it to self.active_backend. If no backends pass the health check, the channel reports "off" and provides installation hints to the user.

What do the tier values (0, 1, 2) represent in Agent Reach channels?

Tier 0 indicates a zero-config channel that works immediately after installation. Tier 1 requires a free API key or simple authentication. Tier 2 requires complex setup, paid credentials, or additional infrastructure. These values help the doctor command prioritize troubleshooting messages and guide users through configuration requirements.

Can I add operational methods like read() or search() to my custom channel?

Yes, you can implement optional methods such as read(self, url), search(self, query), or transcribe(self, ...) depending on the platform's capabilities. Keep these methods thin—they should delegate to the active backend via subprocess.run or library calls rather than implementing platform-specific parsing logic directly, maintaining the channel's role as a routing layer.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →