Work with commands
What is a command
A command is a directed request sent to a specific cell, triggering an action. It has a name, a handler, and a payload.
This guide puts commands in practice and shows how to work with them.
Command Handler
A command handler is a Rust function annotated with the #[cmd] macro provided by the Myrmic SDK.
#[myrmic_sdk::cmd]
fn greet(md: myrmic_sdk::Metadata) -> myrmic_sdk::Result {
let _ = myrmic_sdk::info!("hello from {:?}", md.id).ok();
Ok(())
}
#[cmd] exports the annotated function as a Wasm command. During deployment, the runtime discovers all exported commands and registers them for invocation. By default, the command name is the handler function name - it is the name callers use to invoke the command.
To override the command name, pass the name attribute to the macro:
#[myrmic_sdk::cmd(name = "hello")]
fn greet(md: myrmic_sdk::Metadata) -> myrmic_sdk::Result {
// ...
Ok(())
}
Every command handler takes Metadata as its first argument - it carries context about who called it and which cell is running:
pub struct Metadata {
pub id: Sri, // the SRI of this cell
pub sender: Sri, // the SRI of the sender; itself for self-calls or timer/BLE triggers; nil when sent externally (e.g. CLI)
}
Command payload
A command may require an input payload to fulfil its logic - in that case, add a second argument to the handler function to receive it:
#[myrmic_sdk::cmd]
fn set_threshold(_md: Metadata, value: f32) -> myrmic_sdk::Result {
// ...
Ok(())
}
The payload type must implement Decoder and Encoder - traits the SDK uses to deserialize and serialize the payload. Both rely on serde::Serialize and serde::Deserialize, which must also be derived. To understand why and how, see Message encoding.
Numeric primitives (f32, u32, i32, bool ...), and types provided by the SDK such as myrmic_sdk::String, myrmic_sdk::Bytes, and myrmic_sdk::JsonValue already implement it by default.
A command is limited to one payload parameter - if you need multiple fields, wrap them in a struct and derive myrmic_sdk::Message to automatically implement Decoder and Encoder:
use serde::Deserialize;
#[derive(Deserialize, myrmic_sdk::Message)]
struct WorkItem {
id: myrmic_sdk::String,
priority: u32,
}
#[myrmic_sdk::cmd]
fn process(_md: myrmic_sdk::Metadata, item: WorkItem) -> myrmic_sdk::Result {
let _ = myrmic_sdk::info!("processing {} (priority {})", item.id, item.priority).ok();
Ok(())
}
Invoke a command from the CLI
Rather than writing a second cell just to test, the CLI provides myrmic send - a command (CLI) that targets a running cell directly from a terminal.
It takes the cell SRI, a command name, and an optional payload, and dispatches the command to the target cell:
# No payload
myrmic send my-cell greet
# JSON payload
myrmic send my-cell process '{"id":"item-01","priority":2}'
The payload is sent as JSON by default. For a full reference see myrmic send.
Invoke a command from one cell to another
Now we reach the interesting part - a cell sending commands to another cell.
fn forward() -> myrmic_sdk::Result {
let target = myrmic_sdk::Sri::of_path("worker").map_err(|_| "invalid sri")?;
let payload = WorkItem {
id: myrmic_sdk::String::from("item-01"),
priority: 2,
};
myrmic_sdk::send(target, "process", &payload)?;
Ok(())
}
The Myrmic SDK provides the send function to invoke commands. It takes the target SRI, the command name, and the payload.
send is callable from any handler within a cell - a command handler, an event handler, or an initialization handler. A cell can also invoke commands on itself.
For commands that take no payload, the SDK provides myrmic_sdk::Void - a zero-sized placeholder that signals no data is being sent:
myrmic_sdk::send(target, "greet", &myrmic_sdk::Void)?;
Dispatch from a command handler
send is non-blocking - execution continues immediately after each call. All command sends (and event publishes) made during a command handler commit atomically together with any storage operations when the handler completes - nothing is visible to the outside until that point.
Register a callback to receive a response from a command
Commands are fire-and-forget - once dispatched, the execution continues immediately and there is no way to get a value back directly.
If a command is expected to produce a result, the solution is to include a callback in the payload. The callee receives it, does its work, and sends the result back through it. On the caller side, the result lands on a dedicated command handler.
use serde::{Deserialize, Serialize};
// Shared type - both sides must use the same definition
#[derive(Serialize, Deserialize, myrmic_sdk::Message)]
struct StatusReply {
active: bool,
label: myrmic_sdk::String,
}
The caller sends the callback and handles the result in a dedicated handler:
#[myrmic_sdk::cmd]
fn request_status(_md: Metadata) -> myrmic_sdk::Result {
let target = myrmic_sdk::Sri::of_path("worker").map_err(|_| "invalid sri")?;
let callback = myrmic_sdk::Callback::of::<on_status>(); // or: Callback::to("on_status")?
myrmic_sdk::send(target, "get_status", &callback)?;
Ok(())
}
#[myrmic_sdk::cmd]
fn on_status(_md: Metadata, reply: StatusReply) -> myrmic_sdk::Result {
let _ = myrmic_sdk::info!("active={} label={}", reply.active, reply.label).ok();
Ok(())
}
The callee receives the callback and invokes it with the result:
#[myrmic_sdk::cmd]
fn get_status(md: myrmic_sdk::Metadata, callback: myrmic_sdk::Callback<StatusReply>) -> myrmic_sdk::Result {
let reply = StatusReply {
active: true,
label: myrmic_sdk::String::from("worker-01"),
};
callback.invoke(md.sender, &reply)?;
Ok(())
}
Callback<T> is a type that holds a reference to a handler on the calling cell meant to handle the response. T is the reply type that handler accepts.
Callback provides two ways to create an instance:
Callback::to("on_status")- takes the name of the handler to invoke on the caller.Callback::of::<on_status>()- uses the handler's marker type; the compiler verifies it acceptsT.
See also
- How to publish and handle events
- Scheduling Handlers - how to schedule work at a fixed interval or after a delay
- Message encoding
- State and Storage - working with state and storage
myrmic sendreference