# How to Write a Custom Capability for LoopX: A Step-by-Step Developer Guide

> Learn to write a custom capability for LoopX with this step by step developer guide. Understand initialization, catalog entry, and CLI integration for seamless extension.

- Repository: [huangruiteng/loopx](https://github.com/huangruiteng/loopx)
- Tags: how-to-guide
- Published: 2026-09-04

---

**A LoopX capability is a self-contained package with an [`__init__.py`](https://github.com/huangruiteng/loopx/blob/main/__init__.py), [`catalog_entry.py`](https://github.com/huangruiteng/loopx/blob/main/catalog_entry.py) defining metadata and entry points, and optional [`cli.py`](https://github.com/huangruiteng/loopx/blob/main/cli.py) for command-line integration, discovered at runtime via the extension registry in [`loopx/extensions/registry.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/extensions/registry.py).**

LoopX implements a modular **capability system** that lets developers extend the platform with new functionality—whether value connectors, semantic preferences, or custom utilities. Writing a custom capability for LoopX requires following three core conventions: package layout, catalog entry definition, and runtime hook implementation. This guide walks through the complete process using the `huangruiteng/loopx` source code as reference.

## Understanding the LoopX Capability Architecture

LoopX discovers capabilities dynamically through **catalog entries**. Each capability lives as a Python package under `loopx/capabilities/<capability-name>/` and must expose metadata that the runtime uses for registration, configuration validation, and CLI integration.

The architecture rests on three pillars:

- **Package Layout** – Standard directory structure with required files
- **Catalog Entry** – Metadata describing purpose, version, and entry point
- **Runtime Hooks** – Optional callbacks the LoopX engine invokes at extension points

## Step-by-Step: Creating a Hello World Capability

Follow these steps to create a minimal working capability called `hello_world`.

### Step 1: Create the Package Directory

```bash
mkdir -p loopx/capabilities/hello_world
touch loopx/capabilities/hello_world/__init__.py

```

The [`__init__.py`](https://github.com/huangruiteng/loopx/blob/main/__init__.py) can remain empty; it simply marks the directory as a Python package.

### Step 2: Implement the Core Logic

Create [`loopx/capabilities/hello_world/main.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/capabilities/hello_world/main.py) with your capability's primary function:

```python

# loopx/capabilities/hello_world/main.py

def greet(config: dict | None = None) -> None:
    """Print a greeting. The `config` dict can contain a custom name."""
    name = (config or {}).get("name", "LoopX")
    print(f"👋 Hello, {name}! Welcome to your custom capability.")

```

This function serves as the **entry point** that LoopX will call. Design it to accept an optional configuration dictionary for flexibility.

### Step 3: Define the Catalog Entry

Create [`loopx/capabilities/hello_world/catalog_entry.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/capabilities/hello_world/catalog_entry.py) to expose metadata:

```python

# loopx/capabilities/hello_world/catalog_entry.py

from dataclasses import dataclass

@dataclass
class CatalogEntry:
    """Metadata that LoopX uses to discover the capability."""
    name: str = "hello_world"
    description: str = "A simple demo capability that prints a greeting."
    version: str = "0.1.0"
    entry_point: str = "loopx.capabilities.hello_world.main:greet"
    config_schema: dict = {
        "type": "object",
        "properties": {
            "name": {"type": "string", "description": "Custom name to greet"}
        },
        "additionalProperties": False,
    }

```

The `entry_point` uses the dotted path format `module:object` that Python's `importlib` can resolve. For reference on the canonical structure, examine [`loopx/capabilities/value_connectors/catalog_entry.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/capabilities/value_connectors/catalog_entry.py) in the source code.

### Step 4: Add CLI Integration

Create [`loopx/capabilities/hello_world/cli.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/capabilities/hello_world/cli.py) using **Typer** (LoopX's CLI framework):

```python

# loopx/capabilities/hello_world/cli.py

import typer
from .main import greet

app = typer.Typer(name="hello-world", help="Demo capability that greets you.")

@app.command()
def run(name: str = typer.Option("", help="Custom name to greet")):
    """CLI entry point that forwards to the core function."""
    config = {"name": name} if name else {}
    greet(config)

```

The pattern mirrors [`loopx/capabilities/value_connectors/cli.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/capabilities/value_connectors/cli.py), where commands load configuration and delegate to core functions.

### Step 5: Register with the Extension Registry

LoopX discovers capabilities through [`loopx/extensions/registry.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/extensions/registry.py) or via auto-discovery mechanisms. Ensure your catalog entry is importable at startup. If using explicit registration, add an import or entry-points reference pointing to your `CatalogEntry` class.

### Step 6: Write Documentation

Add usage documentation at [`docs/capabilities/hello_world.md`](https://github.com/huangruiteng/loopx/blob/main/docs/capabilities/hello_world.md) covering:

- Configuration options defined in `config_schema`
- CLI commands and flags
- Privacy or security considerations

### Step 7: Add Tests

Create [`tests/capabilities/test_hello_world.py`](https://github.com/huangruiteng/loopx/blob/main/tests/capabilities/test_hello_world.py) following the pattern in [`tests/capabilities/test_value_connector_social_profile.py`](https://github.com/huangruiteng/loopx/blob/main/tests/capabilities/test_value_connector_social_profile.py):

```python

# tests/capabilities/test_hello_world.py

from typer.testing import CliRunner
from loopx.capabilities.hello_world.cli import app

def test_greeting():
    runner = CliRunner()
    result = runner.invoke(app, ["run", "--name", "Alice"])
    assert result.exit_code == 0
    assert "Hello, Alice!" in result.stdout

def test_greeting_default():
    runner = CliRunner()
    result = runner.invoke(app, ["run"])
    assert result.exit_code == 0
    assert "Hello, LoopX!" in result.stdout

```

### Step 8: Run the Test Suite

```bash
pytest -q tests/capabilities/test_hello_world.py

```

Verify your capability doesn't break existing functionality:

```bash
pytest -q

```

## Key Reference Files in the LoopX Source

| File | Purpose |
|------|---------|
| [`loopx/capabilities/value_connectors/catalog_entry.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/capabilities/value_connectors/catalog_entry.py) | Canonical `CatalogEntry` structure with full metadata fields |
| [`loopx/capabilities/value_connectors/cli.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/capabilities/value_connectors/cli.py) | Typer-based CLI pattern for capabilities |
| [`loopx/capabilities/semantic_preference/catalog_entry.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/capabilities/semantic_preference/catalog_entry.py) | Alternative catalog entry with versioning examples |
| [`loopx/extensions/registry.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/extensions/registry.py) | Central capability discovery and registration |
| [`docs/reference/protocols/value-connector-plan-v0.md`](https://github.com/huangruiteng/loopx/blob/main/docs/reference/protocols/value-connector-plan-v0.md) | Protocol contracts defining public-private boundaries |
| [`tests/capabilities/test_value_connector_social_profile.py`](https://github.com/huangruiteng/loopx/blob/main/tests/capabilities/test_value_connector_social_profile.py) | Full test suite template for capabilities |

## Advanced: Implementing Runtime Hooks

Sophisticated capabilities can expose **hooks** that LoopX calls at defined extension points:

- `on_start()` – Initialization when the runtime loads the capability
- `on_event(event)` – React to system events
- `resolve(context)` – Participate in value resolution pipelines

Respect the **public-private boundary contracts** documented in the protocol specifications. Internal implementation details should remain private; only the declared entry points and hook signatures form the stable API.

## Summary

- **Package layout**: Create `loopx/capabilities/<name>/` with [`__init__.py`](https://github.com/huangruiteng/loopx/blob/main/__init__.py), [`catalog_entry.py`](https://github.com/huangruiteng/loopx/blob/main/catalog_entry.py), and [`cli.py`](https://github.com/huangruiteng/loopx/blob/main/cli.py)
- **Catalog entry**: Define a dataclass with `name`, `version`, `entry_point`, and `config_schema`
- **CLI hook**: Use Typer to expose commands that load config and call core functions
- **Registration**: Ensure discoverability via [`loopx/extensions/registry.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/extensions/registry.py) or auto-discovery
- **Testing**: Mirror patterns from [`tests/capabilities/test_value_connector_social_profile.py`](https://github.com/huangruiteng/loopx/blob/main/tests/capabilities/test_value_connector_social_profile.py)

## Frequently Asked Questions

### What is the minimum viable LoopX capability?

A **catalog entry** with `name`, `description`, `version`, and `entry_point` fields, placed in `loopx/capabilities/<name>/catalog_entry.py`, is sufficient for the runtime to discover your code. CLI integration and tests are recommended but optional for internal capabilities.

### How does LoopX discover capabilities at runtime?

LoopX scans registered entry points or imports modules from `loopx/capabilities/` and looks for `CatalogEntry` definitions. The [`loopx/extensions/registry.py`](https://github.com/huangruiteng/loopx/blob/main/loopx/extensions/registry.py) module coordinates this discovery, caching entries for efficient lookup during command execution.

### Can a capability have multiple entry points?

Yes. While the primary `entry_point` in the catalog entry points to a main function, your [`cli.py`](https://github.com/huangruiteng/loopx/blob/main/cli.py) can expose multiple subcommands through Typer. Each subcommand can invoke different functions from your core module, effectively providing multiple entry points through a unified CLI interface.

### Where should I define configuration validation for my capability?

Define the JSON **schema** in your `CatalogEntry.config_schema`. LoopX uses this schema to validate user-provided configuration before passing it to your entry point function. Complex validation logic should live in your core module, receiving the pre-validated config dictionary.