Skip to content

Concurrency and Promises

Configure Parallel Execution

Python API execution is sequential by default. Enable promise-pipelined template calls globally:

kedi.configure(parallel=True, max_workers=8)

or temporarily:

with kedi.context(parallel=True, max_workers=4):
    result = run_pipeline("...")

Parallelism changes scheduling, not language results. If sequential and parallel execution produce different values, that is a runtime bug rather than a supported race-dependent mode.

The parallel() Context

kedi.parallel() is shorthand for a context override:

with kedi.parallel(max_workers=6):
    result = analyze("...")

Equivalent:

with kedi.context(parallel=True, max_workers=6):
    result = analyze("...")

The previous configuration is restored on exit.

Worker Limits

max_workers bounds the shared thread pool used for independent model calls. Pools are process-wide and reused by worker count. Use a positive value.

The CLI/runtime environment also recognizes KEDI_PARALLEL:

  • unset, empty, 0, false, no, or off: sequential;
  • 1, true, yes, or on: parallel with eight workers;
  • a positive integer: parallel with that worker count;
  • a negative integer: sequential;
  • another value: configuration error.

An explicit Python API parallel setting supplies the engine used by that decorated run.

KediPromise

In parallel mode, an output field is initially represented internally by a KediPromise view over the template's shared future:

from kedi import KediPromise

One model call with several output fields creates one future and one promise view per field. Downstream templates receive dependency edges without blocking the scheduling thread.

Normal substitutions, procedure calls, Python name access, and final query returns resolve at genuine consumption points. Most application code should never observe a promise.

Force a Promise

Resolve an intentionally obtained low-level promise:

from kedi import force

value = force(possible_promise)

force() calls .resolve() only for KediPromise; every other value passes through unchanged. promise.map(fn) creates a derived promise using a completion callback without consuming another worker slot.

Promise Leaks

Promises are deliberately opaque. Using an unresolved promise through str(), an f-string, comparison, indexing, iteration, arithmetic, attribute access, hashing, or a call raises KediPromiseLeak.

This is not a normal “value is not ready” condition. It means a promise escaped runtime deferral and reached a concrete-value operation. The loud failure prevents silent serialization or subtly wrong results.

repr(promise) is safe and never resolves. Copy/deep-copy preserves identity; pickling an unresolved promise raises.

Dataflow Resolution

Kedi snapshots the value environment by value when a template is scheduled. Later writes on the main thread do not change that job's inputs.

Inside embedded Python, ordinary bare-name lookup resolves a promise. Advanced non-resolving dictionary operations such as globals().get("name"), globals().items(), globals().values(), and dict(globals()) can expose the raw promise for forwarding. Call kedi.force(...) before concrete use.

At the outermost return, Kedi drains all scheduled jobs, including unconsumed template calls. The first failure is raised; additional concurrent failures are logged.

Thread Safety

Parallel mode may call an adapter's synchronous production path concurrently from multiple worker threads. Custom adapters must be thread-safe or serialize internally.

Python tools can also run under adapter concurrency. Protect mutable shared state explicitly. Do not rely on the GIL as an application-level consistency guarantee.