SoulFire LogoSoulFire
SDK

Durable tasks

Start, observe, cancel, and reconnect to long-running SoulFire bot work.

A durable task runs on the SoulFire server. It continues when the SDK program disconnects. Use a durable task for progress updates, retries, cancellation, or shared bot control.

Current first-party task types include:

  • Pathfinding to a goal
  • Finding and mining matching blocks
  • Clearing all blocks in a cuboid
  • Building inline schematics with rotation and mirroring
  • Crafting shaped and shapeless recipes
  • Cooking items in a furnace, blast furnace, or smoker
  • Brewing potions in batches of up to three bottles
  • Completing an exact number of villager trades
  • Following a moving entity
  • Chasing and attacking an entity
  • Aiming bows and crossbows at moving targets
  • Hunting the nearest entities that match a safe selector
  • Escaping from selected threats until a safety radius is clear
  • Guarding a position or protecting a live entity
  • Finding a bed and sleeping
  • Fishing with server-timed bite detection
  • Finding mature crops, harvesting them, and replanting them
  • Starting compatible animal breeding pairs with confirmed feeding
  • Exploring a limited area without sending two bots to the same place
  • Moving groups of items into or out of storage
  • Keeping a requested inventory loadout from storage
  • Monitoring hunger and eating
  • Monitoring death and respawning
  • Keeping the strongest available armor equipped
  • Keeping a totem in the offhand

Excavate a cuboid

Use excavate to clear every diggable block between two inclusive corners. SoulFire selects the nearest block and plans each route near the game loop. The pathfinder selects tools and keeps mining inside the requested cuboid.

const task = yield* bot.tasks.excavate(
  { x: 10, y: 60, z: 10, dimension: "minecraft:overworld" },
  { x: 17, y: 64, z: 17, dimension: "minecraft:overworld" },
  {
    maximumBlocks: 128,
    path: { allowPlacing: true },
  },
);

const result = yield* task.result();

The server rejects cuboids larger than 32,768 blocks. It also rejects areas with unloaded chunks. Use maximumBlocks or maximum_blocks to limit the number of blocks in one task. The result reports whether SoulFire cleared the area, reached the limit, or found no reachable blocks. It also lists skipped and unreachable positions. If you stop or pause the task, SoulFire releases all bot controls that the task used.

Build an inline schematic

build accepts a blueprint that contains block positions relative to an origin. SoulFire rotates or mirrors the blueprint on the server. It places blocks from the bottom up and selects the required materials.

const task = yield* bot.tasks.build(
  origin,
  [
    {
      offset: { x: 0, y: 0, z: 0 },
      blockId: "minecraft:oak_stairs",
      properties: { facing: "north", half: "bottom" },
    },
    {
      offset: { x: 1, y: 0, z: 0 },
      blockId: "minecraft:oak_planks",
    },
  ],
  {
    rotation: BuildRotation.CLOCKWISE_90,
    mirror: BuildMirror.X,
    substitutions: {
      "minecraft:oak_planks": ["minecraft:spruce_planks"],
    },
    path: { allowPlacing: true },
    breakIncorrectBlocks: true,
  },
);

const result = yield* task.result();

Block positions and directions rotate or mirror together. SoulFire uses the first available material from the substitution list. It does not replace a block that already has the correct state. The result gives an outcome for every requested block.

For a coordinated build, start the same blueprint on each bot with the same partitionCount or partition_count and a distinct zero-based partition index. SoulFire applies transforms and ordering before partitioning, which makes every bot agree on ownership without a client-side coordinator. Pathfinding can place navigation scaffolds when allowPlacing or allow_placing is enabled. Task suspension preserves its current placement, and cancellation releases every claimed control resource.

Start a task and keep its handle

const task = yield* bot.tasks.autoEat(
  ["minecraft:bread"],
  { maximumMeals: 1 },
);

const result = yield* task.result();

A task handle exposes its stable ID and latest snapshot. Use it to refresh, watch revisions, wait for completion, decode the typed result, or cancel the task.

Craft, smelt, and brew

Recipe discovery and production use the same bot-scoped API. list returns the recipes known to the bot. craft, smelt, and brew create durable tasks that own inventory, container, movement, and hand resources while they run.

const task = yield* bot.recipes.brew(
  { fingerprint: waterPotionFingerprint },
  { itemIds: ["minecraft:nether_wart"] },
  3,
  {
    expectedResult: { fingerprint: awkwardPotionFingerprint },
    station: brewingStandPosition,
  },
);

const result = yield* task.result();

Craft counts are recipe operations. A recipe that produces four items per operation still reports one completed craft. Smelt and brew counts are input items. Brewing groups compatible bottles into batches of three, so one ingredient can produce up to three outputs. Use an item fingerprint when the potion's data components matter, and use expectedResult or expected_result to reject a mix whose predicted output changed. Recipes that fit the player inventory grid need no station. Production tasks accept an explicit station position and navigate to it. A station is optional when the compatible menu is already open.

Trade with a villager

Open the villager menu with bot.actions.interactEntity before inspecting or executing trades. SoulFire reads the live merchant offers, including adjusted costs, uses, stock, experience, demand, and price data.

const offers = yield* bot.recipes.listVillagerTrades();
const offer = offers.offers[0]!;

const task = yield* bot.recipes.villagerTrade(offer.offerIndex, 3, {
  expectedResult: { itemIds: [offer.result!.itemId] },
});
const result = yield* task.result();

expectedResult and expected_result protect against a stale offer index. If the merchant changes the offer, the task stops. It runs one trade at a time for an exact count. It also stops when the inventory cannot hold the output.

Hunt matching entities

Use attackNearest or attack_nearest for a bounded hunt. The SDK defaults to one defeated target and completes immediately when none is observable. Use the run* form for a persistent guard loop that waits for future matches.

const hunt = yield* bot.tasks.attackNearest(
  { entityTypes: ["minecraft:zombie"] },
  {
    radius: 48,
    maximumTargets: 3,
    weapon: { tags: ["minecraft:swords"] },
  },
);

const result = yield* hunt.result();
hunt = await bot.tasks.attack_nearest(
    EntitySelector(entity_types=["minecraft:zombie"]),
    radius=48,
    maximum_targets=3,
    weapon=ItemSelector(tags=["minecraft:swords"]),
)

result = await hunt.result()

The selector must constrain a type, category, identity, tag, name, equipment, effect, or owner. SoulFire rejects an unconstrained selector. Combat selects and equips the strongest matching melee weapon. It respects attack cooldown and chases through Pathfinder v2. When the task stops, it restores the previous hotbar selection.

Use a bow or crossbow

Use rangedAttack or ranged_attack to keep a bot at a distance and fire at a specific observed entity. The task leads moving targets and compensates for arrow gravity. It can strafe while aiming and move back into the configured range.

const archer = yield* bot.tasks.rangedAttack(target, {
  minimumRange: 10,
  maximumRange: 32,
  maximumShots: 6,
  weapon: { itemIds: ["minecraft:bow"] },
  bowDrawTicks: 20,
  leadTarget: true,
  compensateGravity: true,
  strafe: true,
});

const result = yield* archer.result();
archer = await bot.tasks.ranged_attack(
    target,
    minimum_range=10,
    maximum_range=32,
    maximum_shots=6,
    weapon=ItemSelector(item_ids=["minecraft:bow"]),
    bow_draw_ticks=20,
    lead_target=True,
    compensate_gravity=True,
    strafe=True,
)

result = await archer.result()

The task supports bows and crossbows and selects ammunition with the vanilla projectile rules. Its result identifies defeated or lost targets, shot limits, missing ammunition, and missing weapons. The target reference is scoped to the current bot connection. A reconnect invalidates it instead of risking an attack on a recycled entity ID. Cancellation stops charging or aiming, releases movement inputs, and restores the previously selected hotbar slot.

Use flee when movement away from a threat is the goal:

const escape = yield* bot.tasks.flee(
  { categories: [EntityCategory.HOSTILE] },
  {
    triggerRadius: 8,
    safeDistance: 20,
    safeSeconds: 3,
  },
);

The trigger radius decides when SoulFire starts moving. The larger safe distance decides when the dynamic path is complete. runFlee and run_flee keep monitoring after an escape, while the bounded form completes after the area stays safe for the requested number of seconds.

Guard a position or protect an entity

Use guard for a fixed block position and protect for a live, connection-scoped entity. SoulFire searches around the protected subject, intercepts matching threats, limits pursuit distance, and returns to the subject after combat.

const defense = yield* bot.tasks.guard(
  { x: 120, y: 64, z: -32 },
  { categories: [EntityCategory.HOSTILE] },
  {
    guardRadius: 16,
    maximumPursuitDistance: 24,
    weapon: { tags: ["minecraft:swords"] },
  },
);

const result = yield* defense.result();
defense = await bot.tasks.guard(
    BlockPosition(x=120, y=64, z=-32),
    EntitySelector(categories=[ENTITY_CATEGORY_HOSTILE]),
    guard_radius=16,
    maximum_pursuit_distance=24,
    weapon=ItemSelector(tags=["minecraft:swords"]),
)

result = await defense.result()

The bounded calls complete after the area stays clear for clearSeconds or clear_seconds. Set target or attack limits to end the task on a specific combat budget. Use runGuard, run_guard, runProtect, or run_protect for a persistent defense that keeps waiting for new threats. Entity protection follows the subject as it moves and ends safely if the subject becomes unavailable.

Threat selectors follow the same safety rule as target acquisition. They must constrain at least one type, category, identity, tag, name, equipment item, effect, or owner.

Find a bed and sleep

The durable sleep task can use a specific bed or discover the nearest loaded bed, navigate into range, interact, and wait for server confirmation:

const rest = yield* bot.tasks.sleep({
  searchRadius: 24,
  waitUntilPossible: true,
  path: {
    allowMining: false,
    allowPlacing: false,
  },
});

const result = yield* rest.result();
rest = await bot.tasks.sleep(
    search_radius=24,
    wait_until_possible=True,
    options=PathfindOptions(
        allow_mining=False,
        allow_placing=False,
    ),
)

result = await rest.result()

The bounded form fails when the server rejects sleep. Set waitUntilPossible or wait_until_possible to keep retrying through daytime or another temporary restriction. runSleep and run_sleep enable that waiting behavior by default and cancel the remote task when the stream owner disconnects.

Use the atomic TypeScript bot.sleep({ bed }) or Python await bot.sleep(bed) action when the application already has an exact reachable bed and does not need discovery or navigation. Use bot.wake() to leave the bed.

Fish with server-timed bite detection

Fishing timing stays on the SoulFire game thread. The task selects a matching rod, casts, watches the synchronized hook state for a bite, reels in, and repeats:

const fishing = yield* bot.tasks.fish({
  maximumCatches: 3,
  rod: { itemIds: ["minecraft:fishing_rod"] },
  restoreSelectedSlot: true,
});

const result = yield* fishing.result();
fishing = await bot.tasks.fish(
    maximum_catches=3,
    rod=ItemSelector(item_ids=["minecraft:fishing_rod"]),
    restore_selected_slot=True,
)

result = await fishing.result()

The bounded form defaults to one confirmed bite and reel. runFish and run_fish keep fishing until cancellation by default. The result reports confirmed catches and failed casts. SoulFire reels in an active line during cleanup and restores the previous hotbar selection when requested.

Farm and replant mature crops

The farm task scans a bounded area, walks to the nearest mature crop, uses the correct harvest interaction, and replants destructive crops:

const harvest = yield* bot.tasks.farm({
  cropIds: ["minecraft:wheat", "minecraft:carrots"],
  center: farmCenter,
  radius: 16,
  maximumHarvests: 24,
  replant: true,
});

const result = yield* harvest.result();

Crop selectors are block IDs. The server supports wheat, carrots, potatoes, beetroots, torchflower crops, pitcher crops, nether wart, cocoa, sweet berry bushes, and both cave-vine block variants. An empty selector includes every supported crop.

Wheat, root crops, nether wart, cocoa, torchflowers, and pitcher plants are broken and replanted with their matching item. Sweet berries and glow berries are harvested by interaction, so their plant remains in place. The task saves cocoa attachment geometry before breaking it and confirms every replant from the synchronized world state.

farm defaults to one harvest and completes when no mature crop is available. runFarm and run_farm default to an unlimited worker that waits between scans. A replanting worker stops with NO_REPLANT_ITEM when it cannot obtain the required seed or crop item. Results report harvested, replanted, and failed harvest counts.

Breed compatible animals

The breeding task selects two compatible adults in a bounded area. It finds suitable food, walks to each animal, and feeds them:

const breeding = yield* bot.tasks.breed({
  animals: { entityTypes: ["minecraft:cow"] },
  food: { itemIds: ["minecraft:wheat"] },
  center: penCenter,
  radius: 20,
  maximumPairs: 2,
});

const result = yield* breeding.result();

Omit animals to consider every observable animal subtype. Omit food to let SoulFire choose any inventory item that both selected animals recognize as breeding food. The optional selectors can narrow either side without hard-coding food tables in the SDK. SoulFire supports same-type pairs and the vanilla horse and donkey cross. It excludes sterile mules.

A successful entity-interaction call is not enough to count a feed. SoulFire waits for the server's love-mode entity event for each animal. The result therefore reports pairsStarted or pairs_started, not a guessed offspring count. Minecraft can still delay or prevent offspring after both parents enter love mode.

breed defaults to one pair and reports NO_COMPATIBLE_PAIR or NO_FOOD when it cannot begin. runBreed and run_breed wait for future eligible animals or food and continue until cancellation by default.

Explore a coordinated frontier

The exploration task divides a circular area into pathfinding waypoints and claims each waypoint before moving. Bots in the same instance that use the same purpose avoid frontier cells already claimed by another bot:

const scouting = yield* bot.tasks.explore({
  origin: basePosition,
  radius: 512,
  waypointSpacing: 64,
  maximumWaypoints: 8,
  returnToOrigin: true,
  purpose: "village-scouting",
});

const result = yield* scouting.result();

Omit origin to center the frontier on the bot's position when the task starts. waypointSpacing or waypoint_spacing controls the distance between frontier cells. A smaller spacing observes the area more densely but requires more pathfinding work. The purpose is a coordination namespace, so use the same value for bots sharing a survey and different values for independent surveys.

explore defaults to one waypoint. Set a finite maximumWaypoints or maximum_waypoints for bounded work. runExplore and run_explore visit every claimable cell in the finite frontier unless cancelled. The optional return policy walks back to the origin after exploration. Results report visited waypoints, failed routes, approximate horizontal distance, and the final position.

Stash and withdraw supplies

The storage tasks walk to a block container, open it, and apply a semantic transfer batch. Applications describe items and counts instead of scripting menu slots:

const supplies = yield* bot.tasks.withdraw(
  storageChest,
  [
    {
      selector: { itemIds: ["minecraft:bread"] },
      count: 16,
    },
    {
      selector: { tags: ["minecraft:coals"] },
      count: 8,
      allowPartial: true,
    },
  ],
);

const result = yield* supplies.result();

Use stash to move items from the player inventory into the container and withdraw for the opposite direction. Selectors support IDs, tags, fingerprints, names, enchantments, stack counts, and remaining durability. Each exact operation must be fully satisfiable. SoulFire preflights the whole batch before sending any menu click, so a later exact operation cannot leave earlier operations applied. Set allowPartial or allow_partial on an operation when best-effort movement is intentional.

The task closes its menu by default and always closes it after failure or cancellation. Results preserve every requested selector and count, report the actual count moved per operation, and identify a partially satisfied batch. The lower-level container handle remains useful when an application already stands beside an open menu and needs interactive transfers.

Maintain a loadout

Loadout maintenance turns a chest or other block container into a semantic supply source and overflow destination. A requirement has a minimum that triggers restocking, a target to restore, and an optional maximum that triggers an excess deposit.

const loadout = yield* bot.tasks.maintainLoadout(
  storageChest,
  [
    {
      selector: { itemIds: ["minecraft:bread"] },
      minimumCount: 8,
      targetCount: 16,
      maximumCount: 24,
    },
    {
      selector: { tags: ["minecraft:arrows"] },
      minimumCount: 32,
      targetCount: 64,
    },
  ],
  {
    checkIntervalTicks: 100,
    closeContainer: true,
  },
);
loadout = await bot.tasks.maintain_loadout(
    storage_chest,
    [
        LoadoutRequirementSpec(
            ItemSelector(item_ids=["minecraft:bread"]),
            minimum_count=8,
            target_count=16,
            maximum_count=24,
        ),
        LoadoutRequirementSpec(
            ItemSelector(tags=["minecraft:arrows"]),
            minimum_count=32,
            target_count=64,
        ),
    ],
    check_interval_ticks=100,
    close_container=True,
)

The unbounded form keeps monitoring until cancellation. Use balanceLoadout or balance_loadout for a one-shot rebalance, or set a finite rebalance limit. SoulFire plans each transfer batch before it moves items. A temporarily empty chest returns a typed container-exhausted result without corrupting menu state. Results report final counts, whether each requirement is satisfied, and cumulative withdrawn and deposited counts.

Attach ownership to a stream

run* methods start a task and stream all revisions. Their default disconnect policy is CANCEL_WITH_CALL.

yield* bot.tasks.runAutoRespawn({
  maximumRespawns: 1,
}).pipe(Stream.runDrain);
async for event in bot.tasks.run_auto_respawn(maximum_respawns=1):
    print(event.task.status, event.task.progress)

Interrupting the Effect stream, aborting the Promise iterator, or cancelling the Python iterator cancels the server task. Use a detached task handle when the work must survive the SDK process.

Observe progress

const task = yield* bot.tasks.attackEntity(target);

yield* task.events().pipe(
  Stream.runForEach((event) =>
    Effect.logInfo(event.task?.progress.message ?? "Working")
  ),
);

Progress includes a message, current count, optional total, and optional fraction. Terminal snapshots include a typed result or structured failure.

Cancel explicitly

yield* task.cancel("Target changed");

Cancellation is idempotent for a terminal task.

Choose conflict and reconnect policy

Tasks declare resources such as movement, rotation, hands, inventory, containers, chat, and automation. Independent work can run together. Conflicts can reject, queue, replace, or suspend lower-priority tasks.

Reconnect policy controls what happens when the Minecraft connection changes:

  • Fail the task because connection-scoped references are no longer valid.
  • Pause and resume after reconnect.
  • Continue when the task can safely reacquire state.

Entity references carry a connection epoch because Minecraft network entity IDs can be reused after reconnect.

Reattach to a detached task

Store the task ID returned by start. A later SDK process can call bot.tasks.get(taskId, resultSchema) in TypeScript or bot.tasks.get(task_id, result_type) in Python, then resume watching its revisions.

Use an idempotency key when retrying task creation after an uncertain network failure. SoulFire returns the existing task when the owner, bot, key, and input match.

How is this page?

Last updated on

On this page