Task execution and ownership

SoulFire tasks run on the server. Choose the API according to the workflow’s ownership:

API

Result

Cancellation ownership

soulfire.SoulFireBot.collect()

Completed collection result

Cancels unfinished work on interruption

soulfire.SoulFireTasks.collect_blocks()

Handle after server acceptance

Explicit cancellation through task.cancel()

soulfire.SoulFireTasks.run_collect_blocks()

Stream of task events

Cancels unfinished work when the stream closes by default

The same start-versus-stream distinction applies to navigation and other task families. A start method does not wait for completion. run_* methods start work when their streams are consumed.

Wait for a typed result

This workflow uses managed installation and an offline-compatible Minecraft server:

@gen
def collect_with_task_handle() -> EffectGen[None, SoulFireOperationError, Scope]:
    bot = yield from SoulFire.create_bot(server="localhost:25565", username="Builder")
    task = yield from bot.tasks.collect_blocks(
        tags=["minecraft:logs"],
        count=16,
        deadline=datetime.now(UTC) + timedelta(seconds=120),
    )
    yield from sync(lambda: print(f"Accepted task {task.id}"))
    result = yield from task.result()
    yield from sync(lambda: print(result))

The workflow uses the imports and @gen decorator from the complete example. Run it with asyncio.run(run_async(scoped(collect_with_task_handle).or_die())).

soulfire.SoulFireTask.result() waits for successful completion and decodes the expected protobuf result. A failed, cancelled, or timed-out task fails with SoulFireTaskError in the Effect error channel. The error contains the task snapshot, including status and failure details. A missing or incompatible result also fails.

soulfire.SoulFireTask.wait() returns the snapshot regardless of terminal status. Use it when cancellation or failure is an expected outcome to inspect. task.snapshot and task.terminal read cached state. refresh(), wait(), and cancel() update it. Direct event consumption does not update it.

Interrupting task.result() or task.events() closes the observer. It does not itself cancel the server job. Call task.cancel(reason) explicitly, or choose an API that owns cancellation. Server disconnect, reconnect, deadline, and resource-conflict policies still control execution.

Observe progress with ownership

@gen
def collect_with_progress() -> EffectGen[None, SoulFireOperationError, Scope]:
    bot = yield from SoulFire.create_bot(server="localhost:25565", username="Builder")
    yield from bot.tasks.run_collect_blocks(tags=["minecraft:logs"], count=16).run_for_each(
        lambda event: sync(lambda: print(event.task.summary))
    )

Run this workflow with asyncio.run(run_async(scoped(collect_with_progress).or_die())). The stream ends when the task ends. Inspect event snapshots for terminal status; stream completion alone does not establish task success.

The default run_* disconnect policy is CANCEL_WITH_CALL. Interrupting the workflow therefore cancels unfinished remote work. The generic soulfire.SoulFireTasks.run() method also accepts an explicit disconnect policy.

Bound work and avoid duplicate submissions

A task deadline bounds server execution and must be timezone-aware. A request timeout_ms bounds the RPC in milliseconds. Use a deadline when a durable job must finish within a fixed window.

idempotency_key identifies one intended submission. Reuse that key when retrying an uncertain submission. Do not reuse it for a different job. Error recovery must account for resource conflicts, reconnect policies, and completed side effects.

For collection with completion and cleanup, use soulfire.SoulFireBot.collect(). Use a handle when the application manages a job independently. Use a run_* stream when a workflow needs progress and cancellation ownership.

See Connection and bot lifecycle and the collection recipe.