dendrux
v0.4.0Get started →

The public exception hierarchy Dendrux raises, when each fires, the constructor arguments, and a suggested HTTP status mapping for your write routes.

Errors

Dendrux's runtime methods raise typed exceptions on precondition failures. You catch them in your own routes (or CLI, or jobs) and map them to status codes. Dendrux never forces a status-code contract.

from dendrux.errors import (
    RunNotFoundError, RunNotPausedError, PauseStatusMismatchError,
    RunAlreadyClaimedError, RunAlreadyTerminalError, InvalidToolResultError,
    PersistenceNotConfiguredError, SchemaNotInitializedError, SchemaOutOfDateError,
    SkillValidationError, SkillMissingError, SkillMismatchError,
    ContextLengthExceededError,
    ImageError, InvalidImageError, ImageLimitError, ImageFetchError, UnsupportedModalityError,
    ImageRejectedError, ProviderError, BlobNotFoundError, BlobIntegrityError,
    ImagePersistenceUnsupportedError, BlobContentionError,
)

The most common ones are also re-exported from the top-level dendrux package.

The hierarchy

ExceptionBaseRaised whenSuggested HTTP
RunNotFoundError(run_id)LookupErrorrun_id does not exist in the store.404
RunNotPausedError(run_id, current_status)RuntimeErrorA submit method is called on a run that is not paused.409
PauseStatusMismatchError(run_id, current_status, expected_status)RuntimeErrorSubmit called for the wrong pause type (e.g. submit_tool_results on waiting_human_input).409
RunAlreadyClaimedError(run_id)RuntimeErrorA concurrent submit already won the CAS claim on this pause.409
RunAlreadyTerminalError(run_id, current_status)RuntimeErrorA submit method, or submit_note, targets a run that is already terminal; also a submit on a run whose cancellation was requested but not yet observed. (cancel_run and request_pause do not raise this.)409
InvalidToolResultError(run_id, detail)ValueErrorSubmitted tool results do not match the pending set (unknown or duplicate ids).400
PersistenceNotConfiguredError()RuntimeErrorA method needs persistence but no database_url / state_store / DENDRUX_DATABASE_URL is configured.500
SchemaNotInitializedError()RuntimeErrorThe database has no Dendrux tables yet (non-SQLite needs dendrux db migrate).500
SchemaOutOfDateError(missing_tables, missing_columns)RuntimeErrorA non-SQLite database is missing tables or columns this version needs, because dendrux db migrate was not run after upgrading. Raised once per engine, before the first run (Agent, sweep, dashboard; RunStore is not checked). Only presence of tables and columns is verified, not their types or defaults. Carries err.missing_tables, err.missing_columns ("table.column") and err.missing. Set DENDRUX_SKIP_SCHEMA_CHECK=1 to skip the check. See Upgrading.500
ContextLengthExceededError(message, status_code=, provider_code=, model=)RuntimeErrorAn OpenAI-compatible endpoint (OpenAIProvider, OpenRouterProvider) rejected a batch or streaming request as larger than the model's context window. The SDK error is chained as __cause__; the message never contains prompt content. See Models and providers.413 or 400
ImageError(ValueError)ValueErrorBase class of InvalidImageError, ImageLimitError and ImageFetchError; catch it for any bad image input.400
InvalidImageError(reason=, index=, media_type=, byte_length=)ImageError (ValueError)An image failed validation at ingest: reason is unsupported_type, type_mismatch, corrupt, animated, empty, purged, descriptor_version, descriptor_mismatch, bad_base64 or path. See Images.400
ImageLimitError(scope=, limit=, actual=, index=)ImageErrorAn image cap was exceeded (image_bytes, source_bytes, image_side, image_pixels, message_count, request_count, request_bytes, estimated_tokens, many_image_dimension, and store_bytes for a full in-memory store).413
ImageFetchError(reason=, host=, status_code=)ImageErrorImage.from_url was refused or failed (scheme, port, credentials, host_not_allowed, blocked_address, dns, redirect, timeout, too_large, http_status, content_encoding, disabled, connect). Carries the host only, never the path or query.502 for http_status and timeout; otherwise 400
UnsupportedModalityError(provider=, model=, source=, ...)RuntimeErrorThe model cannot take images. source is capability, catalog, adapter or provider_response.400
ImageRejectedError(image_ids=, located=, retried=)RuntimeErrorThe provider refused a specific image.422
ProviderError(provider=, model=, status_code=, provider_code=, message=)RuntimeErrorAny other provider failure on a request that carried images; sanitised, at most 500 characters. Text-only requests keep their existing errors.502
BlobNotFoundError(image_id)LookupErrorThe blob is missing, belongs to another tenant, or the run has no reference to it. The same error for all three.404
BlobIntegrityError(image_id, check=)RuntimeErrorStored bytes or a persisted descriptor do not match the catalog.500
ImagePersistenceUnsupportedError(reason=)RuntimeErrorThe state store has no blob catalog, an external blob row is read with no blob_store, or a tool returned images in a loop run without an image runtime.500
BlobContentionError(image_id)RuntimeErrorImage storage lost a race with a concurrent purge or garbage collection. Transient; safe to retry.503
SkillValidationError(message, field=)ValueErrorA skill is built with an invalid name, description, file path, metadata, or frontmatter. field names the offender.400
SkillMissingError(run_id, missing=, mismatched=)SkillContinuityErrorA paused run is continued on an agent that lacks a skill the run was created with. The run stays paused.409
SkillMismatchError(run_id, mismatched=)SkillContinuityErrorEvery skill is attached, but at least one has different content (digest) than the run was created with. The run stays paused.409

None of the image errors contain image bytes, base64 or a URL's query string, and they are raised without a chained exception. Each carries its arguments as attributes (err.run_id, err.current_status, etc.) for structured handling. ContextLengthExceededError carries err.status_code, err.provider_code and err.model. SkillMissingError and SkillMismatchError share the base class SkillContinuityError (a RuntimeError) and expose err.missing and err.mismatched; see the resume contract.

Also exported from dendrux

A few related exceptions live on the package root rather than dendrux.errors:

ExceptionRaised when
RunAlreadyActiveErrorAn idempotent create finds an already-active run.
IdempotencyConflictErrorAn idempotency_key collides with a different request fingerprint.
StructuredOutputValidationErrorA SingleCall output_type fails validation.
DelegationDepthExceededErrorA sub-agent exceeds max_delegation_depth.

Errors raised by the new run-control methods

The 0.3.0 methods reuse the hierarchy above. These are the cases worth knowing:

CallRaises
submit_approval(decisions=...)ValueError for both or neither of approved= / decisions=, for rejection_reason with decisions, and for a missing, unknown or duplicate call id. Raised before anything is claimed.
submit_resume(run_id)PauseStatusMismatchError when the run is paused for another reason (for example waiting_approval), plus the usual resume errors.
request_pause(run_id)RunNotFoundError, ValueError for a bad reason. A waiting_*, paused or terminal run is a no-op, not an error.
submit_note(run_id, text)ValueError for empty or oversized text or bad metadata, RunNotFoundError, RunAlreadyTerminalError.
cancel_run(..., cascade=True), request_pause, submit_note, RunStore.get_notes or RunStore.get_subtree on a custom pre-0.3 StateStoreNotImplementedError naming the missing store method. Existing flows on such a store keep working.
RunStore.get_subtree / get_notes(tenant_id=...)dendrux.store.RunNotFoundError, a separate class from dendrux.errors.RunNotFoundError.
MCPConnection.call_toolMCPToolNotFoundError for an unlisted tool name, MCPToolCallError for a server error result, TimeoutError when timeout= expires. See MCP.
Agent(before_tool=...) with SingleCallValueError at construction.
A delegated run with a tenant_id different from its parent'sValueError before the child run is created.

MCP errors

MCP tool sources raise their own typed hierarchy under MCPError, importable from dendrux.mcp. The ones an application should be ready to handle — including MCPCircuitOpenError (fail-fast with retry_after), the capacity errors, and MCPOutcomeUnknownError (interrupted call the server may have applied; never retry automatically) — are tabulated with their semantics on the MCP page.

Example: mapping errors in a write route

from fastapi import APIRouter, Depends, HTTPException
from dendrux.errors import (
    PauseStatusMismatchError, PersistenceNotConfiguredError,
    RunAlreadyClaimedError, RunAlreadyTerminalError,
    RunNotFoundError, RunNotPausedError,
)
 
@router.post("/runs/{run_id}/approval")
async def approval_route(run_id: str, body: dict, _=Depends(authorize)):
    try:
        result = await agent.submit_approval(
            run_id, approved=body["approved"], rejection_reason=body.get("rejection_reason"),
        )
    except RunNotFoundError:
        raise HTTPException(404, "run not found")
    except (RunNotPausedError, PauseStatusMismatchError,
            RunAlreadyClaimedError, RunAlreadyTerminalError) as e:
        raise HTTPException(409, str(e))
    except PersistenceNotConfiguredError:
        raise HTTPException(500, "persistence not configured")
    return {"status": result.status.value, "answer": result.answer}

Where this fits