Skip to main content
WebAssembly (WASM) lets you write plugins in any language (Rust, C, Python, Go, etc.) and deploy them securely. Noorle runs plugins in a sandboxed environment using Wasmtime, preventing access to the host system.

Why WebAssembly?

Plugins must be:
  1. Secure - Can’t access system, steal data, or harm other processes
  2. Fast - Near-native performance for complex computation
  3. Portable - Write once, run anywhere
  4. Controlled - Resource limits (memory, CPU time)

WASM Sandbox Model

Noorle uses WASI Preview 2 (WebAssembly System Interface) for safe system access:

Plugin Lifecycle

Npack Format

Npack is Noorle’s plugin package format:
myanalyzer.npack (ZIP archive)

├─ manifest.json          (metadata)
├─ plugin.wasm            (compiled WASM binary)
├─ schema.json            (MCP tool schemas)
└─ assets/                (optional: config files, etc.)
   └─ default.config

manifest.json

{
  "name": "loan_analyzer",
  "version": "1.0.0",
  "description": "Analyzes loan applications",
  "author": "Your Company",
  "permissions": [
    "cpu_bound",         // allow CPU-intensive work
    "memory_bound"       // allow large memory allocation
  ],
  "environment": {
    "RISK_THRESHOLD": "0.75"
  }
}

schema.json

Defines MCP tools the plugin exposes:
{
  "tools": [
    {
      "name": "analyze_loan",
      "description": "Analyze loan application for risk",
      "inputSchema": {
        "type": "object",
        "properties": {
          "income": { "type": "number" },
          "debt": { "type": "number" },
          "credit_score": { "type": "integer" }
        },
        "required": ["income", "debt", "credit_score"]
      }
    }
  ]
}

Example Plugin: Loan Analyzer

Rust Source Code

// src/lib.rs
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct LoanApplication {
    pub income: f64,
    pub debt: f64,
    pub credit_score: i32,
}

#[derive(Serialize, Deserialize)]
pub struct RiskAssessment {
    pub risk_level: String,  // "low", "medium", "high"
    pub approval_probability: f64,
    pub reasoning: String,
}

pub fn analyze_loan(app: LoanApplication) -> RiskAssessment {
    let debt_to_income = app.debt / app.income;

    let risk = if app.credit_score < 600 {
        "high"
    } else if debt_to_income > 0.43 {
        "high"
    } else if app.credit_score < 700 {
        "medium"
    } else {
        "low"
    };

    RiskAssessment {
        risk_level: risk.to_string(),
        approval_probability: calculate_approval(app.credit_score, debt_to_income),
        reasoning: "Based on DTI ratio and credit score".to_string(),
    }
}

fn calculate_approval(credit_score: i32, dti: f64) -> f64 {
    let credit_factor = (credit_score as f64 - 600.0) / 350.0;
    let dti_factor = (0.43 - dti).max(0.0) / 0.43;
    (credit_factor * 0.6 + dti_factor * 0.4).max(0.0).min(1.0)
}

Compilation

# Install Rust WASM target
rustup target add wasm32-wasi

# Compile to WASM
cargo build --target wasm32-wasi --release

# Resulting binary
target/wasm32-wasi/release/loan_analyzer.wasm

Packaging

# Create npack
noorle-cli package \
  --wasm ./target/wasm32-wasi/release/loan_analyzer.wasm \
  --manifest ./manifest.json \
  --schema ./schema.json \
  --output loan_analyzer.npack

# Upload
noorle-cli publish loan_analyzer.npack

Runtime Behavior

When an agent calls your plugin:
Agent: "analyze_loan({income: 80000, debt: 25000, credit_score: 720})"

    ├─ Load WASM from registry (cached)

    ├─ Instantiate in Wasmtime sandbox
    │  ├─ Memory limit: 256 MB
    │  ├─ CPU timeout: 30 seconds
    │  ├─ No filesystem access
    │  └─ No network access

    ├─ Call exported function
    │  analyze_loan({...})

    ├─ Execute in sandbox
    │  └─ Computation happens here

    ├─ Return result
    │  {
    │    "risk_level": "low",
    │    "approval_probability": 0.92,
    │    "reasoning": "..."
    │  }

    └─ Sandbox destroyed (memory freed)

Permissions & Capabilities

Control what each plugin can do via permissions:
{
  "permissions": [
    "cpu_bound",           // allow CPU-intensive work (no limits)
    "memory_bound",        // allow large allocations (256 MB)
    "execution_timeout:60" // execution timeout: 60 seconds
  ]
}
Available permissions:
  • cpu_bound - Remove default CPU time limits
  • memory_bound - Increase memory limit to 256 MB
  • env:{var_name} - Access specific environment variable
  • execution_timeout:{seconds} - Custom timeout
Environment variables: Pass secrets via env vars (encrypted in config):
{
  "permissions": ["env:DATABASE_KEY"],
  "environment": {
    "DATABASE_KEY": "${ENCRYPTED_SECRET}"
  }
}
Plugin accesses via WASI:
let api_key = std::env::var("DATABASE_KEY").unwrap();

Versioning & Rollback

Plugins are versioned:
loan_analyzer
├─ v1.0.0 (deployed)
├─ v1.1.0 (deployed)
├─ v2.0.0 (in gateway, promoted to prod)
└─ v2.1.0 (beta testing)

Gateway: Attached to v2.0.0

If bug found:
  └─ Rollback to v1.1.0 (1 click)
     All calls go to v1.1.0
Managing versions:
# List versions
noorle-cli plugins list loan_analyzer

# Deploy new version
noorle-cli plugins deploy loan_analyzer v2.1.0

# Rollback
noorle-cli plugins rollback loan_analyzer v1.1.0

Performance Characteristics

WASM near-native performance:
Task: Analyze 1000 loan applications

Native Code:
  └─ 500 ms

WASM Code:
  └─ 510 ms (2% overhead from sandbox)

Overhead is minimal, well worth the security.
Startup cost:
First call: 50 ms (load + compile WASM)
             (cached for future calls)

Subsequent: 5 ms overhead
            (calls are direct)

Supported Languages

Any language that compiles to WASM:
LanguageStatusSetup
Rust✓ Bestrustup target add wasm32-wasi
C/C++✓ GoodEmscripten or clang WASM
Go✓ GoodGOOS=js GOARCH=wasm
Python⚠ LimitedPyodide (some limitations)
JavaScript✓ GoodNode.js WASM target
Zig✓ GoodZig target wasm32-wasi
Recommendation: Use Rust for best developer experience and security.

Debugging

Monitor plugin execution:
# View plugin logs
noorle-cli plugins logs loan_analyzer --tail 100

# Get execution metrics
GET /api/plugins/loan_analyzer/metrics

{
  "calls": 1523,
  "errors": 2,
  "avg_duration_ms": 12,
  "max_duration_ms": 45
}

Best Practices

Pure Functions

Plugins should be deterministic. Same input = same output.

Handle Errors Gracefully

Return structured errors. Agents need to know what went wrong.

Keep Simple

Plugins for specific logic. Use builtins for general tasks.

Test Thoroughly

Test locally before uploading. Use unit tests and integration tests.

Limitations

  • Binary size: Max ~50 MB (practical: 10 MB)
  • Memory: 256 MB default (or configurable)
  • Execution time: 30 seconds default (or configurable)
  • Network: No direct network access (use HttpClient builtin instead)
  • Filesystem: No direct file access (use Files builtin instead)
Workaround: Use connectors or builtins for network/file operations.
Plugins power custom business logic while maintaining security. Next: Learn about Authentication.