Skip to content

Messages

How messages are structured, signed, and delivered between nodes.

Message Overview

All inter-node communication in Starweft is conducted via signed envelopes. Each message is typed with a MsgType, and tampering is prevented through canonical JSON serialization and Ed25519 signatures.

Envelope Structure

Messages are sent and received through a three-stage envelope format.

UnsignedEnvelope

This is the envelope before signing. Fields are populated during message creation, then converted into a signed envelope via sign().

rust
pub struct UnsignedEnvelope<T> {
    pub protocol: String,           // "starweft/0.1"
    pub msg_id: MessageId,          // ULID-based unique ID
    pub msg_type: MsgType,          // Message type
    pub from_actor_id: ActorId,     // Sender's actor ID
    pub to_actor_id: Option<ActorId>, // Recipient (None = broadcast)
    pub vision_id: Option<VisionId>,
    pub project_id: Option<ProjectId>,
    pub task_id: Option<TaskId>,
    pub lamport_ts: u64,            // Lamport logical timestamp
    pub created_at: OffsetDateTime, // Creation timestamp
    pub expires_at: Option<OffsetDateTime>, // Expiration time
    pub body: T,                    // Typed message body
}

Envelope (Signed)

A signed envelope produced by UnsignedEnvelope.sign(keypair). It includes a MessageSignature.

rust
pub struct Envelope<T> {
    // ... same fields as UnsignedEnvelope ...
    pub body: T,
    pub signature: MessageSignature,  // Ed25519 signature
}

WireEnvelope (Wire Format)

The format used for network transmission. The body is converted from a typed T to serde_json::Value (untyped JSON).

rust
pub struct WireEnvelope {
    // ... same fields as Envelope ...
    pub body: Value,               // Untyped JSON
    pub signature: MessageSignature,
}

Canonical JSON Serialization

To guarantee deterministic signatures, the data to be signed is serialized as canonical JSON.

  • JSON keys are sorted alphabetically
  • Unnecessary whitespace is removed
  • Floating-point and datetime representations conform to serde_json default output

The signing target includes all envelope fields including the body, but excludes the signature field itself.

rust
// Structure used as the signing target
struct SignableEnvelope<'a, T> {
    protocol: &'a str,
    msg_id: &'a MessageId,
    msg_type: &'a MsgType,
    from_actor_id: &'a ActorId,
    to_actor_id: &'a Option<ActorId>,
    vision_id: &'a Option<VisionId>,
    project_id: &'a Option<ProjectId>,
    task_id: &'a Option<TaskId>,
    lamport_ts: u64,
    created_at: OffsetDateTime,
    expires_at: &'a Option<OffsetDateTime>,
    body: &'a T,
}

Message Type Reference

The Starweft protocol defines the following message types.

Vision & Project

VisionIntent

A vision (goal definition) submitted by the Principal. The Owner receives this and decomposes it into projects and tasks.

rust
pub struct VisionIntent {
    pub title: String,              // Vision title
    pub raw_vision_text: String,    // Free-form vision text
    pub constraints: VisionConstraints, // Execution constraints
}

Direction: Principal → Owner

ProjectCharter

A project charter generated by the Owner from a vision. It defines the project structure and evaluation policy.

rust
pub struct ProjectCharter {
    pub project_id: ProjectId,
    pub vision_id: VisionId,
    pub principal_actor_id: ActorId,     // Requester
    pub owner_actor_id: ActorId,         // Orchestrator
    pub title: String,
    pub objective: String,
    pub stop_authority_actor_id: ActorId, // Stop authority holder
    pub participant_policy: ParticipantPolicy,
    pub evaluation_policy: EvaluationPolicy,
}

Direction: Owner → Principal (+ broadcast)

Capability Discovery

CapabilityQuery

A node announces its identity and capabilities, and queries for peer capabilities.

rust
pub struct CapabilityQuery {
    pub node_id: NodeId,
    pub public_key: String,          // Base64-encoded public key
    pub stop_public_key: Option<String>,
    pub capabilities: Vec<String>,   // e.g. ["openclaw.execution.v1"]
    pub listen_addresses: Vec<String>,
    pub requested_at: OffsetDateTime,
}

Direction: All roles → Broadcast

Note: This message is self-certifying — the signature is verified using the public_key contained in the body. No prior peer public key registration is required.

CapabilityAdvertisement

Sent in response to a CapabilityQuery, advertising the local node's capabilities.

rust
pub struct CapabilityAdvertisement {
    pub node_id: NodeId,
    pub public_key: String,
    pub stop_public_key: Option<String>,
    pub capabilities: Vec<String>,
    pub listen_addresses: Vec<String>,
    pub advertised_at: OffsetDateTime,
}

Direction: All roles → Query originator

Participation Control

JoinOffer

The Owner proposes project participation to a Worker. Includes the required capabilities and a task outline.

rust
pub struct JoinOffer {
    pub required_capabilities: Vec<String>,
    pub task_outline: String,
    pub expected_duration_sec: u64,
}

Direction: Owner → Worker

JoinAccept

A Worker accepts a JoinOffer.

rust
pub struct JoinAccept {
    pub accepted: bool,                    // true
    pub capabilities_confirmed: Vec<String>,
}

Direction: Worker → Owner

JoinReject

A Worker rejects a JoinOffer.

rust
pub struct JoinReject {
    pub accepted: bool,   // false
    pub reason: String,
}

Direction: Worker → Owner

Task Execution

TaskDelegated

The Owner delegates a task to a Worker. Includes input data and the expected output schema.

rust
pub struct TaskDelegated {
    pub parent_task_id: Option<TaskId>, // Parent task (if this is a subtask)
    pub title: String,
    pub description: String,
    pub objective: String,
    pub required_capability: String,
    pub input_payload: Value,            // Input data for the task
    pub expected_output_schema: Value,   // JSON Schema for expected output
}

Direction: Owner → Worker

TaskProgress

A Worker reports task progress. Automatically generated from the OpenClaw bridge's PROGRESS:<float>:<message> output.

rust
pub struct TaskProgress {
    pub progress: f32,      // 0.0 to 1.0
    pub message: String,
    pub updated_at: OffsetDateTime,
}

Direction: Worker → Owner

TaskResultSubmitted

A Worker submits the execution result of a task.

rust
pub struct TaskResultSubmitted {
    pub status: TaskExecutionStatus,  // Completed / Failed / Stopped
    pub summary: String,
    pub output_payload: Value,
    pub artifact_refs: Vec<ArtifactRef>,
    pub started_at: OffsetDateTime,
    pub finished_at: OffsetDateTime,
}

Direction: Worker → Owner

EvaluationIssued

An evaluation certificate issued by the Owner after evaluating a task result.

rust
pub struct EvaluationIssued {
    pub subject_actor_id: ActorId,       // Worker being evaluated
    pub scores: BTreeMap<String, f32>,   // quality, speed, reliability, alignment
    pub comment: String,
}

Direction: Owner → Worker (+ broadcast)

Stop Control

StopOrder

A stop order for a project or task tree. Signed with the stop_authority_key.

rust
pub struct StopOrder {
    pub stop_id: StopId,
    pub scope_type: StopScopeType,  // Project / TaskTree
    pub scope_id: String,
    pub reason_code: String,        // e.g. "misalignment"
    pub reason_text: String,
    pub issued_at: OffsetDateTime,
}

Direction: Principal → Owner → Worker

StopAck

An acknowledgment confirming that a StopOrder has been received and stop processing has begun.

rust
pub struct StopAck {
    pub stop_id: StopId,
    pub actor_id: ActorId,
    pub ack_state: StopAckState,  // Stopping
    pub acked_at: OffsetDateTime,
}

Direction: Worker → Owner → Principal

StopComplete

A final confirmation that stop processing has completed.

rust
pub struct StopComplete {
    pub stop_id: StopId,
    pub actor_id: ActorId,
    pub final_state: StopFinalState,  // Stopped
    pub completed_at: OffsetDateTime,
}

Direction: Worker → Owner → Principal

Snapshots

SnapshotRequest / SnapshotResponse

Used to request and return a state snapshot of a project or task.

rust
pub struct SnapshotRequest {
    pub scope_type: SnapshotScopeType,  // Project / Task
    pub scope_id: String,
}
 
pub struct SnapshotResponse {
    pub scope_type: SnapshotScopeType,
    pub scope_id: String,
    pub snapshot: Value,
}

Direction: Principal → Owner (Request) / Owner → Principal (Response)

Publishing

PublishIntentProposed / PublishIntentSkipped / PublishResultRecorded

A group of messages related to the external publication of task results (e.g., to GitHub).

  • PublishIntentProposed: A proposal to publish
  • PublishIntentSkipped: A notification that publication was skipped
  • PublishResultRecorded: A record of the publication result

Direction: Generated and recorded internally by the Owner

Approval Control

ApprovalGranted / ApprovalApplied

Messages that control the approval gate before task execution.

  • ApprovalGranted: The Principal approves execution of a project or task
  • ApprovalApplied: The Owner applies the approval and resumes paused tasks
rust
pub struct ApprovalGranted {
    pub scope_type: ApprovalScopeType,  // Project / Task
    pub scope_id: String,
    pub approved_at: OffsetDateTime,
}

Direction: Principal → Owner (Granted) / Owner → Principal (Applied)

Delivery via the Outbox Pipeline

All messages are delivered through an outbox queue.

plaintext
queue_outgoing(envelope)


outbox_messages (delivery_state = "queued")

    ▼ flush_outbox()

    ├── to_actor_id present → Look up target peer from peer_addresses

    ├── to_actor_id absent → Broadcast to all peers
    │                        (relay nodes exclude the sender)

    ▼ transport.deliver()

    ├── Success → delivery_state = "delivered_local"
    ├── Failure → Retry (250ms interval, up to 20 attempts)
    └── Limit exceeded → delivery_state = "dead_letter"

Message Expiration

If expires_at is set on an envelope, the receiving side rejects the message if the current time exceeds the expiration plus a 30-second skew tolerance.

rust
const ENVELOPE_EXPIRY_SKEW_SEC: i64 = 30;

Protocol Version

The current protocol version is "starweft/0.1". If an incoming message has a different protocol version, it is rejected unless allow_legacy_protocols = true is configured.

toml
[compatibility]
protocol_version = "starweft/0.1"
allow_legacy_protocols = false