Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nullclaw/nullclaw/llms.txt

Use this file to discover all available pages before exploring further.

NullClaw supports edge deployment with a hybrid architecture that separates network I/O from core agent logic.

Architecture

Edge deployment uses a two-layer pattern:
  1. Edge Host (JavaScript/TypeScript): Handles HTTP, secrets, API calls, webhook parsing
  2. WASM Core (Zig): Contains agent decision logic, policy selection, feature extraction
This keeps secrets and network operations in the trusted edge host while the agent logic remains portable and swappable.
┌─────────────────────────────────────┐
│   Cloudflare Worker (Edge Host)     │
│                                     │
│  • HTTP request handling            │
│  • Telegram/Discord webhooks        │
│  • OpenAI/Anthropic API calls       │
│  • Secret management                │
│  • KV storage (dedup)               │
│                                     │
│  ┌───────────────────────────────┐  │
│  │   agent_core.wasm (< 10 KB)   │  │
│  │                               │  │
│  │  • choose_policy()            │  │
│  │  • extract_features()         │  │
│  │  • decision logic             │  │
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘

Cloudflare Worker Example

The examples/edge/cloudflare-worker/ directory demonstrates a complete edge deployment.

What It Does

1

Receive webhook

Cloudflare Worker receives Telegram webhook update
2

Extract features

JavaScript host extracts text features (length, question marks, keywords)
3

Call WASM policy

WASM choose_policy() returns response policy (concise/detailed/urgent)
4

Build prompt

Host builds system prompt based on selected policy
5

Call LLM

Send request to OpenAI Chat Completions API
6

Send response

Reply sent back to Telegram chat

WASM Core

The agent logic is a tiny Zig module compiled to WASM:
agent_core.zig
const std = @import("std");

pub const Policy = enum(u32) {
    concise = 0,
    detailed = 1,
    urgent = 2,
};

pub export fn choose_policy(
    text_len: u32,
    has_question: u32,
    has_urgent_keyword: u32,
    has_code_hint: u32,
) u32 {
    const urgent_bonus: u32 = if (text_len > 900) 1 else 0;
    const detailed_bonus: u32 = if (text_len > 260) 1 else 0;
    const urgent_score: u32 = has_urgent_keyword * 3 + urgent_bonus;
    const detailed_score: u32 = has_question * 2 + has_code_hint * 2 + detailed_bonus;

    if (urgent_score >= 3) return @intFromEnum(Policy.urgent);
    if (detailed_score >= 3) return @intFromEnum(Policy.detailed);
    return @intFromEnum(Policy.concise);
}

Build WASM

Compile the Zig module to WASM:
zig build-lib examples/edge/cloudflare-worker/agent_core.zig \
  -target wasm32-freestanding \
  -fno-entry \
  -rdynamic \
  -O ReleaseSmall \
  -femit-bin=examples/edge/cloudflare-worker/dist/agent_core.wasm
Or use the build script:
cd examples/edge/cloudflare-worker
./build_wasm.sh
The resulting WASM module is < 10 KB.

Worker Implementation

The edge host loads the WASM module and calls it for each request:
worker.mjs
let wasm_instance_promise;

const POLICY_CONCISE = 0;
const POLICY_DETAILED = 1;
const POLICY_URGENT = 2;

async function get_wasm_instance(env) {
  if (!wasm_instance_promise) {
    wasm_instance_promise = WebAssembly.instantiate(env.AGENT_CORE, {});
  }
  return wasm_instance_promise;
}

async function choose_policy_from_wasm(env, text) {
  const inst = await get_wasm_instance(env);
  const features = extract_text_features(text);
  const choose_policy = inst.instance.exports.choose_policy;
  
  return choose_policy(
    features.text_len,
    features.has_question,
    features.has_urgent_keyword,
    features.has_code_hint,
  ) >>> 0;
}

Wrangler Configuration

wrangler.toml
name = "nullclaw-edge-telegram"
main = "src/worker.mjs"
compatibility_date = "2026-02-26"

[vars]
OPENAI_MODEL = "gpt-4o-mini"
DEDUP_TTL_SECONDS = "86400"

[wasm_modules]
AGENT_CORE = "dist/agent_core.wasm"

Prerequisites

  • Cloudflare account
  • Wrangler CLI
  • Zig 0.15.2
  • Telegram bot token (or other webhook provider)
  • OpenAI API key

Deployment

1

Build WASM core

cd examples/edge/cloudflare-worker
./build_wasm.sh
2

Configure secrets

wrangler secret put TELEGRAM_BOT_TOKEN
wrangler secret put OPENAI_API_KEY
wrangler secret put TELEGRAM_WEBHOOK_SECRET
3

Create KV namespace (optional dedup)

wrangler kv namespace create TELEGRAM_DEDUP
wrangler kv namespace create TELEGRAM_DEDUP --preview
Add to wrangler.toml:
[[kv_namespaces]]
binding = "TELEGRAM_DEDUP"
id = "your_prod_namespace_id"
preview_id = "your_preview_namespace_id"
4

Deploy

wrangler deploy
5

Set webhook

curl -X POST "https://your-worker.workers.dev/telegram/set-webhook"

Why WASM?

Separation of Concerns

  • Host owns secrets: API keys, tokens, credentials stay in edge environment variables
  • Host owns network: HTTP calls, webhooks, rate limiting handled by Worker
  • WASM owns logic: Decision trees, policy selection, feature extraction

Portability

The same WASM module can run on:
  • Cloudflare Workers
  • Deno Deploy
  • AWS Lambda@Edge
  • Fastly Compute@Edge
  • Any WASI-compatible runtime

Security

WASM provides sandboxing:
  • No filesystem access
  • No network access
  • No system calls
  • Pure function execution

Size

Zig’s ReleaseSmall optimization produces tiny WASM modules:
  • agent_core.wasm: < 10 KB
  • Faster cold starts on edge platforms
  • Fits in Cloudflare’s 1 MB Worker script limit with room to spare

Feature Extraction

The host extracts simple features from text:
function extract_text_features(text) {
  const lower = text.toLowerCase();
  return {
    text_len: text.length,
    has_question: text.includes("?") ? 1 : 0,
    has_urgent_keyword: /\b(urgent|asap|immediately|critical)\b/.test(lower) ? 1 : 0,
    has_code_hint: /```|\b(code|bug|error|stack|trace)\b/.test(lower) ? 1 : 0,
  };
}
These features are passed to WASM as integers (no string marshalling overhead).

Policy-Based Prompts

The worker selects system prompts based on WASM policy:
function policy_system_prompt(policy) {
  if (policy === POLICY_URGENT) {
    return "You are an incident-response assistant. Be concise, prioritize safety and immediate next steps.";
  }
  if (policy === POLICY_DETAILED) {
    return "You are a technical assistant. Give concrete, step-by-step guidance with explicit commands when useful.";
  }
  return "You are a concise assistant. Answer directly and avoid unnecessary detail.";
}

Deduplication with KV

Telegram webhooks retry on failure. Use Cloudflare KV to deduplicate:
async function is_duplicate_update(env, update_id) {
  const kv = env.TELEGRAM_DEDUP;
  const dedup_key = `tg:update:${update_id}`;
  
  const existing = await kv.get(dedup_key);
  if (existing !== null) {
    return true; // Already processed
  }

  await kv.put(dedup_key, "1", {
    expirationTtl: 86400, // 24 hours
  });
  return false;
}
KV has eventual consistency. This is best-effort dedup, not a strict guarantee.

Monitoring

Cloudflare Workers provide built-in analytics:
  • Request rate
  • Error rate
  • CPU time
  • Edge location distribution
Access in Cloudflare dashboard under Workers > Analytics.

Evolution Strategy

To update agent behavior:
  1. Edit agent_core.zig
  2. Run ./build_wasm.sh
  3. Run wrangler deploy
No changes to the Worker host required if the exported function signature stays the same.

Limitations

Edge deployments are stateless. They cannot:
  • Maintain long-running agent sessions
  • Execute local tools (filesystem, shell)
  • Store large context windows
Use edge deployment for:
  • Webhook handlers
  • Request routing
  • Policy selection
  • Simple Q&A bots
Use full runtime deployment for autonomous agents.