STATEK Futures
A STATEK future is a persisted placeholder for a value that may not be ready yet.
Futures are lower-level durable waiting machinery. They let Python execution say: "this value is not available now; suspend this job and resume from here later." When the value becomes ready, the same job can continue with the same Python variables and job history.
Use futures carefully. The current implementation checks readiness by polling suspended jobs. That is simple and useful, but it is not the right default for high-scale or low-latency external event delivery. Prefer callbacks and events for human input, webhooks, external completions, and product-facing interruptions unless a future is specifically the right fit.
STATEK futures currently use polling to check suspended jobs. Keep future conditions cheap, control how many jobs can suspend this way, and prefer callbacks/events when wait latency or large-scale event delivery is critical.
Where Futures Fit
Callbacks and events are usually the public product-level model:
message = incoming_user_message
approval = incoming_human_approval
export_result = incoming_webhook_resultFutures are the execution-level model:
approval = wait_for_human_approval(user, summary)
if approval.accepted:
meeting.move_to(proposed_slot)If approval is not ready, the job can suspend. STATEK stores where the wait happened. Later, when the future is ready, the job continues from that point instead of rerunning earlier Python instructions.
That makes futures useful when waiting is part of the agent's Python execution itself.
A Useful Warmup Pattern
Futures are especially useful in warmup code where an agent job is responsible for picking the next task from a queue.
Conceptually:
task = pick_next_task_from_queue()
user = task.user
message = task.message
timestamp = task.created_at
calendar = user.calendarIf pick_next_task_from_queue() is temporal and no task is ready, the warmup can suspend. That works well when the framework controls the number of jobs that are allowed to call and suspend this way. The system can orchestrate a bounded fleet of waiting agent jobs instead of letting arbitrary Python code block a process.
This is different from using futures for every external event. For broad user-facing event delivery, callbacks and queues are usually the better first choice.
For a fuller startup example, including multi-block warmup and the queue-picking caveat, see Warmup Code.
FutureResult
FutureResult represents a pending result.
At a high level, it carries:
deps: the object or objects whose state determines readinessstate_num: the state boundary used to decide what changed- a condition function that checks whether the future is ready
- a complement function that fetches the result once it is ready
Conceptually:
export = start_export(dataset)
report = build_report(export)If export is a future and build_report(export) needs its value, accessing that value can suspend the job. The future is persisted, and the job records what it was waiting on.
Suspended Jobs
When a future is not ready, STATEK can suspend the job.
The job stores:
awaited_result: theFutureResultit is waiting fornext_instr_num: the Python instruction where execution should continue- status
SUSPENDEDwhen normal started execution pauses
The job loop can later check suspended jobs. If awaited_result.check_condition() returns ready, the job moves back to STARTED.
The important behavior is continuation. STATEK records the instruction where the wait happened, so instructions that already ran are not repeated.
counter = counter + 1
result = future_value
after = TrueIf future_value is not ready, counter = counter + 1 should not run again when the job resumes. The job continues from the stored point.
Collecting Future Values
A future behaves like a result once it is ready.
result = future_value
print(result)If the result is not ready, the access suspends the job. If it is ready, execution continues normally.
Tuple-shaped future results can be unpacked conceptually:
left, right = get_two_values()
print(left, right)STATEK represents each unpacked element as a future element tied back to the parent future, so the job can still wait until the underlying result is ready.
Waiting for Any or All
Some waits involve more than one possible future.
Use an "any" shape when the job can continue after the first result:
first_result = wait_for_any(search_a, search_b, search_c)Use an "all" shape when the job needs every result:
all_results = wait_for_all(fetch_profile, fetch_calendar, fetch_preferences)STATEK has combined future machinery for these patterns. Keep the same performance rule in mind: combined future readiness is still checked through polling, so each condition should be cheap and the number of suspended jobs should be controlled.
Advanced: Temporals
@temporal, FutureResult.value, FutureResult.check_condition(), get_any_future(...), get_all_future(...), tuple unpacking, and @temporal(extends=...) are covered in Temporal Tools.
Keep the same positioning: temporal tools are advanced lower-level waiting primitives. For most product flows, start with callbacks and events. Reach for temporal tools when the wait belongs naturally inside controlled Python execution, such as bounded warmup task-picking or low-level orchestration logic.
Failures and Timeouts
Futures need application policy around failure and timeout.
A future can tell STATEK whether a value is ready. It does not automatically decide what should happen if the value never arrives.
Design future-backed waits so that:
- readiness checks are cheap
- result fetching is idempotent
- duplicate or late completions are safe
- timeout and escalation policy is explicit
- external side effects are guarded by durable state checks
- the number of suspended jobs is bounded by the runner or application design
For example, a queue-picking warmup can be reasonable when the framework controls the number of agent jobs trying to pick tasks. A large webhook integration is usually better modeled as external events that deliver work directly, not thousands of futures polling for changes.
Futures Are Not Replay
Futures preserve continuation state. They do not make external systems deterministic.
If a future represents an export, approval, payment, or API call, your application still owns the external side-effect model. Store durable IDs. Check current state before applying changes. Make completion handlers idempotent.
Futures preserve where a job paused, not a deterministic replay of everything outside STATEK. Production external waits need idempotency, authorization, auditability, timeout handling, and resource limits. See Security for runtime boundaries.
Practical Rule
Use callbacks and events by default.
Use futures when the agent's Python code naturally needs a value that may not exist yet, and when polling is acceptable for that wait. They are most useful for controlled, bounded orchestration patterns such as warmup code that picks tasks from a queue, or lower-level execution flows where suspending and resuming the same Python instruction is exactly what you need.
Where to go next
Read Temporal Tools for FutureResult, get_any_future(...), get_all_future(...), and @temporal(...) details; Callbacks and Interruptions for the preferred product-level event model; Warmup Code for bounded queue-picking patterns; Subtasks for delegated work; and Replay and Recovery for side-effect policy.