# How to Add a Custom Backend to an Existing Channel in Agent-Reach

> Learn to add a custom backend to an existing Agent-Reach channel. Implement probes and integrate into the check loop for seamless runtime selection. Full guide available.

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

---

**To add a custom backend to an existing channel in Agent-Reach, append the backend identifier to the channel's `backends` list, implement a private `_check_<backend>()` probe method that returns a status tuple, and wire it into the channel's `check()` loop so the channel can select it at runtime.**

Agent-Reach treats every platform (YouTube, Twitter, Reddit) as a **channel** that delegates `read`, `search`, and `check` operations to external **backends**. When you need to integrate a new command-line tool or API client into an existing channel, you extend the channel's backend probing logic. This guide walks through the exact steps to add a custom backend to an existing channel using the source code from the Panniantong/Agent-Reach repository.

## Understanding the Channel-Backend Architecture

The architecture relies on ordered backend lists and standardized health probes.

### The Backends List

Every concrete channel declares an ordered list of backend identifiers in the `backends` class attribute. In [`agent_reach/channels/base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/base.py) (lines 34-35), the base `Channel` class defines this structure, and concrete implementations like `TwitterChannel` populate it with strings such as `"twitter-cli"`, `"OpenCLI"`, or `"bird CLI (legacy)"`.

### Backend Selection Logic

The `ordered_backends(config)` method in [`agent_reach/channels/base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/base.py) (lines 45-59) returns this list, moving any user-specified override (via `<channel>_backend` config or `<CHANNEL>_BACKEND` environment variable) to the front. The channel's `check()` method then loops through these candidates, calling private `_check_<backend>()` helpers to probe each one. The first backend returning `"ok"` or `"warn"` is stored in `self.active_backend` (lines 61-70).

## Step-by-Step Implementation Guide

Follow these seven steps to integrate a new backend into an existing channel:

1. **Choose the target channel** (e.g., `twitter`, `youtube`, `reddit`) located in `agent_reach/channels/<channel>.py`.
2. **Define a backend identifier**—a short lowercase string like `"mycli"` that will appear in the `backends` list.
3. **Insert the identifier into the channel's `backends` list**, positioning it according to your preferred priority order.
4. **Implement the probe method** `_check_<backend>()` inside the channel class. Use `probe_command` from [`agent_reach/probe.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/probe.py) for simple binaries, or create a shared utility under `agent_reach/backends/` for complex multi-step checks (as demonstrated by [`agent_reach/backends/opencli.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/backends/opencli.py)).
5. **Wire the probe into the `check()` loop** by adding an `elif backend == "<identifier>":` branch that assigns `result = self._check_<backend>()`.
6. **Return a standardized tuple** `(status, message)` where status is `"ok"`, `"warn"`, `"error"`, or return `None` to skip the candidate. The message should describe the backend's condition.
7. **Run the test suite** with `pytest tests/ -v` to verify the new code does not break existing channel probes.

## Practical Example: Extending the Twitter Channel

The following example adds a fictional `"mycli"` backend to the Twitter channel in [`agent_reach/channels/twitter.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/twitter.py). This demonstrates the complete pattern: updating the `backends` list, adding the probe helper, and wiring it into the selection loop.

```python

# File: agent_reach/channels/twitter.py

class TwitterChannel(Channel):
    name = "twitter"
    description = "Twitter/X 推文"
    # Insert the new backend where you want it tried (after OpenCLI, before bird CLI)

    backends = ["twitter-cli", "OpenCLI", "mycli", "bird CLI (legacy)"]
    tier = 1

    def check(self, config=None):
        self.active_backend = None
        findings = []

        for backend in self.ordered_backends(config):
            if backend == "twitter-cli":
                result = self._check_twitter_cli()
            elif backend == "OpenCLI":
                result = self._check_opencli()
            elif backend == "mycli":
                result = self._check_mycli()          # ← new branch

            elif backend == "bird CLI (legacy)":
                result = self._check_bird()
            else:
                continue

            if result is None:
                continue
            findings.append((backend, *result))

        # ... existing selection logic ...

    # ----------------------------------------------------------------------

    # New probe helper for the custom backend

    # ----------------------------------------------------------------------

    def _check_mycli(self):
        """Probe the custom `mycli` tool."""
        from agent_reach.probe import probe_command

        probe = probe_command(
            "mycli", ["status"], timeout=10, package="mycli"
        )
        if probe.status == "missing":
            # Not installed – exclude from candidate list

            return None
        if probe.status == "broken":
            return "error", "mycli 命令存在但无法执行。" + probe.hint
        if probe.status == "timeout":
            return "error", "mycli 健康检查超时。" + probe.hint

        # Assume a healthy mycli prints "ready: true"

        if "ready: true" in probe.output.lower():
            return "ok", "mycli 可用（读取、搜索推文）"
        return "warn", "mycli 已安装但未准备好，请检查配置。"

```

The `_check_mycli()` method imports `probe_command` from [`agent_reach/probe.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/probe.py) and follows the same contract as `_check_opencli()` (lines 94-108 in [`twitter.py`](https://github.com/Panniantong/Agent-Reach/blob/main/twitter.py)), returning status tuples that the base class logic consumes.

## Key Source Files and Utilities

When you add a custom backend to an existing channel, you will work with these specific files:

- **[`agent_reach/channels/base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/base.py)** (lines 34-70): Defines the abstract `Channel` class, the `backends` attribute, `ordered_backends()`, and the generic `check()` framework that evaluates probe results.
- **[`agent_reach/channels/twitter.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/twitter.py)** (lines 19-48): Concrete reference showing how `_check_twitter_cli()`, `_check_opencli()`, and `_check_bird()` integrate into the backend selection loop.
- **[`agent_reach/backends/opencli.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/backends/opencli.py)** (lines 1-137): Illustrates complex backend validation with a dedicated module, useful when your custom backend requires sophisticated health checks beyond a simple command probe.
- **[`agent_reach/probe.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/probe.py)**: Provides `probe_command()`, which executes external binaries safely and classifies results as `missing`, `broken`, `ok`, or `timeout`, handling exceptions and timeouts uniformly.

## Summary

To successfully add a custom backend to an existing channel in Agent-Reach:

- Append the backend identifier string to the channel's `backends` class attribute in the concrete channel file.
- Implement a `_check_<backend>()` method that uses `probe_command` or custom logic to verify the external tool is installed and functional.
- Return standard status tuples (`"ok"`, `"warn"`, `"error"`, or `None`) so the channel's `check()` method can evaluate candidates against each other.
- Wire the new probe into the `check()` method's backend iteration with an `elif` branch.
- Users can force selection of your backend via the `<channel>_backend` configuration key or `<CHANNEL>_BACKEND` environment variable, which `ordered_backends()` automatically prioritizes without requiring code changes.

## Frequently Asked Questions

### What status values should my custom backend probe return?

Your `_check_<backend>()` method should return a two-element tuple `(status, message)`. The status must be a string: `"ok"` indicates the backend is fully functional, `"warn"` indicates it is usable but degraded, `"error"` indicates a broken installation, and returning `None` excludes the backend from consideration. This contract matches the implementation in [`agent_reach/channels/base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/base.py) (lines 61-70).

### Can I place my backend logic in a separate file instead of the channel class?

Yes. For complex backends requiring multiple helper functions or shared across channels, create a module under `agent_reach/backends/` (similar to [`agent_reach/backends/opencli.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/backends/opencli.py) for the OpenCLI backend). Import your health check functions into the channel file and call them from the `_check_<backend>()` method to maintain clean separation of concerns.

### How does Agent-Reach handle user-specified backend preferences?

The `ordered_backends(config)` method in [`agent_reach/channels/base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/base.py) (lines 45-59) checks for a configuration key matching `<channel>_backend` or an uppercase environment variable `<CHANNEL>_BACKEND`. When detected, it moves that identifier to index zero in the returned list, ensuring your custom backend is evaluated first regardless of its position in the default `backends` declaration.

### Do I need to modify the base Channel class to add a custom backend?

No. You only need to modify the concrete channel file (e.g., [`agent_reach/channels/twitter.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/twitter.py)). The base class in [`agent_reach/channels/base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/base.py) provides the generic probing framework and selection logic, while individual channels define their specific `backends` list and `_check_*` implementations. This design keeps custom backend logic scoped to the relevant platform.