Skip to content

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:

Terminal window
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.create

The 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:

Terminal window
# 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:

TopicDirectionDescription
<name>.calluser -> actionInvocation with optional input + args
<name>.responseaction -> streamCollected result of the action pipeline
<name>.erroraction -> streamPer-invocation runtime error (distinct from lifecycle invalid)

Both .response and .error carry:

  • action_id: ID of the action definition (the create frame’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.

Terminal window
# Permanently remove the repeat action
null | .append xs.action.repeat.term

Error 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.

Terminal window
r#'{
run: {|frame|
use my-math
my-math double ($frame.meta.args.number)
}
}'# | .append xs.action.calculator.create

Built-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 with action_id and frame_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 inputRuntime ack on successRuntime ack on failure
xs.action.<name>.createxs.action.<name>.activexs.action.<name>.invalid
xs.action.<name>.termxs.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

FeatureActionsActorsServices
StateStatelessStateful between callsStateless
ExecutionOn-demandEvent-drivenContinuous
ResultsCollected into responseBatched on completionStreamed
ParallelismMultiple parallel callsSequential processingSingle instance
Error HandlingPer-invocationUnregisters actorAuto-restarts
ModulesSupportedSupportedSupported