Skip to content

Index

axm

AXM CLI — Unified command-line interface for the AXM ecosystem.

AXMTool

Bases: Protocol

Structural protocol for AXM deterministic tools.

Implementors must provide: - name (property): unique tool identifier - execute(...) : deterministic execution with explicit params

Optionally provide: - agent_hint (class attribute): optional, free-form one-liner optimized for LLM consumption: what the tool does, key params, and what it replaces. Like the other discovery attributes below, it is not a protocol member and carries no guaranteed fallback — some discovery tooling reads it best-effort via getattr / :func:tool_metadata; absent, nothing is substituted. - expose_directly (class attribute, default False): when True, the MCP server registers this tool directly in tools/list (the hot path). When False (default), the tool is reachable only through the MCP facade (axm_search -> axm_describe -> axm_call), keeping the tools/list payload small. - domain (class attribute, default None): coarse capability group used by the facade for axm_capabilities and to scope axm_search (e.g. "ast", "git", "ticket"). - tags (class attribute, default frozenset()): free-form keywords feeding facade discovery (axm_search).

Uses structural typing (PEP 544) — no inheritance required. @runtime_checkable enables isinstance() checks. The discovery attributes (expose_directly / domain / tags) are not protocol members on purpose: adding data attributes to a runtime_checkable protocol would make them required for isinstance() and break the check for the many tools that satisfy AXMTool structurally without subclassing it. Read them through :func:tool_metadata (or getattr(tool, name, default)) instead, which works for subclasses and structural tools alike.

Example::

Text Only
class MyTool(AXMTool):
    agent_hint = "Frobnicate widgets — use width param."
    expose_directly = True          # hot path (read via tool_metadata)
    domain = "widget"
    tags = frozenset({"frobnicate"})

    @property
    def name(self) -> str:
        return "my-tool"

    def execute(self, *, value: int = 0) -> ToolResult:
        return ToolResult(success=True, data={"result": value})
Source code in packages/axm/src/axm/tools/base.py
Python
@runtime_checkable
class AXMTool(Protocol):
    """Structural protocol for AXM deterministic tools.

    Implementors must provide:
    - ``name`` (property): unique tool identifier
    - ``execute(...)`` : deterministic execution with explicit params

    Optionally provide:
    - ``agent_hint`` (class attribute): optional, free-form one-liner
      optimized for LLM consumption: what the tool does, key params, and
      what it replaces.  Like the other discovery attributes below, it is
      *not* a protocol member and carries no guaranteed fallback — some
      discovery tooling reads it best-effort via ``getattr`` /
      :func:`tool_metadata`; absent, nothing is substituted.
    - ``expose_directly`` (class attribute, default ``False``): when
      ``True``, the MCP server registers this tool directly in
      ``tools/list`` (the *hot path*).  When ``False`` (default), the
      tool is reachable only through the MCP facade
      (``axm_search`` -> ``axm_describe`` -> ``axm_call``), keeping the
      ``tools/list`` payload small.
    - ``domain`` (class attribute, default ``None``): coarse capability
      group used by the facade for ``axm_capabilities`` and to scope
      ``axm_search`` (e.g. ``"ast"``, ``"git"``, ``"ticket"``).
    - ``tags`` (class attribute, default ``frozenset()``): free-form
      keywords feeding facade discovery (``axm_search``).

    Uses structural typing (PEP 544) — no inheritance required.
    ``@runtime_checkable`` enables ``isinstance()`` checks.  The discovery
    attributes (``expose_directly`` / ``domain`` / ``tags``) are *not*
    protocol members on purpose: adding data attributes to a
    ``runtime_checkable`` protocol would make them required for
    ``isinstance()`` and break the check for the many tools that satisfy
    ``AXMTool`` structurally without subclassing it.  Read them through
    :func:`tool_metadata` (or ``getattr(tool, name, default)``) instead,
    which works for subclasses and structural tools alike.

    Example::

        class MyTool(AXMTool):
            agent_hint = "Frobnicate widgets — use width param."
            expose_directly = True          # hot path (read via tool_metadata)
            domain = "widget"
            tags = frozenset({"frobnicate"})

            @property
            def name(self) -> str:
                return "my-tool"

            def execute(self, *, value: int = 0) -> ToolResult:
                return ToolResult(success=True, data={"result": value})
    """

    @property
    def name(self) -> str:
        """Unique tool identifier (e.g., 'esbmc', 'dafny', 'pytest')."""
        ...

    def execute(self, **kwargs: Any) -> ToolResult:
        """Execute the tool with given arguments.

        Subclasses should override with explicit, typed parameters::

            def execute(self, *, title: str = "", body: str = "") -> ToolResult:
                ...

        The ``**kwargs`` signature here is the *structural minimum* —
        any callable accepting keyword arguments satisfies it.

        Returns:
            ToolResult with success status and structured data.
        """
        ...
name property

Unique tool identifier (e.g., 'esbmc', 'dafny', 'pytest').

execute(**kwargs)

Execute the tool with given arguments.

Subclasses should override with explicit, typed parameters::

Text Only
def execute(self, *, title: str = "", body: str = "") -> ToolResult:
    ...

The **kwargs signature here is the structural minimum — any callable accepting keyword arguments satisfies it.

Returns:

Type Description
ToolResult

ToolResult with success status and structured data.

Source code in packages/axm/src/axm/tools/base.py
Python
def execute(self, **kwargs: Any) -> ToolResult:
    """Execute the tool with given arguments.

    Subclasses should override with explicit, typed parameters::

        def execute(self, *, title: str = "", body: str = "") -> ToolResult:
            ...

    The ``**kwargs`` signature here is the *structural minimum* —
    any callable accepting keyword arguments satisfies it.

    Returns:
        ToolResult with success status and structured data.
    """
    ...

HookAction

Bases: Protocol

Protocol for hook implementations.

All hook actions receive a context dict with session info: - session_id: str - protocol_name: str - phase_name: str - task_name: str | None - working_dir: str | None - artifacts_path: str | None

Source code in packages/axm/src/axm/hooks/base.py
Python
@runtime_checkable
class HookAction(Protocol):
    """Protocol for hook implementations.

    All hook actions receive a context dict with session info:
    - session_id: str
    - protocol_name: str
    - phase_name: str
    - task_name: str | None
    - working_dir: str | None
    - artifacts_path: str | None
    """

    def execute(self, context: dict[str, Any], **params: Any) -> HookResult:
        """Execute the hook action.

        Args:
            context: Session context dictionary
            **params: Hook-specific parameters

        Returns:
            HookResult indicating success/failure with metadata
        """
        ...
execute(context, **params)

Execute the hook action.

Parameters:

Name Type Description Default
context dict[str, Any]

Session context dictionary

required
**params Any

Hook-specific parameters

{}

Returns:

Type Description
HookResult

HookResult indicating success/failure with metadata

Source code in packages/axm/src/axm/hooks/base.py
Python
def execute(self, context: dict[str, Any], **params: Any) -> HookResult:
    """Execute the hook action.

    Args:
        context: Session context dictionary
        **params: Hook-specific parameters

    Returns:
        HookResult indicating success/failure with metadata
    """
    ...

HookResult dataclass

Result of a hook execution.

Attributes:

Name Type Description
success bool

Whether the hook succeeded

error str | None

Error message if failed

metadata dict[str, Any]

Optional execution metadata

text str | None

Pre-rendered text representation of the result

Source code in packages/axm/src/axm/hooks/base.py
Python
@dataclass(frozen=True)
class HookResult:
    """Result of a hook execution.

    Attributes:
        success: Whether the hook succeeded
        error: Error message if failed
        metadata: Optional execution metadata
        text: Pre-rendered text representation of the result
    """

    success: bool
    error: str | None = None
    metadata: dict[str, Any] = field(default_factory=dict)
    text: str | None = None

    @classmethod
    def ok(cls, *, text: str | None = None, **metadata: Any) -> HookResult:
        """Create a successful result.

        Args:
            text: Pre-rendered text representation of the result.
            **metadata: Arbitrary key-value pairs stored in metadata.
        """
        return cls(success=True, text=text, metadata=metadata)

    @classmethod
    def fail(
        cls, error: str, *, text: str | None = None, **metadata: Any
    ) -> HookResult:
        """Create a failed result.

        Args:
            error: Human-readable error message.
            text: Pre-rendered text representation of the result.
            **metadata: Arbitrary key-value pairs stored in metadata.
        """
        return cls(success=False, error=error, text=text, metadata=metadata)

    @classmethod
    def skip(cls, reason: str = "condition not met") -> HookResult:
        """Create a skipped result (success, hook intentionally not run)."""
        return cls(success=True, metadata={"skipped": True, "reason": reason})
fail(error, *, text=None, **metadata) classmethod

Create a failed result.

Parameters:

Name Type Description Default
error str

Human-readable error message.

required
text str | None

Pre-rendered text representation of the result.

None
**metadata Any

Arbitrary key-value pairs stored in metadata.

{}
Source code in packages/axm/src/axm/hooks/base.py
Python
@classmethod
def fail(
    cls, error: str, *, text: str | None = None, **metadata: Any
) -> HookResult:
    """Create a failed result.

    Args:
        error: Human-readable error message.
        text: Pre-rendered text representation of the result.
        **metadata: Arbitrary key-value pairs stored in metadata.
    """
    return cls(success=False, error=error, text=text, metadata=metadata)
ok(*, text=None, **metadata) classmethod

Create a successful result.

Parameters:

Name Type Description Default
text str | None

Pre-rendered text representation of the result.

None
**metadata Any

Arbitrary key-value pairs stored in metadata.

{}
Source code in packages/axm/src/axm/hooks/base.py
Python
@classmethod
def ok(cls, *, text: str | None = None, **metadata: Any) -> HookResult:
    """Create a successful result.

    Args:
        text: Pre-rendered text representation of the result.
        **metadata: Arbitrary key-value pairs stored in metadata.
    """
    return cls(success=True, text=text, metadata=metadata)
skip(reason='condition not met') classmethod

Create a skipped result (success, hook intentionally not run).

Source code in packages/axm/src/axm/hooks/base.py
Python
@classmethod
def skip(cls, reason: str = "condition not met") -> HookResult:
    """Create a skipped result (success, hook intentionally not run)."""
    return cls(success=True, metadata={"skipped": True, "reason": reason})

ToolMetadata dataclass

Facade/CLI discovery metadata for a tool, with safe defaults.

Built by :func:tool_metadata from the optional expose_directly / domain / tags attributes a tool may declare. Centralising the defaults here keeps the MCP facade and the CLI reading the same contract.

Attributes:

Name Type Description
expose_directly bool

Whether the tool is on the MCP hot path (registered directly in tools/list). Default False.

domain str | None

Coarse capability group, or None if ungrouped.

tags frozenset[str]

Discovery keywords (possibly empty).

Source code in packages/axm/src/axm/tools/base.py
Python
@dataclass(frozen=True)
class ToolMetadata:
    """Facade/CLI discovery metadata for a tool, with safe defaults.

    Built by :func:`tool_metadata` from the optional ``expose_directly`` /
    ``domain`` / ``tags`` attributes a tool may declare.  Centralising the
    defaults here keeps the MCP facade and the CLI reading the same contract.

    Attributes:
        expose_directly: Whether the tool is on the MCP hot path
            (registered directly in ``tools/list``).  Default ``False``.
        domain: Coarse capability group, or ``None`` if ungrouped.
        tags: Discovery keywords (possibly empty).
    """

    expose_directly: bool = False
    domain: str | None = None
    tags: frozenset[str] = frozenset()

ToolNodeError

Bases: RuntimeError

A tool invoked as a DAG node failed (ToolResult.success was False).

Source code in packages/axm/src/axm/tools/node.py
Python
class ToolNodeError(RuntimeError):
    """A tool invoked as a DAG node failed (``ToolResult.success`` was ``False``)."""

ToolResult dataclass

Immutable result of a tool execution.

Attributes:

Name Type Description
success bool

Whether the tool execution succeeded.

data dict[str, Any]

Structured output data (backend-specific).

error str | None

Human-readable error message, if any.

hint str | None

Optional next-step suggestion for the agent.

text str | None

Optional pre-rendered text representation (e.g. Markdown).

Source code in packages/axm/src/axm/tools/base.py
Python
@dataclass(frozen=True)
class ToolResult:
    """Immutable result of a tool execution.

    Attributes:
        success: Whether the tool execution succeeded.
        data: Structured output data (backend-specific).
        error: Human-readable error message, if any.
        hint: Optional next-step suggestion for the agent.
        text: Optional pre-rendered text representation (e.g. Markdown).
    """

    success: bool
    data: dict[str, Any] = field(default_factory=dict)
    error: str | None = None
    hint: str | None = None
    text: str | None = None

ValidationFeedback dataclass

Structured feedback for validation failures.

Attributes:

Name Type Description
what str

What failed (brief description)

why str

Why it failed (expected vs actual)

how str

How to fix it (actionable guidance)

Source code in packages/axm/src/axm/witnesses.py
Python
@dataclass(frozen=True)
class ValidationFeedback:
    """Structured feedback for validation failures.

    Attributes:
        what: What failed (brief description)
        why: Why it failed (expected vs actual)
        how: How to fix it (actionable guidance)
    """

    what: str
    why: str
    how: str

    def to_dict(self) -> dict[str, str]:
        """Serialize to dictionary."""
        return {"what": self.what, "why": self.why, "how": self.how}
to_dict()

Serialize to dictionary.

Source code in packages/axm/src/axm/witnesses.py
Python
def to_dict(self) -> dict[str, str]:
    """Serialize to dictionary."""
    return {"what": self.what, "why": self.why, "how": self.how}

WitnessResult dataclass

Result of a witness validation.

Attributes:

Name Type Description
passed bool

Whether validation passed

feedback ValidationFeedback | None

Feedback if validation failed

verdict str | None

Optional routing decision for Gates

metadata dict[str, Any]

Optional execution metadata

Source code in packages/axm/src/axm/witnesses.py
Python
@dataclass(frozen=True)
class WitnessResult:
    """Result of a witness validation.

    Attributes:
        passed: Whether validation passed
        feedback: Feedback if validation failed
        verdict: Optional routing decision for Gates
        metadata: Optional execution metadata
    """

    passed: bool
    feedback: ValidationFeedback | None = None
    verdict: str | None = None
    metadata: dict[str, Any] = field(default_factory=dict)

    @classmethod
    def success(
        cls,
        verdict: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> WitnessResult:
        """Create a passing result."""
        return cls(passed=True, verdict=verdict, metadata=metadata or {})

    @classmethod
    def failure(
        cls,
        feedback: ValidationFeedback,
        verdict: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> WitnessResult:
        """Create a failing result with feedback."""
        return cls(
            passed=False, feedback=feedback, verdict=verdict, metadata=metadata or {}
        )
failure(feedback, verdict=None, metadata=None) classmethod

Create a failing result with feedback.

Source code in packages/axm/src/axm/witnesses.py
Python
@classmethod
def failure(
    cls,
    feedback: ValidationFeedback,
    verdict: str | None = None,
    metadata: dict[str, Any] | None = None,
) -> WitnessResult:
    """Create a failing result with feedback."""
    return cls(
        passed=False, feedback=feedback, verdict=verdict, metadata=metadata or {}
    )
success(verdict=None, metadata=None) classmethod

Create a passing result.

Source code in packages/axm/src/axm/witnesses.py
Python
@classmethod
def success(
    cls,
    verdict: str | None = None,
    metadata: dict[str, Any] | None = None,
) -> WitnessResult:
    """Create a passing result."""
    return cls(passed=True, verdict=verdict, metadata=metadata or {})

WitnessRule

Bases: Protocol

Protocol for witness validation rules.

Source code in packages/axm/src/axm/witnesses.py
Python
@runtime_checkable
class WitnessRule(Protocol):
    """Protocol for witness validation rules."""

    def validate(self, content: str, **kwargs: Any) -> WitnessResult:
        """Validate content.

        Args:
            content: The content to validate
            **kwargs: Additional validation parameters

        Returns:
            WitnessResult indicating pass/fail with feedback
        """
        ...
validate(content, **kwargs)

Validate content.

Parameters:

Name Type Description Default
content str

The content to validate

required
**kwargs Any

Additional validation parameters

{}

Returns:

Type Description
WitnessResult

WitnessResult indicating pass/fail with feedback

Source code in packages/axm/src/axm/witnesses.py
Python
def validate(self, content: str, **kwargs: Any) -> WitnessResult:
    """Validate content.

    Args:
        content: The content to validate
        **kwargs: Additional validation parameters

    Returns:
        WitnessResult indicating pass/fail with feedback
    """
    ...

tool_metadata(tool)

Read a tool's optional discovery attributes into a :class:ToolMetadata.

Works for tools that subclass :class:AXMTool, tools that satisfy it structurally, and plain callables — anything missing an attribute falls back to the default. tags is coerced to a frozenset so callers can rely on set semantics regardless of how the tool declared it.

Parameters:

Name Type Description Default
tool object

The tool instance (or plain callable) to introspect.

required

Returns:

Name Type Description
A ToolMetadata

class:ToolMetadata with resolved values.

Source code in packages/axm/src/axm/tools/base.py
Python
def tool_metadata(tool: object) -> ToolMetadata:
    """Read a tool's optional discovery attributes into a :class:`ToolMetadata`.

    Works for tools that subclass :class:`AXMTool`, tools that satisfy it
    structurally, and plain callables — anything missing an attribute falls
    back to the default.  ``tags`` is coerced to a ``frozenset`` so callers
    can rely on set semantics regardless of how the tool declared it.

    Args:
        tool: The tool instance (or plain callable) to introspect.

    Returns:
        A :class:`ToolMetadata` with resolved values.
    """
    raw_tags = getattr(tool, "tags", None) or ()
    return ToolMetadata(
        expose_directly=bool(getattr(tool, "expose_directly", False)),
        domain=getattr(tool, "domain", None),
        tags=frozenset(raw_tags),
    )

tool_node(name, *, args=None, returns=None)

Build a DAG python-node fn(payload) -> dict around an axm.tools tool.

Parameters:

Name Type Description Default
name str

The tool's axm.tools entry-point name (e.g. "ast_impact").

required
args Mapping[str, str] | None

Optional {param_name: mem_key} rename map for params whose mem key differs from the execute parameter name. Params absent from args are read from payload by their own name when present.

None
returns Mapping[str, str] | None

{write_key: source} where source is "text" (the ToolResult.text) or a key inside ToolResult.data. Defaults to {name: "text"} is not assumed — supply it so the node's writes are explicit (decision: writes nommés par clé).

None

Returns:

Type Description
Callable[[Mapping[str, object]], dict[str, object]]

A callable mapping the node's payload to a dict keyed by returns.

Raises:

Type Description
ToolNodeError

At call time, if the tool is unknown or returns success=False (fail-fast).

Source code in packages/axm/src/axm/tools/node.py
Python
def tool_node(
    name: str,
    *,
    args: Mapping[str, str] | None = None,
    returns: Mapping[str, str] | None = None,
) -> Callable[[Mapping[str, object]], dict[str, object]]:
    """Build a DAG python-node ``fn(payload) -> dict`` around an ``axm.tools`` tool.

    Args:
        name: The tool's ``axm.tools`` entry-point name (e.g. ``"ast_impact"``).
        args: Optional ``{param_name: mem_key}`` rename map for params whose mem
            key differs from the ``execute`` parameter name. Params absent from
            *args* are read from ``payload`` by their own name when present.
        returns: ``{write_key: source}`` where *source* is ``"text"`` (the
            ``ToolResult.text``) or a key inside ``ToolResult.data``. Defaults to
            ``{name: "text"}`` is **not** assumed — supply it so the node's writes
            are explicit (decision: writes nommés par clé).

    Returns:
        A callable mapping the node's ``payload`` to a dict keyed by *returns*.

    Raises:
        ToolNodeError: At call time, if the tool is unknown or returns
            ``success=False`` (fail-fast).
    """
    rename = dict(args or {})
    out_spec = dict(returns or {})

    def _run(payload: Mapping[str, object]) -> dict[str, object]:
        tool = _load_tool(name)
        kwargs = _kwargs_from_payload(payload, rename)
        try:
            result = tool.execute(**kwargs)
        except TypeError as exc:
            # A payload key with no matching ``execute`` parameter (strict
            # signature, no ``**kwargs``) must surface as the documented
            # ToolNodeError, not a raw TypeError.
            msg = f"tool {name!r}: bad payload for execute(): {exc}"
            raise ToolNodeError(msg) from exc
        if not result.success:
            msg = f"tool {name!r} failed: {result.error or '<no error message>'}"
            raise ToolNodeError(msg)
        return _shape_output(name, result.data, result.text, out_spec)

    return _run