Skip to content

Architecture

Understand Starweft's peer-to-peer architecture and communication model.

System Overview

Starweft operates on a fully peer-to-peer architecture with no central server. Every node runs as a CLI process on the user's local machine, requiring no cloud infrastructure or database servers whatsoever.

plaintext
┌──────────────────────────────────────────────────────────┐
│                  User's Local Machine                    │
│                                                          │
│  ┌─────────────┐     ┌─────────────┐                     │
│  │  principal   │────▶│   owner     │                     │
│  │  (CLI)       │     │   (CLI)     │                     │
│  │              │     │             │                     │
│  │ Ed25519 Key  │     │ Ed25519 Key │                     │
│  │ SQLite WAL   │     │ SQLite WAL  │                     │
│  └──────────────┘     └──────┬──────┘                     │
│                              │                            │
│                    TaskDelegated                           │
│                              │                            │
│                     ┌────────▼────────┐                   │
│                     │    worker       │                    │
│                     │    (CLI)        │                    │
│                     │                 │                    │
│                     │ Ed25519 Key     │                    │
│                     │ SQLite WAL      │                    │
│                     │ OpenClaw bridge │                    │
│                     └─────────────────┘                    │
│                                                          │
│  Transport: Unix socket (local_mailbox)                   │
│  or         libp2p TCP (multi-machine setup)              │
└──────────────────────────────────────────────────────────┘

Each node is an independent CLI process that maintains the following:

  • Ed25519 key pair -- Used for signing and verifying messages
  • SQLite WAL database -- State persistence via event sourcing
  • inbox/outbox pipeline -- Message receive and send queues

Transport Layer

Starweft provides two transport backends.

local_mailbox (File-Based)

The default transport on Unix environments. It uses a /unix/ multiaddr path as a mailbox and receives messages through atomic file renames.

toml
[node]
listen = ["/unix//home/user/.starweft/mailbox.sock"]
 
[p2p]
transport = "local_mailbox"
  • Ideal for inter-node communication on a single machine
  • Messages are atomically retrieved via the filesystem rename operation
  • Crash recovery: Automatic recovery from .recv temporary files

libp2p TCP

Used for multi-machine setups and communication across networks. This is the default on Windows environments.

toml
[node]
listen = ["/ip4/0.0.0.0/tcp/4001"]
 
[p2p]
transport = "libp2p"
  • Encrypted communication via the Noise protocol
  • Stream multiplexing with the Yamux multiplexer
  • CBOR-encoded request-response protocol (/starweft/0.1)
  • Automatic reconnection to seed peers (every 5 seconds)
  • Idle connections maintained for 300 seconds
  • Inbox capacity limit: 10,000 messages

Automatic Peer Discovery via mDNS

When using the libp2p transport, enabling mDNS allows automatic detection of peers on the local network.

toml
[discovery]
mdns = true

NAT Traversal

When using the libp2p transport, Starweft supports automatic NAT traversal to enable communication between nodes behind NAT.

MechanismDescription
AutoNATAutomatically detects whether the node is behind NAT by probing reachability from peers
UPnPAttempts to automatically open ports on the NAT gateway via UPnP
Circuit Relay v2Routes traffic through a relay node when direct connection is not possible
DCUtRDirect Connection Upgrade through Relay — establishes a direct connection via hole-punching after initial relay connection
toml
[p2p]
transport = "libp2p"
nat_traversal = true  # Enable NAT traversal (default: true)

Event Sourcing and SQLite WAL

Starweft employs an event sourcing pattern. All protocol messages are recorded as immutable events in the task_events table, while projection tables (projects, tasks, task_results, etc.) provide the current state view.

plaintext
┌──────────────────┐
│  Incoming Message │
│  (WireEnvelope)   │
└────────┬─────────┘


┌──────────────────┐     ┌──────────────────┐
│  task_events     │────▶│  projects        │
│  (Event Log)     │     │  tasks           │
│  ────────────    │     │  task_results    │
│  msg_id (PK)     │     │  artifacts       │
│  msg_type        │     │  evaluation_certs│
│  body_json       │     │  stop_orders     │
│  signature_json  │     │  (Projections)   │
│  lamport_ts      │     └──────────────────┘
└──────────────────┘

Key table structure:

TablePurpose
local_identityThe node's own actor ID, public key, and display name
peer_addressesKnown peers' multiaddr addresses
peer_keysPeers' public keys and capabilities
visionsSubmitted visions
projectsProject state (planning / active / stopping / stopped)
tasksTask state (queued → running → completed/failed/stopped)
task_dependenciesTask dependency graph (depends_on relationships)
task_eventsImmutable log of all messages
task_resultsSummary of task execution results
inbox_messagesReceive queue (with deduplication)
outbox_messagesSend queue (with retry and dead-letter management)

SQLite's WAL (Write-Ahead Logging) mode ensures read/write concurrency and fault tolerance.

inbox/outbox Pipeline

Message sending and receiving is managed through an inbox/outbox pattern via the RuntimePipeline.

plaintext
Receive flow:
  transport.receive()
    → JSON deserialization of WireEnvelope
    → Signature verification (Ed25519)
    → Deduplication check (inbox_message_processed)
    → Routed to appropriate handler by MsgType via route_incoming_wire()
    → Projection update
    → mark_inbox_message_processed()
 
Send flow:
  RuntimePipeline.queue_outgoing()
    → Saved to outbox_messages with queued status
  flush_outbox()
    → Delivery destination resolved from peer_addresses
    → Sent via transport.deliver()
    → Success: status updated to delivered
    → Failure: retry (250ms interval, up to 20 attempts)
    → Limit exceeded: transitions to dead_letter status

Ed25519 Signature Verification

All protocol messages are signed with Ed25519.

2

A SignableEnvelope struct is created from the UnsignedEnvelope. All fields, including the body, are included in the signing payload.

4

The SignableEnvelope is serialized into canonical JSON (keys sorted, no whitespace) to produce a deterministic byte sequence.

6

The canonical JSON byte sequence is signed with the Ed25519 private key, generating a MessageSignature (algorithm "ed25519" + key_id + Base64 signature).

8

The receiving side reconstructs the same canonical JSON using the sender's public key and verifies the signature. Peers' public keys are exchanged in a self-certifying manner through CapabilityQuery / CapabilityAdvertisement messages.

rust
// Signature structure
pub struct MessageSignature {
    pub alg: String,       // "ed25519"
    pub key_id: KeyId,     // Unique identifier for the key
    pub sig: String,       // Base64-encoded signature
}

Crate Dependency Structure

The Starweft codebase is organized into crates separated by responsibility.

plaintext
apps/starweft/
└── src/                — CLI core (commands, runtime, TUI)
 
crates/
├── starweft-protocol/  — Message types, envelopes, signature verification
├── starweft-store/     — SQLite persistence, event sourcing
├── starweft-runtime/   — inbox/outbox pipeline
├── starweft-p2p/       — local_mailbox + libp2p transport
├── starweft-crypto/    — Ed25519 key management, canonical JSON
├── starweft-id/        — ULID-based typed IDs
├── starweft-observation/ — Task decomposition, evaluation scoring
├── starweft-openclaw-bridge/ — OpenClaw subprocess execution
└── starweft-stop/      — Stop control logic

Dependency direction:

plaintext
starweft (CLI)
├── starweft-protocol   ← starweft-crypto, starweft-id
├── starweft-store      ← starweft-protocol, starweft-crypto, starweft-id, starweft-stop
├── starweft-runtime    ← starweft-protocol, starweft-store
├── starweft-p2p        ← (libp2p, multiaddr)
├── starweft-observation ← starweft-protocol
├── starweft-openclaw-bridge ← starweft-protocol
└── starweft-stop       ← (standalone)