Errors
Every error the framework raises subclasses LoomError. The
hierarchy lets you catch broadly (except LoomError) or
narrowly (except RateLimitError) without surprises.
LoomError
├── ConfigError (Agent / Memory / Model misconfiguration)
├── ModelError
│ ├── TransientModelError (retried automatically)
│ │ ├── RateLimitError (HTTP 429, Retry-After honored)
│ │ └── ... (5xx, network blips, timeouts)
│ ├── PermanentModelError (NOT retried)
│ │ ├── AuthenticationError (HTTP 401)
│ │ ├── InvalidRequestError (HTTP 400 — bad prompt or args)
│ │ └── ContentFilterError (provider safety filter)
│ └── OutputValidationError (output_schema validation failed)
├── ToolError (a tool raised — wrapped into ToolResult.error)
├── PermissionDenied (permissions returned deny_)
├── BudgetExceeded (per-run or per-user cap hit)
├── MemoryStoreError (Memory backend failure)
├── RuntimeJournalError (Runtime journal failure)
├── SandboxError (FilesystemSandbox / SubprocessSandbox blocked the call)
├── MCPError (MCP server failure)
├── FreshnessError (data lineage policy violation)
├── LineageError (data lineage policy violation)
└── CancelledByUser (user cancelled the run)What raises what
ConfigError
Misconfigured Agent. Examples:
Agent("...")withoutmodel=(since 0.2).Agent("...", model="bad-spec")for an unknown string.Agent("...", architecture="bogus")for an unknown architecture.
Catch at startup; not retryable.
ModelError and subclasses
Raised by the model adapter. The framework’s RetryPolicy retries
TransientModelError automatically (3 attempts default).
PermanentModelError is fail-fast.
RateLimitError. Provider returned 429. The retry honors anyRetry-Afterheader.AuthenticationError. Your API key is wrong / missing / revoked.InvalidRequestError. Request body malformed (oversize prompt, bad tool definition, etc.).ContentFilterError. Provider’s safety filter rejected the request.OutputValidationError,output_schema=Pydantic validation failed. Handled separately: the framework appends the error to the conversation and asks the model to retry.
See RetryPolicy + error taxonomy.
ToolError
A tool raised an exception. The framework wraps it into
ToolResult(ok=False, error=str(exc)) so the model sees the failure
in the next turn and can decide how to react. Your code typically
doesn’t catch ToolError directly. The agent loop handles it.
PermissionDenied
The permissions layer returned deny_(reason). The agent loop
reports this as a ToolResult with an error message; doesn’t usually
surface as a Python exception unless you opt into raising via custom
hooks.
BudgetExceeded
A per-run or per-user budget cap was exceeded. The run terminates
cleanly with result.interrupted = True, result.interruption_reason = "budget:...". You can also intercept by catching
BudgetExceeded if you need to react.
See Per-user budget caps.
MemoryStoreError
The Memory backend’s underlying store failed (Postgres connection
dropped, Redis cluster unreachable, etc.). Often transient. Your
runtime layer’s reconnect logic handles re-establishing the
connection.
RuntimeJournalError
The runtime journal failed to write. Rare; usually points at disk-full or a misconfigured DSN.
SandboxError
A sandbox blocked the call. Subclasses:
PathEscapeError,FilesystemSandboxrejected a path argument.- Subprocess timeout / pickle failure,
SubprocessSandbox.
Returned to the model as a ToolResult error; the model adapts.
MCPError
MCP server crashed or returned a malformed response. Subclasses include details. Exit code for stdio servers, HTTP status for HTTP servers.
CancelledByUser
The user broke out of the agent.stream() iterator (or otherwise
cancelled). Run terminates cleanly with interrupted=True.
Catching broadly
from loomflow import Agent, LoomError
try:
result = await agent.run("...")
except LoomError as exc:
log.error("agent run failed: %s", exc)
return user_friendly_error()Catching narrowly
from loomflow import Agent, BudgetExceeded
from loomflow.core.errors import AuthenticationError, RateLimitError
try:
result = await agent.run("...")
except AuthenticationError:
raise HTTPException(401, "API key invalid")
except RateLimitError:
raise HTTPException(429, "rate limit; retry later")
except BudgetExceeded as exc:
raise HTTPException(402, f"budget exceeded: {exc}")classify_model_error(exc)
Useful when you’re catching a raw provider exception and want the framework’s typed wrapper:
from loomflow.governance import classify_model_error
try:
response = await my_provider_call()
except Exception as exc:
typed = classify_model_error(exc)
# typed is now one of the Loom error subclasses
raise typed from excErrors vs. interruptions. A RunResult with interrupted=True
is a clean termination. Budget cap hit, user cancelled,
max_turns reached. An exception is a failure. Something the
framework couldn’t recover from. Most production code wants to
handle both: check result.interrupted after a successful return,
catch LoomError for the rest.