SoulFire LogoSoulFire
SDK

Fleet orchestration

Select groups of bots, divide work between them, and monitor their tasks.

instance.fleet creates a reusable group from configured accounts. A selector can start or stop bots, divide input, launch tasks, merge events, collect results, and cancel the group.

Build a selector

A selector can combine:

  • Explicit bot IDs, account names, and account types.
  • Desired state, runtime state, online state, and connection phase.
  • Persistent account metadata.
  • Dimension, distance from a point, health, food, and ping.
  • Required server capabilities.
  • A final custom predicate.
  • Ordering and a result limit.
const builders = {
  online: true,
  runtimeStates: [BotRuntimeState.RUNNING],
  dimensions: ["minecraft:overworld"],
  minimumHealth: 12,
  near: {
    x: 120,
    y: 64,
    z: -32,
    radius: 256,
    dimension: "minecraft:overworld",
  },
  metadata: [{
    namespace: "fleet",
    key: "role",
    equals: "builder",
  }],
  orderBy: "health",
  limit: 8,
} satisfies FleetSelector;

const selected = yield* instance.fleet.select(builders);

Fleet selection reads one bot-list snapshot and one instance configuration snapshot. Persistent metadata comes from the configured account, while position and health come from current live state. A bot that disconnects after selection can still reject the following operation, so group reports always retain per-bot failures.

requiredCapabilities and required_capabilities validate capabilities negotiated for the connected SoulFire server. They fail early when the server cannot run the requested operation.

Control matching bots

Lifecycle methods resolve a selector and submit the resulting IDs as one batch:

const statuses = yield* instance.fleet.start(builders);

Distribute input

Round-robin distribution is the default. Contiguous distribution keeps adjacent input items together. Both modes can cap work per bot and reject an under-capacity selection.

const assignments = yield* instance.fleet.distribute(
  schematicSections,
  builders,
  {
    strategy: "round-robin",
    maximumItemsPerBot: 16,
  },
);
assignments = await instance.fleet.distribute(
    schematic_sections,
    builders,
    strategy="contiguous",
    maximum_items_per_bot=16,
)

Every assignment contains the selected FleetBot descriptor and its ordered items. Empty assignments remain visible, which makes the selected team and partition indices stable.

Start a typed task group

startTasks or start_tasks accepts a generated task input type and result type. The input can be one shared value or a factory that receives the bot, its stable index, and the total selected count.

import {
  BuildTaskResultSchema,
  BuildTaskSchema,
} from "@soulfiremc/sdk/generated/soulfire/task_pb";

const group = yield* instance.fleet.startTasks(
  builders,
  BuildTaskSchema,
  (bot, index, total) => ({
    ...buildInputFor(assignments[index]!.items),
    partitionIndex: index,
    partitionCount: total,
  }),
  BuildTaskResultSchema,
  {
    concurrency: 4,
    idempotencyKey: deploymentId,
  },
);

The concurrency limit applies to task-start RPCs. Tasks continue concurrently on the SoulFire server after they start. A group idempotency key is suffixed with the bot ID in Python so retries remain unique per bot.

Start failures do not discard tasks that started successfully. Inspect startFailures or start_failures, then supervise the remaining members.

Merge events and results

The group merges each task's server stream while preserving its originating bot:

yield* group.events().pipe(
  Stream.runForEach(({ bot, event }) =>
    Effect.logInfo(`${bot.id}: ${event.task?.summary ?? "task update"}`)
  ),
);

const report = yield* group.results();
async for update in group.events():
    print(update.bot.id, update.event.task.summary)

report = await group.results()

report.fulfilled and report.rejected preserve selection order and bot identity. Use requireResults or require_results when any rejection must raise a FleetTaskGroupError containing the complete report.

Cancel as a group

const cancellation = yield* group.cancel(
  "deployment superseded",
  { concurrency: 4 },
);
cancellation = await group.cancel(
    "deployment superseded",
    concurrency=4,
)

Cancellation also returns an aggregate report, so one unreachable bot does not hide successful cancellations.

Fleet orchestration uses durable server tasks. Closing the SDK process does not cancel them unless the task's disconnect policy requires cancellation. Keep the group handle or save its taskIds so another process can resume supervision.

How is this page?

Last updated on

On this page