Docs›Getting Started›Quick Start
~10 minRust · WASM · libp2p · Sled

Quick Start

In this guide you will install the qos CLI, scaffold a new Rust edge agent, write a fully functional module that reads and writes from the local Sled DB, broadcasts a Gossipsub message across the P2P mesh, and returns a JSON response — then deploy and monitor it with live tail logs.

#Prerequisites

Make sure you have the following installed before continuing:

01

Install the CLI

The Q-OS CLI is a single Rust binary that manages your local edge node, handles module signing, and gives you a live window into the running mesh. Install it with a single curl command:

terminal
1234567
$ curl -sL https://q-os.io/install | bash
> [OK] Detected platform: darwin-arm64
> [OK] Downloading qos-cli v0.1.0...
> [OK] Binary installed to /usr/local/bin/qos
> [OK] Sled state directory initialized at ~/.qos/state/
> [OK] Ed25519 keypair generated at ~/.qos/master.key
> Run 'qos --version' to verify the install.

ℹ️ NOTE

The installer generates an Ed25519 keypair at ~/.qos/master.key on first run. This is your module signing identity — keep the private key secure. The public key is embedded in every module you publish.

⚠️ WARNING

On macOS you may need to clear the Gatekeeper flag: xattr -d com.apple.quarantine /usr/local/bin/qos
02

Initialize Project

Use qos init to scaffold a new edge agent from the official Rust template. This creates a ready-to-build Cargo project pre-configured with the qos-sdk dependency and a signed module manifest.

terminal
123456789
$ qos init my-edge-agent --template rust
> [OK] Scaffolding Rust edge agent in ./my-edge-agent/
> [OK] Creating Cargo.toml with wasm32 target config
> [OK] Creating src/lib.rs with qos-sdk boilerplate
> [OK] Creating qos.toml (module manifest)
>
> Your edge agent is ready. Next steps:
>   cd my-edge-agent
>   qos build --release

Project Structure

my-edge-agent/
my-edge-agent/
├── Cargo.toml       # wasm32 lib target + qos-sdk dependency
├── qos.toml         # Module manifest (name, version, public_key)
└── src/
    └── lib.rs       # Your module logic — the run() entry point

Cargo.toml

The scaffolded Cargo.toml sets the crate type to cdylib so Cargo emits a .wasm binary, and includes qos-sdk for safe host ABI access:

Cargo.toml
12345678910111213141516
[package]
name    = "my-edge-agent"
version = "0.1.0"
edition = "2021"

# cdylib emits a .wasm binary the Q-OS runtime can load
[lib]
crate-type = ["cdylib"]

[dependencies]
# qos-sdk provides safe wrappers around the host ABI.
# It is a zero-dependency crate — no std required.
qos-sdk = "0.1"

# serde_json is compiled to WASM for payload serialization
serde_json = { version = "1", default-features = false, features = ["alloc"] }
03

Write the Module

Open src/lib.rs. The snippet below is a complete, production-annotated module demonstrating all four core SDK operations: reading from the Sled DB, incrementing and persisting a counter, broadcasting a Gossipsub message to mesh peers, and returning a structured JSON payload to the caller.

src/lib.rs
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
//! my-edge-agent — a Q-OS WASM module
//!
//! Demonstrates the four core SDK operations:
//!   1. Reading persistent state from the node's local Sled DB
//!   2. Incrementing and writing state back
//!   3. Broadcasting a Gossipsub message to all peers on the mesh
//!   4. Returning a structured JSON payload to the caller

use qos_sdk::{
    state,    // Typed Sled DB wrappers (get / set / delete)
    gossip,   // libp2p Gossipsub broadcast API
    input,    // Read the raw input bytes from the caller
    output,   // Write the response payload back to the host
};

/// Module entry point — called by the Q-OS runtime on every invocation.
#[no_mangle]
pub extern "C" fn run() {
    // ── Step 1: Read the caller's JSON payload ─────────────────────────────
    let raw = input::read();
    let payload: serde_json::Value =
        serde_json::from_slice(&raw).unwrap_or(serde_json::Value::Null);

    let agent_id = payload["agent_id"]
        .as_str()
        .unwrap_or("unknown")
        .to_string();

    // ── Step 2: Read & increment a heartbeat counter from Sled DB ──────────
    // state::get returns Option<Vec<u8>>; state::set persists raw bytes.
    let counter_key = format!("heartbeat::{agent_id}");
    let count: u64 = state::get(&counter_key)
        .and_then(|bytes| String::from_utf8(bytes).ok())
        .and_then(|s| s.parse().ok())
        .unwrap_or(0) + 1;

    state::set(&counter_key, count.to_string().as_bytes());

    // ── Step 3: Broadcast a Gossipsub message to all mesh peers ────────────
    let gossip_msg = serde_json::json!({
        "topic":    "qos.agents.heartbeat",
        "agent_id": agent_id,
        "count":    count,
        "ts":       qos_sdk::clock::unix_ms(),
    });
    gossip::publish("qos.agents.heartbeat", gossip_msg.to_string().as_bytes());

    // ── Step 4: Return a JSON response payload ─────────────────────────────
    let response = serde_json::json!({
        "ok":       true,
        "agent_id": agent_id,
        "count":    count,
        "message":  format!("Heartbeat #{count} recorded and broadcast to mesh."),
    });
    output::write(response.to_string().as_bytes());
}

💡 TIP

qos-sdk is a thin zero-dependency wrapper around the host ABI. All functions (state::get, gossip::publish, output::write) compile to a handful of extern "C" calls that the runtime resolves at link time. No networking, no async runtime — just raw speed.

ℹ️ NOTE

qos_sdk::clock::unix_ms() reads the host's monotonic clock via host::now_ms() in the ABI. This ensures timestamps are tamper-proof even when the WASM sandbox has no direct access to system time.
04

Deploy & Monitor

Build the module to a signed .qos bundle, deploy it to your running node, then tail the live execution logs to watch each invocation, state mutation, and Gossipsub broadcast in real time:

Build & Deploy

terminal — deploy
1234567891011121314
# Build the module to WASM (one-time target setup)
$ rustup target add wasm32-unknown-unknown

# Compile in release mode — optimized, stripped
$ qos build --release
> [OK] Compiling my-edge-agent (wasm32)
> [OK] Module size: 38 KB (stripped)
> [OK] Ed25519 signature applied — hash: a3f9c12...

# Deploy the signed module to your live edge node
$ qos module deploy ./dist/my-edge-agent.qos --name heartbeat-agent
> [MESH] Uploading payload to local node...
> [OK] Module 'heartbeat-agent' registered on node edge-001
> [OK] Invoke via: POST http://localhost:3000/api/v1/invoke/heartbeat-agent

Monitor with Live Logs

qos logs --tail streams structured log lines directly from the runtime engine — WASM execution traces, state reads/writes, gossip broadcasts, and peer receipts:

terminal — qos logs --tail
12345678910111213
# Stream live execution logs from the edge node
$ qos logs --tail
> 2026-06-26T13:01:04Z  INFO  qos_runtime   Module 'heartbeat-agent' loaded (38 KB)
> 2026-06-26T13:01:05Z  INFO  qos_wasm      run() invoked — agent_id="node-42"
> 2026-06-26T13:01:05Z  DEBUG qos_state     state::get "heartbeat::node-42" → None (first run)
> 2026-06-26T13:01:05Z  DEBUG qos_state     state::set "heartbeat::node-42" = "1"
> 2026-06-26T13:01:05Z  INFO  qos_gossip    Published 84 bytes → topic "qos.agents.heartbeat"
> 2026-06-26T13:01:05Z  INFO  qos_runtime   run() → 200 OK (0.6ms)
> 2026-06-26T13:01:06Z  INFO  qos_mesh      Peer edge-002 received gossip — heartbeat::node-42 count=1

# Query current persisted state directly
$ qos state get heartbeat::node-42
> 1

💡 TIP

Add --filter heartbeat-agent to scope logs to a single module. Use --level debug for full WASM frame traces including fuel consumption.