Live events and synchronized state
Watch bot activity and keep a local copy of the Minecraft world state.
bot.events() returns the ordered server stream.
bot.observe() reads that stream and maintains an immutable state model.
Use the raw stream for temporary activity such as sounds and particles.
Use a session for retained state such as entities, blocks, weather, and scoreboards.
Watch an entire instance
instance.events() combines events from selected bots into one connection.
Each BotEvent item identifies its bot with botProfileId or bot_profile_id.
Use this stream for fleet telemetry, shared chat, dashboards, and coordinators.
yield* instance.events({
botEvents: {
includeChat: true,
includeLifecycle: true,
includeStateDeltas: true,
},
botIds: selectedBotIds,
}).pipe(
Stream.runForEach((item) =>
Effect.logInfo("bot event", {
botId: item.botProfileId,
event: item.event,
})
),
);The default instance filter includes every stateful category and uses the
server's bounded entity and block radii. Sounds and particles remain opt-in.
On subscription, each selected bot contributes current status and snapshots
before live changes. Pass botIds in TypeScript or bot_ids in Python to
limit the stream to a stable profile UUID allowlist.
Open a synchronized session
const session = yield* bot.observe();
const hostile = session.state.entitySnapshots.get(networkId);
const raining = session.state.environment.raining;
yield* session.events().pipe(
Stream.filter((event) => event.event.case === "damage"),
Stream.runForEach((event) => Effect.logInfo("damage", event)),
);The default session filter includes state, lifecycle, chat, damage, inventory, entities, nearby block updates, environment changes, the player list, boss bars, and scoreboard changes. Sounds and particles are opt-in because busy servers can emit them at a high rate. Resource-pack offers and removals are included by default. Titles are also included by default because they often carry gameplay instructions. Chunk events remain opt-in because movement can produce a large stream.
Match chat without managing a session
bot.chat.waitFor matches a substring, regular expression, or predicate and
returns the semantic chat event plus regular-expression captures. Limit a
matcher to player chat, whispers, system messages, or action-bar messages when
the same text can appear in several channels.
const match = yield* bot.chat.waitFor(
/code (?<code>\d{4})/u,
{
sources: [ChatSource.SYSTEM],
timeoutMs: 10_000,
},
);
yield* Effect.logInfo("received code", {
code: match.groups.code,
});Use bot.chat.watch(...) for every matching message.
The Effect entry point returns a Stream.
The Promise entry point returns an async iterable.
Python returns an async or synchronous iterator.
State indexes
The session maintains:
- Full semantic entity snapshots, including velocity, equipment, effects, attributes, ownership, passengers, targets, and dropped items.
- Full block snapshots, including state properties, light, biome, fluid, hardness, tool hints, and interaction metadata.
- The current player list with latency, game mode, display name, and list presentation fields.
- World clocks, rain state, rain level, thunder level, and the latest generic game event.
- Incrementally merged boss bars.
- Objectives, display slots, scores, teams, and team membership.
- Pending resource-pack offers with IDs, URLs, hashes, requirements, and prompts.
- Player, inventory, connection status, stream epoch, and revision metadata.
Entity network IDs are valid only for one connection epoch. Use each
EntitySnapshot.reference.connectionEpoch with its network ID when caching a
target beyond the current callback.
Choose event categories
await using session = await bot.observe({
filter: {
includeEntityEvents: true,
entityRadius: 64,
includeBlockUpdates: true,
blockRadius: 24,
includeEnvironment: true,
includePlayerList: true,
includeBossBars: true,
includeScoreboard: true,
includeResourcePacks: true,
includeTitles: true,
includeChunks: false,
includeSounds: true,
includeParticles: false,
},
});Python accepts the generated BotEventFilter message through
BotSessionOptions.
The server sends an initial player, inventory, entity, environment, and player-list view when a session attaches to an already connected bot. Sequence gaps or epoch changes clear synchronized state before replacement snapshots are applied.
Observe titles and chunk loading
Title events preserve title, subtitle, animation timing, clear, and reset as distinct event kinds. Chunk events report loaded and unloaded columns with their dimension and chunk coordinates.
const stream = bot.events({
includeTitles: true,
includeChunks: true,
});
yield* stream.pipe(
Stream.runForEach((event) =>
Effect.logDebug("transient event", { event: event.event })
),
);
const loaded = yield* bot.waitForChunks({
radiusChunks: 2,
timeoutMs: 30_000,
});from soulfire import BotEventFilter
async for event in bot.events(
BotEventFilter(include_titles=True, include_chunks=True)
):
print(event.WhichOneof("event"))
loaded = await bot.wait_for_chunks(
2,
wait_timeout_ms=30_000,
)waitForChunks and wait_for_chunks run on the SoulFire server. They inspect
the bot's current chunk as it moves and finish only when the complete square
radius is present. Cancelling the SDK operation cancels its pending server
wait. The maximum supported radius is 16 chunks and the maximum server wait is
five minutes.
Build custom stores and replay tests
Both SDKs expose their reducers independently from the live session.
let state = emptyBotSessionState();
for (const event of recordedEvents) {
state = reduceBotSessionState(state, event);
}state = empty_bot_session_state()
for event in recorded_events:
state = reduce_bot_session_state(state, event)These pure reducers are useful for Effect SubscriptionRef integrations,
Redux-style stores, deterministic event replays, and testing automation
without a live Minecraft server.
How is this page?
Last updated on
