Runtime Architecture

Mapping how high-level workflow primitives compile down to deterministic VM execution. Honest architectural audit of the current alpha prototype.

The Layered Stack

Understanding how high-level workflow DSLs map to low-level VM execution. Each layer builds on the one below it.

W3 Workflow Stack (Draft)

High-level workflows → Low-level VM execution

1 Workflow DSL

YAML spec with triggers, Smart Actions, verification policy

2 Compiler

Parse, validate, optimize → compile to bytecode

3 VM / ISA

Instruction set, interpreter loop, gas metering

4 Host Environment

~

Syscalls for external ops (HTTP, storage, Smart Actions)

5 Runtime State

Block-based state machine, mempool, consensus

Bitcoin

1:Bitcoin Script
2:None (interpreted)
3:Stack-based VM
4:No host calls
5:UTXO set

Ethereum

1:Solidity/Vyper
2:solc, vyper
3:EVM (stack-based)
4:Opcodes (CALL, SLOAD)
5:Account state trie

Solana

1:Rust/C (Anchor)
2:cargo-build-sbf
3:eBPF (register-based)
4:Syscalls (sol_*)
5:Account storage

Current Implementation Reality

Honest assessment of the alpha prototype's architecture. Understanding where we are vs. where we need to be.

Layer 1: Workflow DSL

Status: Great! GitHub Actions YAML syntax with W3-specific extensions.

workflows/defi-monitor.yaml
name: "DeFi Risk Monitor"
on:
schedule:
  cron: "*/5 * * * *"
  verification: onchain

jobs:
monitor:
runs-on: w3/ubuntu-latest
steps: - name: Fetch price
uses: w3/oracle-action@v1
verification: tee

    - name: Alert if risky
      run: ./check_and_alert.sh
      verification: shadow

Layer 2: Compiler

Status: Missing! Template compilation happens at runtime, not deploy time.

execute/run.rs:109-127
// ❌ Compilation work done DURING execution!
let parsed_command = parse_template(run_command)?;
let evaluated_command = evaluate_value(&parsed_command, context)?;

for (key, value) in step.env() {
let evaluated = evaluate_value(value, context)?;
env_vars.insert(key.clone(), value_str);
}

// ❌ No bytecode, no optimization, no static analysis

Layer 3: VM / ISA

Status: Doesn't exist! Just Rust enum matching.

execute/step.rs:31-38
match step.kind() {
  StepKind::Run { run } =>
      run::execute_run_step(...),
  StepKind::Uses { uses, with } =>
      uses::execute_uses_step(...),
}
// ❌ No VM, no bytecode, no gas metering

~

Layer 4: Host Environment

Status: Implicit! Docker provides isolation but no explicit syscall boundary.

execute/container.rs:107-131
docker run --rm --user 1001:1001 \
-v $workspace:/workspace \
-v $temp:/_temp \
w3-runner-ubuntu:22.04 \
bash -c "$evaluated_command"
// ✅ Process isolation (good!)
// ❌ No explicit host call interface

Layer 5: Runtime State

Status: Solid! Block-based state machine with deterministic replay.

context_builder.rs:228-234
for block in &blocks {
  for message in &block.messages {
      replay_message_into_context(message)?;
  }
}
// ✅ State derived from blocks
// ✅ Deterministic replay

The Layering Violations

Critical architectural issues where layers mix responsibilities, making the system fragile and non-deterministic.

1 Template Parsing During Execution (Layer 2 work in Layer 3)

The prototype evaluates templates and environment variables at runtime, not compile time. This means compilation happens during every single execution, making determinism fragile.

execute/run.rs:109-161
// Layer 2 work: Parsing + evaluation (COMPILATION!)
let parsed_command = parse_template(run_command)?;
let evaluated_command = evaluate_value(&parsed_command, context)?;

// Build environment variables (also Layer 2 work!)
for (key, value) in step.env() {
let evaluated = evaluate_value(value, context)?;
env_vars.insert(key.clone(), value_str);
}

// Then immediately jump to Layer 4: Docker execution
let result = container::execute_in_container_with_shell(
"w3-runner-ubuntu:22.04",
&evaluated_command,
&runner_env,
workspace_dir.path(),
temp_dir.path(),
None,
shell,
).await?;

// ❌ Problem: No Layer 3 (VM) at all!
// ❌ Layer 2 work (compilation) done at runtime
// ❌ Direct jump from high-level DSL to host execution

Should be: Compiler produces bytecode at deploy time. VM executes bytecode. Host environment receives explicit syscalls.

2 No VM Layer (Pattern Matching ≠ Virtual Machine)

There's no actual VM. Execution is just a Rust match statement dispatching to native functions. No bytecode, no instruction set, no gas metering.

execute/step.rs:31-38
// "VM" is just pattern matching!
match step.kind() {
  StepKind::Run { run } => {
      run::execute_run_step_with_defaults(
          step, run, context, job_defaults
      ).await?
  }
  StepKind::Uses { uses, with } => {
      uses::execute_uses_step(
          step, uses, with, action_cache, context
      ).await?
  }
}

// ❌ No instruction set
// ❌ No bytecode interpreter
// ❌ No gas metering
// ❌ Just Rust calling Rust

Should be: True VM with instruction set (WASM/eBPF/EVM), bytecode interpreter, and gas metering. VM executes OP_LOAD_CONST, OP_HOST_CALL, etc.

3 Implicit Host Environment (No Explicit Syscall Interface)

Host functions are implicit. Docker provides isolation, but there's no explicit HostCall enum defining what operations the VM can request from the runtime.

Current: execute/container.rs
// Host environment is implicit: Docker + shell
cmd.arg("run")
  .arg("--rm")
  .args(runner_env.docker_args(...));
cmd.arg(image);
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());

let output = cmd.spawn()?.wait_with_output().await?;

// ❌ No explicit HostCall enum
// ❌ No permission model
// ❌ No gas cost for operations
Should be: Explicit host interface
enum HostCall {
ShellCommand { cmd: String, shell: String },
HttpRequest { url: String, method: String },
StorageRead { key: String },
EmitEvent { data: Value },
}

trait HostEnvironment {
async fn invoke(
&self,
call: HostCall,
gas_limit: u64
) -> Result<(HostResult, u64)>;
}

// ✅ Explicit interface
// ✅ Can add permission checks
// ✅ Can meter gas per call

What's Actually Good

Despite architectural issues, several components are solid and provide a strong foundation.

Consensus Hooks

  • Block production/verification separation
  • Mempool with priority fees
  • BFT consensus ready (Tendermint-style)
  • Clean validator interface

State Management

  • Deterministic replay from blocks
  • Context-based execution model
  • Message-driven state transitions
  • Clean separation from consensus

Mempool Design

  • Gas pricing + priority fees
  • Transaction validation before inclusion
  • Account nonce tracking
  • Clean interface for proposers

Process Isolation

  • Docker containers isolate execution
  • Filesystem sandboxing
  • User-level permissions
  • Resource limits (CPU, memory)

Path Forward

Concrete steps to fix layering violations and build proper VM infrastructure.

⚙️ Build Compiler (Layer 2)

  • Parse YAML at deploy time
  • Validate refs, types, schemas
  • Emit bytecode + manifest
  • Static analysis (gas estimation)
  • Deterministic builds

Implement VM (Layer 3)

  • Choose ISA (WASM recommended)
  • Integrate runtime (wasmer/wasmtime)
  • Gas metering per instruction
  • Stack/memory limits
  • Deterministic execution

🔌 Define Host Interface (Layer 4)

  • Explicit HostCall enum
  • Permission model per call
  • Gas costs for operations
  • Async syscall handling
  • Receipt-based verification

Migration Path

  1. Week 1-2: Build compiler prototype (YAML → bytecode)
  2. Week 3-4: Integrate WASM runtime, replace execution engine
  3. Week 5-6: Define and implement HostCall interface
  4. Week 7-8: End-to-end testing, gas tuning, benchmarks