Studio previewSelf-host Studio locally.Run it

Handle Errors and Recovery

Updated 1 day ago • September 14, 2026

Kortyx exposes stable failure codes and a JSON-safe FailureDescriptor. Use the code and metadata to make recovery decisions. Messages explain a failure to a person; they are not a classification API.

Import server helpers from kortyx, or use the browser-safe @kortyx/core/errors entry point. kortyx/browser also exports the error helpers. KortyxError, DomainError, ValidationError, ProviderRequestError, ProviderConfigurationError, WorkflowContractError and PersistenceError extend Error.

Inspect a failure

import { serializeFailure } from "kortyx"; try { await performWork(); } catch (error) { const failure = serializeFailure(error); console.error(failure.code, failure.message); // Decide recovery using failure.code, status and retryable. }

serializeFailure propagates cancellation, limits and graph interrupts instead of turning them into ordinary failures. It bounds normal error details and never reads arbitrary getters, response bodies or exception properties. Unknown exceptions produce EXECUTION_FAILED with a generic safe message. Live errors retain their original .message and .cause for trusted server code. JSON.stringify of Kortyx error classes uses the safe descriptor.

Descriptors contain version: 1, code, category, message, and retryable. Retry eligibility is true, false, or null when unknown. Optional fields include status, retryAfterMs, source, operation, issues, usage, finishReason, context, cause, domainCode, details, and ordered aggregate results.

Use isFailureDescriptor and errorFromFailure for values crossing JSON boundaries. Restoring a descriptor does not recreate an application's original error class or raw cause.

CodeMeaning
PROVIDER_CONFIGURATIONInvalid provider settings or unavailable credentials
PROVIDER_HTTP_ERRORHTTP rejection; inspect status and available retryAfterMs
PROVIDER_TRANSPORT_ERRORA recognized network/transport exception
PROVIDER_REQUEST_FAILEDAn unclassified provider request failure
PROVIDER_INVALID_RESPONSEMalformed provider output or protocol data
PROVIDER_REFUSALProvider refusal; do not treat it as a schema correction request
MODEL_OUTPUT_SCHEMAModel JSON does not satisfy useReason.outputSchema
INVALID_MODEL_JSONThe model did not produce valid JSON
OUTPUT_TRUNCATEDAvailable finish metadata identifies an output length limit
SCHEMA_VALIDATION, INVALID_OUTPUTHook or workflow contract validation failed
WORKFLOW_CONTRACT_ERRORChild registration, schema, call identity or replay contract failed
DOMAIN_ERRORApplication-declared safe domain failure; inspect domainCode
PARALLEL_FAILEDA parallel group failed; inspect its ordered results
PERSISTENCE_ERROR, RESUME_FAILEDStore operation or resume failed
INVALID_REQUEST, INVALID_INPUT, INVALID_RESUMEA command or input is invalid
NOT_FOUND, UNKNOWN_WORKFLOWRequested checkpoint or workflow is unavailable
MALFORMED_STREAM, TRUNCATED_STREAMResponse stream could not be consumed correctly
EXECUTION_FAILEDAn unknown exception; consult trusted server diagnostics

Existing execution request codes such as MISSING_CONTRACT and CONTRACT_MISMATCH remain supported. Consumers should tolerate unknown future codes.

Choose recovery deliberately

Kortyx does not start a retry or correction because metadata is available. Applications choose which operations may be repeated, their attempt budget, delay and idempotency protection. HTTP 408, 429 and 5xx are transient candidates; known terminal quota/authentication reasons can override that classification. Missing metadata is not permission to retry.

Use separate counters for transport retries and structured corrections. For example, a policy can permit one retry for a transient provider HTTP failure and one correction for MODEL_OUTPUT_SCHEMA. A correction changes the prompt using the expected schema and available validation issues; repeating the same request is a transport retry. The live ValidationError.issues and .cause retain original validation information. Persisted issues omit custom messages and replace string field names with [field] to avoid exposing data-dependent schema paths. Numeric path positions and issue codes remain available.

Every actual model dispatch consumes the existing shared execution allowance. A correction does not grant another allowance. Available usage on failed or invalid output is recorded once. Refusals, authorization failures, cancellations and human/limit pauses must not enter a correction loop. withRetries excludes control flow and supports abortSignal, but its default policy retries ordinary errors; use its retryOn predicate for selective recovery. Node behavior.retry is also a broad ordinary-error retry policy.

Preserve domain failures across child calls

import { DomainError, serializeFailure, useWorkflow } from "kortyx"; // In a specialist: these fields are explicitly approved for persistence/display. throw new DomainError("SPECIALIST_UNAVAILABLE", "Research is unavailable.", { details: { specialist: "research" }, }); // In its caller: try { await useWorkflow({ id: "research-attempt-1", workflow: research, input }); } catch (error) { const failure = serializeFailure(error); if (failure.domainCode !== "SPECIALIST_UNAVAILABLE") throw error; // Apply the application's fallback policy. }

WorkflowCallError retains the original cause during the current execution and a safe failure descriptor after checkpoint restore. ParallelError.results retains normal fulfilled/rejected entries in input order. Its serialized descriptor retains every ordered status and rejected failure within the existing 64-call limit; successful values remain on the live results and child checkpoints rather than being copied into diagnostic payloads.

Failed child calls are cached. An intentional new attempt uses a distinct stable ID, such as research-attempt-2, under a bounded application policy. Old message-only checkpoints can resume, but their discarded metadata cannot be recovered or classified from prose.

Transports, telemetry and compatibility

Failed agent.execute/agent.resume outcomes include safe structured fields in result.error. HTTP failures retain the legacy string error and add failure. Error stream chunks retain message and add failure. React keeps these fields on its error object and error content pieces; message-only legacy servers remain supported. Buffered responses retain { chunks, text, structured }.

Request validation errors use client-error statuses; unexpected server errors default to HTTP 500. An explicit route errorStatus override remains authoritative. A provider's HTTP status describes the upstream request and does not automatically become the application's HTTP status. Once SSE headers are sent, failures travel as stream events. Partial output followed by an error is still a failed response; done means stream termination, not business success.

Malformed stream events surface a typed failure without logging their payload. Intentional cancellation remains separate from network failures. Studio/telemetry show safe failure codes and context, while historical name/message events continue to render. OTel cancellation and suspension do not set error status. Reporter or cleanup failures must not replace an existing primary failure.

Upgrade dependent SDK/provider/runtime packages together. Mixed old/new workers and legacy checkpoints do not establish recovery parity. Safe generic messages, corrected default HTTP statuses and explicit malformed-stream failures are observable changes; replace tests that assert raw public error text with code/metadata assertions. Keep secrets out of DomainError messages/details: constructing one is an explicit declaration that those fields are safe to persist.