Actions
cross.stream actions use Nushell expressions to define reusable
operations that can be called on-demand with arguments. Unlike actors which
maintain state between invocations, or services which run continuously,
actions are stateless and execute independently each time they are called.
Defining Actions
To create an action, append a definition string with the topic
<action-name>.define:
r#'{ # Required: Action closure run: {|frame| # frame.topic - always <action>.call # frame.hash - contains input content if present # frame.meta.args - contains call arguments let input = if ($frame.hash != null) { .cas $frame.hash } else { null } let n = $frame.meta.args.n 1..($n) | each {$"($in): ($input)"} }
# Optional: Control output frame behavior return_options: { suffix: ".output" # Output topic suffix (default: ".response") ttl: "last:1" # Keep only most recent frame }}'# | .append repeat.defineThe return_options field controls the suffix and TTL for the .response frame
produced by the action. TTL only applies to this .response frame---.error
events never expire.
The action definition requires:
run: A closure that receives the call frame and can return a pipeline of results
Upon successful definition, the action emits a <action>.ready frame indicating it’s ready to accept calls.
All values produced by the closure’s output pipeline are collected into a single
.response event automatically.
Calling Actions
Actions are called by appending to <action-name>.call with input content and
arguments:
# Call the repeat action with input and args"foo" | .append repeat.call --meta {args: {n: 3}}Lifecycle Events
Actions emit events to track their execution. See for all processor suffixes.
| Event | Description |
|---|---|
<action>.ready | Action successfully defined and ready for calls |
<action>.response | Collected result of the action pipeline |
<action>.error | Error occurred during action execution |
All events include:
action_id: ID of the action definitionframe_id: ID of this specific invocation
Error Handling
If an action encounters an error during execution, it will:
- Emit a
<action>.errorframe with:- The error message
- Reference to both action_id and frame_id
- Stop processing the current invocation
Unlike services, actions do not automatically restart on error - each invocation is independent.
Modules
Actions can use modules registered via *.nu topics. An action sees the modules as they existed when it was defined. See for details.
r#'{ run: {|frame| use xs/my-math my-math double ($frame.meta.args.number) }}'# | .append calculator.defineBuilt-in Store Commands
When the run closure executes it can use several helper commands provided by
cross.stream:
.append— append a new frame. Metadata you provide is merged withaction_idandframe_id..cat— read frames from the store..last— fetch the most recent frame(s), optionally filtered by topic..cas— read content from CAS by hash..get— retrieve a frame by ID..remove— delete a frame from the stream.
Key Differences
| Feature | Actions | Actors | Services |
|---|---|---|---|
| State | Stateless | Stateful between calls | Stateless |
| Execution | On-demand | Event-driven | Continuous |
| Results | Collected into response | Batched on completion | Streamed |
| Parallelism | Multiple parallel calls | Sequential processing | Single instance |
| Error Handling | Per-invocation | Unregisters actor | Auto-restarts |
| Modules | Supported | Supported | Supported |