Skip to content

Custom capabilities

The engine ships with built-in capabilities (Architect, TaskPlanner, CodeGenerator, TestGenerator, TestRunner…). When none of them fit, you can write your own by subclassing BaseExecutor.

Writing a custom capability

A capability has two responsibilities: decide when it should run (can_handle), and do the work (execute).

from antcrew_engine.capabilities import BaseExecutor
from antcrew_engine.artifacts import ArtifactStore, ArtifactKind
from antcrew_engine.goals import Goal, ConditionId
from antcrew_engine.config import build_llm

class WebResearcher(BaseExecutor):
    """Searches the web and stores a research_report artifact."""

    name = "WebResearcher"

    def __init__(self, llm, search_fn):
        self.llm = llm
        self.search_fn = search_fn  # callable(query: str) -> list[str]

    def can_handle(self, goal: Goal, store: ArtifactStore) -> bool:
        # Run when there's no research report yet
        return not store.get("research_report")

    def execute(self, goal: Goal, store: ArtifactStore) -> None:
        snippets = self.search_fn(goal.description)
        context = "\n\n".join(snippets)

        response = self.llm.complete(
            system="You are a research analyst. Write a concise report.",
            user=f"Goal: {goal.description}\n\nSources:\n{context}",
        )

        store.put("research_report", {
            "kind": ArtifactKind.DOCUMENT,
            "content": response.text,
            "sources": snippets,
        })

Register it like any other capability:

from antcrew_engine import EngineLoop, CapabilityRegistry, MemoryStore, artifact_validators
from antcrew_engine.config import build_llm

llm = build_llm("anthropic:claude-opus-5")

registry = CapabilityRegistry()
registry.register(WebResearcher(llm, search_fn=my_search))
registry.register(...)   # add other capabilities

store = MemoryStore()
engine = EngineLoop(registry, artifact_validators)
engine.run(store, goal)

Adding tools to a capability

Capabilities don't use a @tool decorator — they're plain Python. Pass any callable as a dependency and call it directly inside execute:

import httpx

def fetch_url(url: str) -> str:
    return httpx.get(url, timeout=10).text

class PageScraper(BaseExecutor):
    name = "PageScraper"

    def __init__(self, llm):
        self.llm = llm

    def can_handle(self, goal, store):
        return not store.get("scraped_content")

    def execute(self, goal, store):
        url = store.get("target_url")["value"]
        html = fetch_url(url)          # plain function call
        summary = self.llm.complete(
            system="Extract main content from HTML.",
            user=html[:8000],
        )
        store.put("scraped_content", {"kind": ArtifactKind.DOCUMENT, "content": summary.text})

HITL inside a custom capability

To pause for human input, use HitlReviewer as a separate capability that runs after yours. Register it in the CapabilityRegistry with after_capability set to your capability's name:

from antcrew_engine import HitlReviewer

registry.register(WebResearcher(llm, search_fn=my_search))
registry.register(HitlReviewer(
    platform_url="https://antcrew.org",
    api_key="acw_live_...",
    after_capability="WebResearcher",
    prompt="Review the research report before the plan is created.",
))
registry.register(TaskPlanner(llm))

The engine will call WebResearcher, then pause at HitlReviewer until someone approves in the platform dashboard, then continue with TaskPlanner.

Custom artifact validators

By default the engine uses the built-in artifact_validators dict. To add your own validation conditions:

from antcrew_engine import artifact_validators, ConditionId

my_validators = {
    **artifact_validators,
    ConditionId("research_complete"): lambda store: bool(store.get("research_report")),
}

engine = EngineLoop(registry, my_validators)

See Typed artifacts for the full ArtifactStore API and built-in artifact kinds.