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
xs.action.<name>.create:
r#'{ # Required: Action closure run: {|frame| # frame.topic - always <name>.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 target: "cas" # Default: omitted (record output stored as frame metadata) }}'# | .append xs.action.repeat.createThe return_options field controls the suffix, TTL, and storage target for the
.response frame produced by the action. By default, record output goes to
frame metadata. Set target: "cas" to store in CAS; any type is accepted. 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 an xs.action.<name>.active
frame indicating it’s ready to accept calls. If the definition fails to parse,
an xs.action.<name>.invalid frame is emitted instead.
The closure’s output is collected into a single .response event. Record output
goes to frame metadata by default; set target: "cas" for non-record output.
Calling Actions
Actions are called by appending to <name>.call (in the user namespace, not
under xs.) with input content and arguments:
# Call the repeat action with input and args"foo" | .append repeat.call --meta {args: {n: 3}}Per-call topics
Per-invocation topics live in the user namespace, separate from the action’s lifecycle:
| Topic | Direction | Description |
|---|---|---|
<name>.call | user -> action | Invocation with optional input + args |
<name>.response | action -> stream | Collected result of the action pipeline |
<name>.error | action -> stream | Per-invocation runtime error (distinct from lifecycle invalid) |
Both .response and .error carry:
action_id: ID of the action definition (thecreateframe’s id)frame_id/call_id: ID of this specific invocation
Removing actions
Append xs.action.<name>.term to undefine an action. The runtime emits
xs.action.<name>.fin.term and drops the active definition.
# Permanently remove the repeat actionnull | .append xs.action.repeat.termError Handling
If an action encounters an error during a call, it emits an <name>.error
frame with the error message and references to action_id + the call’s
frame_id. The action is not removed; subsequent calls still work. Each call
is independent.
Lifecycle parse errors (xs.action.<name>.invalid) are emitted only when a
.create definition fails to parse. The two error topics are distinct.
Modules
Actions can use modules registered via xs.module.<name> topics. An action
sees the modules as they existed when it was defined. See
Module Topics for details.
r#'{ run: {|frame| use my-math my-math double ($frame.meta.args.number) }}'# | .append xs.action.calculator.createBuilt-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..cas-post, write content to CAS and return its hash..get, retrieve a frame by ID..remove, delete a frame from the stream..import, insert a frame verbatim, preserving its ID.
Lifecycle
Actions share the unified lifecycle vocabulary documented in the lifecycle reference. The action subset is small:
| User input | Runtime ack on success | Runtime ack on failure |
|---|---|---|
xs.action.<name>.create | xs.action.<name>.active | xs.action.<name>.invalid |
xs.action.<name>.term | xs.action.<name>.fin.term | , |
Actions don’t emit fin.ok, fin.error, stopped, or replaced: with no
long-lived task there is no natural completion, no participation in
xs.stopping, and no running instance to step aside, so a re-create
just rebuilds the definition and re-emits active.
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 |