Skip to content

Topics

Topics organize frames in the event stream. The . character creates hierarchy, enabling wildcard queries.

Naming Rules

RuleDetail
Allowed charactersa-z A-Z 0-9 _ - .
Must start witha-z A-Z _
Cannot end with.
Cannot contain..
Max length255 bytes

Valid: chat, user.alice, orders.2024.pending, my_topic-v2

Invalid: .hidden, -dash, 123, trailing., foo..bar, has space

Hierarchy

The . separator creates a topic hierarchy:

user.alice.messages
user.alice.status
user.bob.messages

This structure enables prefix queries.

Wildcard Queries

Use .* suffix to query all direct and nested children of a prefix:

Terminal window
# All frames under user.alice (messages, status, etc.)
xs cat ./store --topic "user.alice.*"
# All user frames
xs cat ./store --topic "user.*"

The wildcard user.* matches user.alice, user.alice.messages, and user.bob—but not user itself.

Use * alone to match all topics (equivalent to omitting --topic).

Topic queries return frames in chronological order (by frame ID), not grouped by topic. Both historical reads and live follows maintain this ordering.

Module Topics

Topics ending with .nu register Nushell modules into the virtual filesystem (VFS). Any actor, service, or action script can then use these modules by their stable VFS path.

Terminal window
# Register a module
r#'export def greet [name: string] { $"hello ($name)" }'# | .append mymod.nu

The topic name (minus the .nu suffix) maps to a directory path — dots become slashes:

TopicVFS Path
mymod.numymod/mod.nu
discord.api.nudiscord/api/mod.nu

Scripts reference modules by their stable VFS path. Re-appending a module topic shadows the previous version:

Terminal window
r#'{
run: {|frame, state|
use mymod
{out: {greeting: (mymod greet "world")}, next: $state}
}
}'# | .append greeter.register

Each actor, service, or command sees the modules as they existed when it was registered. Modules appended later are only visible to processors registered after them. Re-appending a module and then re-registering a processor picks up the new version.

Processor Suffixes

cross.stream uses topic suffixes to coordinate processor lifecycle.

Services

SuffixDescriptionEmitted By
.spawnCreate or update a serviceUser
.terminateStop a serviceUser
.sendInput to duplex servicesUser
.runningService has startedSystem
.stoppedService pipeline stoppedSystem
.shutdownService loop fully exitedSystem
.parse.errorScript parsing failedSystem
.recvOutput from serviceSystem

Actors

SuffixDescriptionEmitted By
.registerRegister an actorUser
.unregisterUnregister an actorUser
.activeActor is now activeSystem
.unregisteredActor has been removedSystem
.outOutput from actorSystem

Actions

SuffixDescriptionEmitted By
.defineDefine an actionUser
.callInvoke an actionUser
.readyAction ready for callsSystem
.errorAction execution failedSystem
.responseAction execution resultSystem

System Topics

TopicDescription
xs.startSystem initialization complete
xs.thresholdStream processing threshold marker
xs.pulseSynthetic pulse events (configurable interval)

Customizable Suffixes

Output suffixes can be changed via return_options.suffix:

  • .recv (Services)
  • .out (Actors)
  • .response (Actions)
Terminal window
{
run: {|| "Hello, World!" },
return_options: {
suffix: ".message" # Changes .recv to .message
}
}

Event Metadata

Lifecycle and output events include:

  • source_id / actor_id / action_id: Component instance ID
  • frame_id: Triggering frame ID (actors/actions)
  • error: Error details (on error events)
  • reason: Stop reason (on stopped events)