Skip to content

Upgrading from v0.25.0 to v0.26.0

v0.26.0 improves shared-client ownership, persistence failures, provider compatibility, admission, lifecycle handling, and diagnostics. Review the changes below before upgrading: several defaults and accepted inputs change. Existing JSONL and SQLite schemas remain compatible; no schema migration is required. Recovery of incomplete JSONL tails changes as described below.

Inputs, configuration, and public names

  • Pass an LLMCallStrategy instance to work items and convenience entry points. Duck-typed strategies must inherit that interface or use CallableStrategy. Wrap model clients with OpenAIStrategy and agents with PydanticAIStrategy.
  • A CallableStrategy callback must accept the prompt plus attempt, timeout, and state keywords. An inspectable incompatible signature now fails at construction.
  • Pass an iterable of strings or (item_id, prompt[, context]) tuples to process_prompts/process_stream. A top-level string, bytes object or mapping is rejected. Bare integer entries still become strings with a deprecation warning; convert them explicitly. Other invalid entries report their index.
  • Duplicate IDs in process_prompts warn once. Use unique IDs or submission_index when distinguishing results; IDs are not ordering keys.
  • Numeric configuration fields reject booleans and non-finite values. Integer fields also reject integral floats: use max_workers=5, not max_workers=5.0.
  • A bare ExponentialBackoffStrategy() now starts at 300 seconds, with multiplier 1.5, matching RateLimitConfig (previously 60 seconds and 2.0). To preserve the former settings, pass initial_cooldown=60, backoff_multiplier=2.0 explicitly.
  • LLMCallPool is now the class name and capacity-warning label. LLMGateway remains an alias. Code that inspects the class name or matches warning text should update.

CallOutcome.token_usage accepts either input_tokens / output_tokens or OpenAI's prompt_tokens / completion_tokens. Do not mix naming schemes. Stored counters use canonical names. testing.FakeStrategy and FakeRateLimitError provide provider-free success, latency and failure simulation; see the testing guide.

Processor and shared-model lifecycle

Use each processor for one batch or streaming run. Mixing start() and process_all(), restarting after close, or calling finish() after closure without an earlier finish now raises. Repeated start() while streaming and repeated finish() following a normal finish remain no-ops. Use a new processor for the next run.

add_work() raises BatchAdmissionClosedError after finish, shutdown or abort. Blocked producers are woken when admission closes. A concurrently committed enqueue is retained; an uncommitted submission is rejected without a submission index. For multiple producers, join them before calling finish().

Overlapping hosts on the same event loop share strategy preparation and release leases on cleanup; actual cleanup waits for the last owner. Ordinary preparation failures are shared with waiting hosts. Built-in owned OpenAI-compatible and Gemini models also protect clients shared by distinct strategies. Sequential reuse reopens owned clients. This changes prepare/cleanup call counts for shared instances; hooks should remain idempotent.

Caller-provided OpenAI-compatible HTTP transports are no longer closed by model cleanup; the caller must close them. Automatic connection-pool resizing is refused once provider execution has begun, including after cleanup until reopening. Choose capacity up front. Always close hosts explicitly: garbage collection cannot perform asynchronous cleanup. See the cleanup contract for abandoned-lease behavior.

Provider retries, categories, and token usage

OpenAI-compatible from_api_key() clients now default to max_retries=0, so ABL owns retry counts and token accounting. Explicit SDK retry settings still win and survive reopening. Applications that relied on layered SDK retries should choose that intentionally.

Audit category-based metrics and abort_on_error_categories:

  • Provider timeouts preserve the provider exception and category rather than becoming framework attempt timeouts. Default, OpenAI and OpenRouter classifiers use api_timeout; Gemini recognizes a bare TimeoutError as timeout with is_timeout=True.
  • Terminal rate-limit budget exhaustion uses rate_limit_retries_exceeded and is no longer marked as a rate-limit event. Empty responses use empty_response.
  • Gemini structured-output failures use the shared structured-output categories.
  • OpenAI insufficient_quota and unambiguous Gemini daily quota exhaustion are not retried. PydanticAI HTTP failures use status classification, including explicit insufficient quota.
  • OpenRouter “No allowed providers” is retryable only with status 502/503; other statuses use client_error.
  • A broken custom classifier produces a non-retryable classifier_error item; the result retains the originating provider exception.

Gemini output totals now include thinking tokens, also exposed as metadata["reasoning_tokens"]; input totals include tool-use prompt tokens. Token totals can therefore increase without an increase in visible response text.

Gemini caches are matched using normalized request-content fingerprints and provider expiry. Existing caches without the reserved abl-content tag are not adopted: first use can create an additional paid cache. Escaped display names longer than 128 characters are rejected. A narrowly recognized missing-cache response triggers one replacement/retry.

Admission and deadlines

The optional ProcessorConfig.quota_burst_seconds limits short bursts; leaving it unset preserves the full-minute default. Oversized TPM reservations may borrow future refill. See token-aware admission for bounds and reconciliation.

A cooldown detected just before provider start releases unstarted quota/capacity and re-enters FIFO admission without consuming an attempt. Admission events can repeat during this process. Stale-generation 429s no longer escalate an already newer cooldown. An item whose rate-limit budget is exhausted returns without waiting for the shared cooldown.

When the actual retry delay cannot fit the remaining item deadline, ABL returns ItemDeadlineExceeded immediately. Its message/cause preserve the last provider error; the attempt timing retains the original category, while abort matching uses the final framework_total_item_timeout category. Item deadlines start separately at logical-item pickup, so an item can legitimately start after a peer times out if its own budget remains.

Artifacts, recovery, and events

JSONL inspection remains read-only and excludes all unterminated tails. Writable resume truncates them, even when the final bytes form complete JSON without the terminating newline. A recognizable partial initial manifest can be recovered; unrelated files are not truncated. After a write failure, further appends fail closed. JSONL remains single-writer only.

Built-in stores convert record serialization failures into item-level artifact_serialization_error results, keeping the output in memory and writing a minimal, non-replayable fallback record. Invalid data includes lone Unicode surrogates in outputs, metadata or context; diagnostics identify the offending path. Stored error text escapes such surrogates. I/O and format failures remain fatal; guardrail audit rules are unchanged. Custom stores retain their existing fatal serialization behavior.

Serialization fallback error_counts now use ArtifactSerializationError, rather than a separate key containing each item's message. Decoders run only for successful, included, non-null output and for non-null context. Replayed output without a decoder is JSON data; it can differ in type from a fresh typed model. Use an encoder/decoder pair when typed reconstruction is required; see results and artifacts.

Each finalized item emits exactly one ITEM_COMPLETED, ITEM_FAILED, or ITEM_REPLAYED after checkpointing and before fail-fast BATCH_ABORTED. Filtering and failed middleware recovery count as failures. Fatal checkpoint errors or cancellation before finalization emit no terminal item event. Replay emits only ITEM_REPLAYED; replayed failures contribute their error type to metrics. Live provider-success event duration/tokens keep final-attempt semantics. See the observer contracts.

Middleware recovery includes failed-attempt tokens in addition to usage supplied by the hook; do not return those same failed tokens again. Invalid after_process return types now preserve the successful result with a warning rather than repeating the provider call.

Diagnostic tracebacks and bounded logging

Batch failures log diagnostic user-code stacks before detaching traceback frames. call, call_result and LLMCallPool retain original frames, so holding failed results can also retain callback locals. Routine retryable network failures do not gain diagnostic stacks.

Repeated terminal errors share a diagnostic and summary. Tracking is bounded to 1,000 keys with 200-character message prefixes; overflow errors remain visible at ERROR without being retained. Messages differing only after character 200 deduplicate. See callable integration.

Explicitly deferred follow-ups

These existing behaviors are not changed in v0.26.0:

Since v0.27: #177, #178 and #181 are fixed. See the v0.27 migration guide.

  • #177: default message-based classification can mistake a numeric 429 in user error text for a rate limit. Supply a custom classifier when application exceptions need stricter classification.
  • #178: REUSE_ALL can replay token_estimator_required failures after configuration is corrected. Use REUSE_SUCCESSES to re-execute failures when resuming that run.
  • #181: an item expiring while waiting for capacity can report zero admission wait and no timing timeout category. Its final framework_total_item_timeout error remains correct.
  • Newer SDK coverage and refactoring follow-ups are tracked in #176, #179, and #180. Dependency PR #166 is deferred; this release keeps the reviewed dependency set and existing OpenAI/OpenRouter SDK floors.

For upgrades from before v0.25, also read the v0.25 and v0.24 migration guides.