How to Write a Custom Capability for LoopX: A Step-by-Step Developer Guide
A LoopX capability is a self-contained package with an __init__.py, catalog_entry.py defining metadata and entry points, and optional cli.py for command-line integration, discovered at runtime via the extension registry in 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
mkdir -p loopx/capabilities/hello_world
touch loopx/capabilities/hello_world/__init__.py
The __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 with your capability's primary function:
# 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 to expose metadata:
# 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 in the source code.
Step 4: Add CLI Integration
Create loopx/capabilities/hello_world/cli.py using Typer (LoopX's CLI framework):
# 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, where commands load configuration and delegate to core functions.
Step 5: Register with the Extension Registry
LoopX discovers capabilities through 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 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 following the pattern in tests/capabilities/test_value_connector_social_profile.py:
# 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
pytest -q tests/capabilities/test_hello_world.py
Verify your capability doesn't break existing functionality:
pytest -q
Key Reference Files in the LoopX Source
| File | Purpose |
|---|---|
loopx/capabilities/value_connectors/catalog_entry.py |
Canonical CatalogEntry structure with full metadata fields |
loopx/capabilities/value_connectors/cli.py |
Typer-based CLI pattern for capabilities |
loopx/capabilities/semantic_preference/catalog_entry.py |
Alternative catalog entry with versioning examples |
loopx/extensions/registry.py |
Central capability discovery and registration |
docs/reference/protocols/value-connector-plan-v0.md |
Protocol contracts defining public-private boundaries |
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 capabilityon_event(event)– React to system eventsresolve(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,catalog_entry.py, andcli.py - Catalog entry: Define a dataclass with
name,version,entry_point, andconfig_schema - CLI hook: Use Typer to expose commands that load config and call core functions
- Registration: Ensure discoverability via
loopx/extensions/registry.pyor auto-discovery - Testing: Mirror patterns from
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 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →