Skip to content

Exceptions Reference

Alquimia uses typed exceptions to signal specific failure conditions in the agent execution loop. All core exceptions carry a control_id field for log correlation — this is the same control_id that appears on the command or response that triggered the failure.


Source: src/alquimia/core/exceptions.py

Base class for all controller-level exceptions. All subclasses inherit control_id.

@dataclass
class ControllerException(Exception):
control_id: str

str(exception) returns [control_id=<id>] <ClassName> for all subclasses.


These are raised by _handle_event() when a command or response arrives that has no registered handler. They indicate a bug in the event dispatch table or an unregistered event type.

ExceptionRaised when
UnknownAgentDiscoveryReceivedAn AgentDiscovery response arrives with no matching handler
UnknownHumanApprovalReceivedA HumanApprovalRequired response arrives with no matching handler
UnknownToolExecutionReceivedA tool execution response arrives with no matching handler
UnknownResponseExecutionReceivedA ResponseInference response arrives with no matching handler
UnknownShieldExecutionReceivedA ShieldInference response arrives with no matching handler
UnknownToolSchemaReceivedA ToolSchema response arrives with no matching handler
UnknownAttachmentNormalizationReceivedAn attachment normalization response arrives with no matching handler
UnknownContextFlushExecutionReceivedA ContextFlush response arrives with no matching handler

Fields: control_id: str

Handling: These exceptions indicate a programming error, not a runtime condition. They should not be caught in application code — let them propagate and surface as 500 errors.


These are raised when a tool exceeds its configured execution limits. They protect against infinite loops and runaway tool calls.

Raised when a tool has been called more times than max_steps allows in the current evaluation strategy.

@dataclass
class MaxIterationsReachedException(ControllerException):
iterations: int # current iteration count
control_id: str
toolname: str # the tool that exceeded the limit
max_iterations: int # the configured limit

Example message:

[control_id=abc-123] Tool 'search_tickets' exceeded the maximum number of iterations —
allowed: 10, current: 11.

Raised when a tool has been called more times concurrently than max_concurrent_tools allows.

@dataclass
class MaxIterationVolumeReachedException(ControllerException):
iterations: int
max_iterations: int
control_id: str
toolname: str

Subclass of MaxIterationsReachedException. Raised when the hard iteration limit is reached — execution is permanently blocked for this tool in the current run.

Subclass of MaxIterationVolumeReachedException. Raised when the hard concurrent call limit is reached.

Handling: Catch MaxIterationsReachedException (catches both soft and hard variants) to surface a user-facing error:

from alquimia.core.exceptions import MaxIterationsReachedException
try:
response = await evaluate(controller, command, registry)
except MaxIterationsReachedException as exc:
logger.error(f"Tool {exc.toolname} exceeded iteration limit: {exc}")
# Return a graceful error to the user

Source: runtime/src/utils/exceptions.py

These exceptions are raised by the runtime’s database and storage layers.

ExceptionRaised when
EntityDoesNotExistA requested database record (agent, secret, parameter, topic, file) does not exist
EntityAlreadyExistsAn attempt is made to create a record that already exists (duplicate key)

Fields: none (plain Exception subclasses)

HTTP mapping: The runtime’s routers catch these and return appropriate HTTP status codes:

ExceptionHTTP status
EntityDoesNotExist404 Not Found
EntityAlreadyExists409 Conflict
ExceptionRaised when
FileDoesNotExistA requested file is not found in S3/blob storage
FileAlreadyExistsAn attempt is made to upload a file that already exists in storage

Fields: none

HTTP mapping:

ExceptionHTTP status
FileDoesNotExist404 Not Found
FileAlreadyExists409 Conflict

Raised when agent-to-agent (A2A) delegation exceeds the configured maximum depth.

class A2AMaxDepthReached(Exception):
pass

The maximum depth is configured via A2A_MAX_DEPTH (default 5). This prevents infinite delegation chains where Agent A calls Agent B calls Agent A.

HTTP mapping: 500 Internal Server Error with message "An internal error occurred".

Handling: If you expect deep A2A chains, increase A2A_MAX_DEPTH. If you see this in production, it likely indicates a circular delegation configuration.

Raised when an agent is temporarily unavailable — its agentspace has been put on hold.

class AgentHoldedException(Exception):
pass

HTTP mapping: 500 Internal Server Error.

Handling: Check the agentspace status in the registry. An agent on hold will not accept new inference requests until the hold is released.


All ControllerException subclasses carry control_id. This is the same ID that appears on the command that triggered the failure. Use it to find the relevant worklog record:

from alquimia.core.exceptions import MaxIterationsReachedException
from alquimia.core.worklog import Worklog
try:
response = await evaluate(controller, command, registry)
except MaxIterationsReachedException as exc:
# Find the tool execution record that triggered the limit
record = worklog.find_by_id("ServerToolExecution", exc.control_id)
logger.error(f"Tool exceeded limit. Last call data: {record.data}")

  • evaluate() — where most exceptions surface
  • Event Model — the commands and responses that carry control_id
  • Observability — correlating exceptions with traces and logs