Bulkhead & Rate-Limit Headers

resilience recovers from failures and rate_limit paces calls, but nothing capped the number of simultaneous in-flight calls to one resource (so a slow dependency could exhaust every worker), and the HTTP client read Retry-After / RateLimit-* response headers but honored none of them. This adds a bulkhead (bounded-concurrency permit with load-shedding) and a parser for the server’s advised delay.

Pure standard library (threading + email.utils); the permit counting is non-blocking (reject when full), so it is deterministic and CI-testable without spawning threads. Imports no PySide6.

Headless API

from je_auto_control import Bulkhead, BulkheadFullError, next_delay

payments = Bulkhead(max_concurrent=4, name="payments")
try:
    result = payments.run(call_payment_api, order)
except BulkheadFullError:
    defer(order)               # shed load instead of piling on

# honor the server's back-off after an HTTP call
wait = next_delay(response)     # from Retry-After / RateLimit-* headers
if wait:
    sleep(wait)

Bulkhead caps simultaneous holders to max_concurrenttry_enter / release, a context manager, and run(func) all reject (BulkheadFullError) when full, isolating one slow dependency from exhausting the rest. parse_retry_after understands both the delta-seconds and HTTP-date forms; parse_ratelimit reads the RateLimit-Limit/Remaining/Reset convention; next_delay combines them into the wait a flow should observe after a 429 / 503.

Executor commands

AC_bulkhead_run runs an actions list under a named bulkhead (name, max_concurrent) and returns {entered, in_flight, record?}. AC_retry_after takes an HTTP response ({status, headers}) and returns {delay}. Both are exposed as MCP tools (ac_bulkhead_run / ac_retry_after) and as Script Builder commands under Flow.