Skip to content

tenro.errors

Exceptions raised by Tenro when your agent doesn't behave as expected.

What errors mean

When a Tenro verification fails, it means your agent didn't behave as expected:

from tenro import Provider, link_tool
from tenro.simulate import llm, tool
from tenro.testing import tenro

@link_tool("search")
def search(query: str) -> list[str]:
    return api.search(query)

@tenro
def test_agent_searches_before_responding():
    tool.simulate(search, result=["doc1"])
    llm.simulate(Provider.OPENAI, response="Summary")

    my_agent.run("Find info")

    # If this fails, your agent didn't call search the expected number of times
    tool.verify_many(search, count=1)

If verify_tool fails, you'll see:

tenro.TenroVerificationError: Expected tool 'search' to be called 1 time, but was called 0 times

This tells you: Your agent didn't call the search tool. Fix your agent code, not the test.

Exception types

Exception Meaning Example
TenroVerificationError Agent didn't behave as expected Tool called wrong number of times
TenroConfigError Test setup is invalid Unknown provider name
TenroSimulationSetupError Simulation couldn't be applied Missing required simulation

Common error messages

"Expected tool 'X' to be called N times, but was called M times"

Your agent called a tool more or fewer times than expected. Check your agent logic.

"No simulation configured for provider 'X'"

You forgot to simulate the LLM before running your agent:

from tenro import Provider
from tenro.simulate import llm
from tenro.testing import tenro
@tenro
def test_agent():
    # Missing: llm.simulate(Provider.OPENAI, response="...")
    my_agent.run("query")  # Fails - no simulation for OpenAI

"Unused simulation for tool 'X'"

You simulated a tool your agent never called. Either your agent has a bug, or remove the unnecessary simulation.

Reference

Exceptions and warnings for Tenro SDK.

SimulationDiagnostic dataclass

Structured diagnostic information for simulation errors.

Provides machine-readable fields for tooling and human-readable messages.

Source code in tenro/errors/simulation.py
@dataclass
class SimulationDiagnostic:
    """Structured diagnostic information for simulation errors.

    Provides machine-readable fields for tooling and human-readable messages.
    """

    target_path: str
    """Canonical path of the simulation target (e.g., 'mymodule.my_tool')."""

    target_type: str
    """Type of the target (e.g., 'function', 'method', 'class', 'builtin')."""

    is_linked: bool
    """Whether the target is a linked callable (@link_tool, @link_agent, @link_llm)."""

    failure_reason: str
    """Technical explanation of why the operation failed."""

    recommended_fix: str
    """Actionable guidance for resolving the error."""

failure_reason instance-attribute

failure_reason: str

Technical explanation of why the operation failed.

is_linked instance-attribute

is_linked: bool

Whether the target is a linked callable (@link_tool, @link_agent, @link_llm).

recommended_fix instance-attribute

recommended_fix: str

Actionable guidance for resolving the error.

target_path instance-attribute

target_path: str

Canonical path of the simulation target (e.g., 'mymodule.my_tool').

target_type instance-attribute

target_type: str

Type of the target (e.g., 'function', 'method', 'class', 'builtin').

TenroAgentError

Bases: TenroError

Base exception for agent-related errors.

Source code in tenro/errors/base.py
class TenroAgentError(TenroError):
    """Base exception for agent-related errors."""

TenroAgentRecursionError

Bases: TenroAgentError

Raised when agent exceeds maximum nesting depth.

This usually indicates an infinite loop between agents (e.g., Agent A calls Agent B, which calls Agent A again).

Source code in tenro/errors/base.py
class TenroAgentRecursionError(TenroAgentError):
    """Raised when agent exceeds maximum nesting depth.

    This usually indicates an infinite loop between agents
    (e.g., Agent A calls Agent B, which calls Agent A again).
    """

TenroCoercionWarning

Bases: TenroWarning

Emitted when ToolCall() automatically converts a value to JSON-serializable format.

This helps catch unexpected coercions. If you expect the coercion, silence with: warnings.filterwarnings("ignore", category=TenroCoercionWarning)

Source code in tenro/errors/warnings.py
class TenroCoercionWarning(TenroWarning):
    """Emitted when ToolCall() automatically converts a value to JSON-serializable format.

    This helps catch unexpected coercions. If you expect the coercion, silence with:
        warnings.filterwarnings("ignore", category=TenroCoercionWarning)
    """

TenroConfigError

Bases: TenroError

Raised when configuration or setup is invalid.

Source code in tenro/errors/base.py
class TenroConfigError(TenroError):
    """Raised when configuration or setup is invalid."""

TenroConfigWarning

Bases: TenroWarning

Emitted for non-fatal configuration issues.

The SDK continues operating but behavior may not match expectations.

Source code in tenro/errors/warnings.py
class TenroConfigWarning(TenroWarning):
    """Emitted for non-fatal configuration issues.

    The SDK continues operating but behavior may not match expectations.
    """

TenroConstructError

Bases: TenroError

Base exception for Construct test harness errors.

Source code in tenro/errors/base.py
class TenroConstructError(TenroError):
    """Base exception for Construct test harness errors."""

TenroDeprecationWarning

Bases: TenroWarning, DeprecationWarning

Emitted when using a deprecated API that will be removed.

Inherits from both TenroWarning (for SDK filtering) and DeprecationWarning (for standard Python filtering behavior).

Source code in tenro/errors/warnings.py
class TenroDeprecationWarning(TenroWarning, DeprecationWarning):
    """Emitted when using a deprecated API that will be removed.

    Inherits from both TenroWarning (for SDK filtering) and DeprecationWarning
    (for standard Python filtering behavior).
    """

TenroError

Bases: Exception

Base exception for all Tenro errors.

Source code in tenro/errors/base.py
class TenroError(Exception):
    """Base exception for all Tenro errors."""

TenroFutureWarning

Bases: TenroWarning, FutureWarning

Emitted when behavior will change in an upcoming release.

Inherits from both TenroWarning (for SDK filtering) and FutureWarning (for standard Python filtering behavior).

Source code in tenro/errors/warnings.py
class TenroFutureWarning(TenroWarning, FutureWarning):
    """Emitted when behavior will change in an upcoming release.

    Inherits from both TenroWarning (for SDK filtering) and FutureWarning
    (for standard Python filtering behavior).
    """

TenroLateImportWarning

Bases: TenroWarning

Emitted when modules are imported before Tenro can patch them.

Best-effort patching is applied but stale references may exist. Import tenro before other libraries to avoid this warning.

Source code in tenro/errors/warnings.py
class TenroLateImportWarning(TenroWarning):
    """Emitted when modules are imported before Tenro can patch them.

    Best-effort patching is applied but stale references may exist.
    Import tenro before other libraries to avoid this warning.
    """

TenroMissingLLMCallError

Bases: TenroVerificationError

Raised when a linked LLM function doesn't call the provider.

The decorated function executed but no HTTP request was made to the LLM provider, so the configured simulation was never used.

Source code in tenro/errors/base.py
class TenroMissingLLMCallError(TenroVerificationError):
    """Raised when a linked LLM function doesn't call the provider.

    The decorated function executed but no HTTP request was made to the
    LLM provider, so the configured simulation was never used.
    """

TenroPatchingWarning

Bases: TenroWarning

Emitted when PatchEngine fails to install in non-strict mode.

The SDK continues operating but simulation may not work correctly for captured function references. Use --tenro-strict-patch to make patching failures fatal.

Source code in tenro/errors/warnings.py
class TenroPatchingWarning(TenroWarning):
    """Emitted when PatchEngine fails to install in non-strict mode.

    The SDK continues operating but simulation may not work correctly
    for captured function references. Use --tenro-strict-patch to
    make patching failures fatal.
    """

TenroPluginWarning

Bases: TenroWarning

Emitted when a provider plugin fails to load.

Other plugins continue loading; only the failing plugin is skipped.

Source code in tenro/errors/warnings.py
class TenroPluginWarning(TenroWarning):
    """Emitted when a provider plugin fails to load.

    Other plugins continue loading; only the failing plugin is skipped.
    """

TenroProviderConfigError

Bases: TenroConfigError

Raised when provider configuration is invalid.

Source code in tenro/errors/base.py
class TenroProviderConfigError(TenroConfigError):
    """Raised when provider configuration is invalid."""

TenroProviderRuntimeError

Bases: TenroError

Raised when a provider encounters a runtime error.

Source code in tenro/errors/base.py
class TenroProviderRuntimeError(TenroError):
    """Raised when a provider encounters a runtime error."""

TenroSimulationCoverageError

Bases: TenroVerificationError

Raised when simulation coverage requirements are not met.

Coverage errors indicate incomplete test execution where expected simulations were not triggered.

Source code in tenro/errors/base.py
class TenroSimulationCoverageError(TenroVerificationError):
    """Raised when simulation coverage requirements are not met.

    Coverage errors indicate incomplete test execution where expected
    simulations were not triggered.
    """

TenroSimulationExecutionError

Bases: TenroError

Raised when simulation execution produces unexpected callable kind.

This error occurs at call time when a simulation rule's side_effect or configured response produces the wrong type for the wrapper's expected kind:

  • Sync wrapper expects non-awaitable, non-generator value
  • Async wrapper expects awaitable
  • Generator wrapper expects Generator
  • Async generator wrapper expects AsyncGenerator

Attributes:

Name Type Description
expected_kind

The callable kind the wrapper expected.

actual_kind

The callable kind that was produced.

target_path

The simulation target's canonical path.

Source code in tenro/errors/simulation.py
class TenroSimulationExecutionError(TenroError):
    """Raised when simulation execution produces unexpected callable kind.

    This error occurs at call time when a simulation rule's side_effect or
    configured response produces the wrong type for the wrapper's expected kind:

    - Sync wrapper expects non-awaitable, non-generator value
    - Async wrapper expects awaitable
    - Generator wrapper expects Generator
    - Async generator wrapper expects AsyncGenerator

    Attributes:
        expected_kind: The callable kind the wrapper expected.
        actual_kind: The callable kind that was produced.
        target_path: The simulation target's canonical path.
    """

    def __init__(
        self,
        target_path: str,
        expected_kind: str,
        actual_kind: str,
    ) -> None:
        """Initialize with kind mismatch information.

        Args:
            target_path: The simulation target's canonical path.
            expected_kind: Expected kind (sync/async/gen/asyncgen).
            actual_kind: Actual kind that was produced.
        """
        self.target_path = target_path
        self.expected_kind = expected_kind
        self.actual_kind = actual_kind
        super().__init__(
            f"Simulation for '{target_path}' returned wrong callable kind.\n\n"
            f"Expected: {expected_kind}\n"
            f"Got: {actual_kind}\n\n"
            "Ensure side_effect/result matches the target's signature.",
        )

__init__

__init__(target_path: str, expected_kind: str, actual_kind: str) -> None

Initialize with kind mismatch information.

Parameters:

Name Type Description Default
target_path str

The simulation target's canonical path.

required
expected_kind str

Expected kind (sync/async/gen/asyncgen).

required
actual_kind str

Actual kind that was produced.

required
Source code in tenro/errors/simulation.py
def __init__(
    self,
    target_path: str,
    expected_kind: str,
    actual_kind: str,
) -> None:
    """Initialize with kind mismatch information.

    Args:
        target_path: The simulation target's canonical path.
        expected_kind: Expected kind (sync/async/gen/asyncgen).
        actual_kind: Actual kind that was produced.
    """
    self.target_path = target_path
    self.expected_kind = expected_kind
    self.actual_kind = actual_kind
    super().__init__(
        f"Simulation for '{target_path}' returned wrong callable kind.\n\n"
        f"Expected: {expected_kind}\n"
        f"Got: {actual_kind}\n\n"
        "Ensure side_effect/result matches the target's signature.",
    )

TenroSimulationSetupError

Bases: TenroConfigError

Raised when simulation setup fails due to invalid target.

This error occurs during simulate() calls when the target cannot be simulated. Common causes:

  • Target is not decorated with @link_tool, @link_agent, or @link_llm
  • Target is a builtin or C-extension that cannot be intercepted
  • Target identity cannot be resolved

Attributes:

Name Type Description
diagnostic

Structured diagnostic with target info and fix suggestions.

Source code in tenro/errors/simulation.py
class TenroSimulationSetupError(TenroConfigError):
    """Raised when simulation setup fails due to invalid target.

    This error occurs during `simulate()` calls when the target cannot be
    simulated. Common causes:

    - Target is not decorated with @link_tool, @link_agent, or @link_llm
    - Target is a builtin or C-extension that cannot be intercepted
    - Target identity cannot be resolved

    Attributes:
        diagnostic: Structured diagnostic with target info and fix suggestions.
    """

    def __init__(self, message: str, diagnostic: SimulationDiagnostic) -> None:
        """Initialize with message and diagnostic info.

        Args:
            message: Human-readable error message.
            diagnostic: Structured diagnostic information.
        """
        self.diagnostic = diagnostic
        super().__init__(message)

    @classmethod
    def not_linked(
        cls, target_path: str, target_type: str = "function"
    ) -> TenroSimulationSetupError:
        """Create error for non-linked target.

        Args:
            target_path: The target's dotted path or repr.
            target_type: Type of the target (function, method, class, etc.).

        Returns:
            Configured error with diagnostic.
        """
        diagnostic = SimulationDiagnostic(
            target_path=target_path,
            target_type=target_type,
            is_linked=False,
            failure_reason="Target is not linked; capture-safe interception unavailable.",
            recommended_fix="Decorate with @link_tool, @link_agent, or @link_llm.",
        )
        return cls(
            f"Cannot simulate '{target_path}': not a linked callable.\n\n"
            f"Reason: {diagnostic.failure_reason}\n"
            f"Fix: {diagnostic.recommended_fix}",
            diagnostic=diagnostic,
        )

    @classmethod
    def not_patchable(
        cls, target_path: str, reason: str, target_type: str = "function"
    ) -> TenroSimulationSetupError:
        """Create error for non-patchable target (registered function simulation).

        Args:
            target_path: The target's dotted path or repr.
            reason: Why the target is not patchable.
            target_type: Type of the target (function, method, class, etc.).

        Returns:
            Configured error with diagnostic.
        """
        diagnostic = SimulationDiagnostic(
            target_path=target_path,
            target_type=target_type,
            is_linked=False,
            failure_reason=f"Target is not patchable: {reason}",
            recommended_fix=(
                "Use @link_tool decorator, HTTP boundary interception, "
                "or wrap the function in a patchable Python function."
            ),
        )
        return cls(
            f"Cannot register '{target_path}' for simulation: not patchable.\n\n"
            f"Reason: {reason}\n"
            f"Fix: {diagnostic.recommended_fix}",
            diagnostic=diagnostic,
        )

__init__

__init__(message: str, diagnostic: SimulationDiagnostic) -> None

Initialize with message and diagnostic info.

Parameters:

Name Type Description Default
message str

Human-readable error message.

required
diagnostic SimulationDiagnostic

Structured diagnostic information.

required
Source code in tenro/errors/simulation.py
def __init__(self, message: str, diagnostic: SimulationDiagnostic) -> None:
    """Initialize with message and diagnostic info.

    Args:
        message: Human-readable error message.
        diagnostic: Structured diagnostic information.
    """
    self.diagnostic = diagnostic
    super().__init__(message)

not_linked classmethod

not_linked(target_path: str, target_type: str = 'function') -> TenroSimulationSetupError

Create error for non-linked target.

Parameters:

Name Type Description Default
target_path str

The target's dotted path or repr.

required
target_type str

Type of the target (function, method, class, etc.).

'function'

Returns:

Type Description
TenroSimulationSetupError

Configured error with diagnostic.

Source code in tenro/errors/simulation.py
@classmethod
def not_linked(
    cls, target_path: str, target_type: str = "function"
) -> TenroSimulationSetupError:
    """Create error for non-linked target.

    Args:
        target_path: The target's dotted path or repr.
        target_type: Type of the target (function, method, class, etc.).

    Returns:
        Configured error with diagnostic.
    """
    diagnostic = SimulationDiagnostic(
        target_path=target_path,
        target_type=target_type,
        is_linked=False,
        failure_reason="Target is not linked; capture-safe interception unavailable.",
        recommended_fix="Decorate with @link_tool, @link_agent, or @link_llm.",
    )
    return cls(
        f"Cannot simulate '{target_path}': not a linked callable.\n\n"
        f"Reason: {diagnostic.failure_reason}\n"
        f"Fix: {diagnostic.recommended_fix}",
        diagnostic=diagnostic,
    )

not_patchable classmethod

not_patchable(target_path: str, reason: str, target_type: str = 'function') -> TenroSimulationSetupError

Create error for non-patchable target (registered function simulation).

Parameters:

Name Type Description Default
target_path str

The target's dotted path or repr.

required
reason str

Why the target is not patchable.

required
target_type str

Type of the target (function, method, class, etc.).

'function'

Returns:

Type Description
TenroSimulationSetupError

Configured error with diagnostic.

Source code in tenro/errors/simulation.py
@classmethod
def not_patchable(
    cls, target_path: str, reason: str, target_type: str = "function"
) -> TenroSimulationSetupError:
    """Create error for non-patchable target (registered function simulation).

    Args:
        target_path: The target's dotted path or repr.
        reason: Why the target is not patchable.
        target_type: Type of the target (function, method, class, etc.).

    Returns:
        Configured error with diagnostic.
    """
    diagnostic = SimulationDiagnostic(
        target_path=target_path,
        target_type=target_type,
        is_linked=False,
        failure_reason=f"Target is not patchable: {reason}",
        recommended_fix=(
            "Use @link_tool decorator, HTTP boundary interception, "
            "or wrap the function in a patchable Python function."
        ),
    )
    return cls(
        f"Cannot register '{target_path}' for simulation: not patchable.\n\n"
        f"Reason: {reason}\n"
        f"Fix: {diagnostic.recommended_fix}",
        diagnostic=diagnostic,
    )

TenroSimulationUsageError

Bases: TenroConfigError

Raised when simulation API is used incorrectly.

This error is raised when simulate() is called without an active Construct context. All simulation calls must be made within a Construct context.

Source code in tenro/errors/base.py
class TenroSimulationUsageError(TenroConfigError):
    """Raised when simulation API is used incorrectly.

    This error is raised when simulate() is called without an active Construct
    context. All simulation calls must be made within a Construct context.
    """

TenroTracingWarning

Bases: TenroWarning

Emitted when tracing cannot be applied to a linked component.

The component still functions but without extended tracing.

Source code in tenro/errors/warnings.py
class TenroTracingWarning(TenroWarning):
    """Emitted when tracing cannot be applied to a linked component.

    The component still functions but without extended tracing.
    """

TenroUnexpectedLLMCallError

Bases: TenroVerificationError

Raised when an unmatched request hits a blocked LLM domain.

This error protects against accidentally calling real LLM APIs during tests. If a request to a known LLM provider (e.g., api.openai.com) is made without a matching simulation, this error is raised immediately.

Attributes:

Name Type Description
domain

The blocked domain that was accessed (e.g., "api.openai.com").

url

The full URL of the blocked request.

Source code in tenro/errors/base.py
class TenroUnexpectedLLMCallError(TenroVerificationError):
    """Raised when an unmatched request hits a blocked LLM domain.

    This error protects against accidentally calling real LLM APIs during
    tests. If a request to a known LLM provider (e.g., api.openai.com) is
    made without a matching simulation, this error is raised immediately.

    Attributes:
        domain: The blocked domain that was accessed (e.g., "api.openai.com").
        url: The full URL of the blocked request.
    """

    def __init__(self, domain: str, url: str) -> None:
        """Initialize with domain and URL information.

        Args:
            domain: The blocked domain (e.g., "api.openai.com").
            url: The full URL of the blocked request.
        """
        self.domain = domain
        self.url = url
        super().__init__(
            f"Unmatched request to '{domain}' would hit real API: {url}\n\n"
            "To fix:\n"
            "  1. Add simulation: construct.simulate_llm(provider=..., response=...)\n"
            "  2. Or allow real calls: Construct(allow_real_llm_calls=True)"
        )

__init__

__init__(domain: str, url: str) -> None

Initialize with domain and URL information.

Parameters:

Name Type Description Default
domain str

The blocked domain (e.g., "api.openai.com").

required
url str

The full URL of the blocked request.

required
Source code in tenro/errors/base.py
def __init__(self, domain: str, url: str) -> None:
    """Initialize with domain and URL information.

    Args:
        domain: The blocked domain (e.g., "api.openai.com").
        url: The full URL of the blocked request.
    """
    self.domain = domain
    self.url = url
    super().__init__(
        f"Unmatched request to '{domain}' would hit real API: {url}\n\n"
        "To fix:\n"
        "  1. Add simulation: construct.simulate_llm(provider=..., response=...)\n"
        "  2. Or allow real calls: Construct(allow_real_llm_calls=True)"
    )

TenroUnusedSimulationError

Bases: TenroVerificationError

Raised when a simulation was registered but never triggered.

The simulate_llm() call set up a simulation, but the code path that would trigger it was never executed.

Source code in tenro/errors/base.py
class TenroUnusedSimulationError(TenroVerificationError):
    """Raised when a simulation was registered but never triggered.

    The `simulate_llm()` call set up a simulation, but the code path that would
    trigger it was never executed.
    """

TenroUnusedSimulationWarning

Bases: TenroWarning

Emitted when a simulation was registered but never triggered.

The simulation was set up but the code path that would trigger it was never executed. Use optional=True to suppress this warning for intentionally optional simulations.

Source code in tenro/errors/warnings.py
class TenroUnusedSimulationWarning(TenroWarning):
    """Emitted when a simulation was registered but never triggered.

    The simulation was set up but the code path that would trigger it
    was never executed. Use `optional=True` to suppress this warning
    for intentionally optional simulations.
    """

TenroValidationError

Bases: TenroError

Raised when API usage or parameters are invalid.

Source code in tenro/errors/base.py
class TenroValidationError(TenroError):
    """Raised when API usage or parameters are invalid."""

TenroVerificationError

Bases: AssertionError

Raised when test verification fails.

Inherits from AssertionError so pytest shows FAIL (not ERROR), matching expected test semantics.

Source code in tenro/errors/base.py
class TenroVerificationError(AssertionError):
    """Raised when test verification fails.

    Inherits from AssertionError so pytest shows FAIL (not ERROR),
    matching expected test semantics.
    """

TenroWarning

Bases: UserWarning

Base warning for all Tenro SDK warnings.

Use this to filter all SDK warnings at once.

Example

import warnings warnings.filterwarnings("ignore", category=TenroWarning)

Source code in tenro/errors/warnings.py
class TenroWarning(UserWarning):
    """Base warning for all Tenro SDK warnings.

    Use this to filter all SDK warnings at once.

    Example:
        >>> import warnings
        >>> warnings.filterwarnings("ignore", category=TenroWarning)
    """

warn

warn(message: str, category: type[Warning] = TenroWarning, *, stacklevel: int = 2) -> None

Emit a Tenro warning pointing to the caller's location.

Parameters:

Name Type Description Default
message str

Warning message to display.

required
category type[Warning]

Warning category class. Defaults to TenroWarning.

TenroWarning
stacklevel int

Stack frames to skip. Default (2) points to the caller of the function that calls warn(). Increase for nested helpers.

2
Source code in tenro/errors/warnings.py
def warn(
    message: str,
    category: type[Warning] = TenroWarning,
    *,
    stacklevel: int = 2,
) -> None:
    """Emit a Tenro warning pointing to the caller's location.

    Args:
        message: Warning message to display.
        category: Warning category class. Defaults to TenroWarning.
        stacklevel: Stack frames to skip. Default (2) points to the caller
            of the function that calls warn(). Increase for nested helpers.
    """
    _warnings.warn(message, category, stacklevel=stacklevel)

See also