Skip to main content
← Back to list
01Issue
FeatureClosedSwamp Club
AssigneesNone

feat: Execution Driver Abstraction — Pluggable Isolation for Model Execution

Opened by swampadmin · 9/13/2025

Summary

Swamp currently has no execution isolation — all model methods run directly in the host Deno process ("raw exec"). This issue proposes introducing pluggable execution drivers so users can opt-in to isolated execution environments (starting with Docker), following the same pattern as the existing VaultProvider/VaultTypeRegistry system.

Key constraint: Zero breaking changes. If no driver field is specified, the system defaults to "raw" and behaves identically to today.

Motivation

  • Security (SA-01): Extension models execute with full process privileges. A malicious or compromised extension can exfiltrate vault secrets, read/write arbitrary files, spawn subprocesses, and make unrestricted network requests. The driver abstraction creates the architectural boundary needed for runtime isolation.
  • Extensibility: Users should be able to define custom execution drivers (Docker, Lambda, SSH, Deno Worker) via extensions/drivers/, just like they can define custom vault providers today.
  • Consistency: Follows the same proven pattern as VaultProvider/VaultTypeRegistry.

Design

Driver Interface

interface ExecutionDriver {
  readonly type: string;
  execute(request: ExecutionRequest, callbacks: ExecutionCallbacks): Promise<ExecutionResult>;
  initialize?(): Promise<void>;
  shutdown?(): Promise<void>;
}
  • ExecutionRequest — Serializable envelope containing model type, method name, pre-evaluated arguments, output specs, and optionally the bundled model code.
  • ExecutionResult — Status, outputs (persisted or pending), logs, duration.
  • DriverOutput — Discriminated union: { kind: "persisted", handle: DataHandle } (raw driver writes directly) | { kind: "pending", content: Uint8Array, ... } (out-of-process drivers return content for host-side persistence).

Built-in Drivers

  • raw (default) — Current behavior. Runs in-process with full access. Zero overhead.
  • docker (opt-in) — Serializes context, runs in a container, collects results. Vault secrets piped via stdin (never on disk).

User-Defined Drivers

Loaded from extensions/drivers/ using the same bundle→import→register flow as vault providers:

// extensions/drivers/lambda-driver.ts
export const driver = {
  type: "@acme/lambda",
  name: "AWS Lambda",
  description: "Execute methods as Lambda functions",
  configSchema: z.object({ functionName: z.string(), region: z.string().default("us-east-1") }),
  createDriver(config) {
    return { type: "@acme/lambda", async execute(request, callbacks) { /* ... */ } };
  },
};

Driver Configuration in YAML

Optional driver and driverConfig fields at multiple levels with precedence:

step > job > workflow > model definition > "raw"

Model definition level:

name: my-vpc
type: aws/ec2/vpc
driver: docker
driverConfig:
  memory: 512m
  network: none

Workflow step level (overrides model):

jobs:
  deploy:
    steps:
      - name: create-vpc
        driver: docker
        driverConfig:
          memory: 1g
        task:
          model_method:
            model: my-vpc
            method: create

Workflow/job level (inherited by all steps):

driver: docker
driverConfig:
  image: custom-runner:latest
jobs:
  deploy:
    steps:
      - name: create-vpc
        task: ...

CLI flags:

swamp model method run my-vpc create --driver docker
swamp workflow run deploy --driver docker

Implementation Plan

Phase 1: Domain Interfaces and Registry (no behavioral changes)

Create src/domain/drivers/ with:

  • execution_driver.ts — Core interfaces (ExecutionDriver, ExecutionRequest, ExecutionResult, DriverOutput)
  • driver_type_registry.tsDriverTypeRegistry singleton (mirror of VaultTypeRegistry)
  • driver_types.ts — Built-in "raw" and "docker" registration on module load
  • raw_execution_driver.ts — Stub (fleshed out in Phase 3)
  • docker_execution_driver.ts — Stub
  • mod.ts — Barrel re-export
  • Tests for registry and interface conformance

Phase 2: Schema Changes (all optional, zero breakage)

Add optional driver and driverConfig fields to:

  • DefinitionSchema + Definition class (src/domain/definitions/definition.ts)
  • WorkflowSchema + Workflow class (src/domain/workflows/workflow.ts)
  • JobSchema + Job class (src/domain/workflows/job.ts)
  • StepSchema + Step class (src/domain/workflows/step.ts)

Create driver_resolution.ts with precedence resolution function.

Phase 3: Wire RawExecutionDriver

Extract lines 328-379 of DefaultMethodExecutionService.executeWorkflow() (create writers → inject context → call method.execute()) into RawExecutionDriver.execute(). The orchestration layer delegates to the resolved driver. Defaults to "raw" — identical behavior.

Key design: RawExecutionDriver is constructed per-execution with in-process references (MethodDefinition, MethodContext). Out-of-process drivers use the serialized ExecutionRequest.

What stays in orchestration: validation, lifecycle checks, ModelOutput tracking, deletion markers, follow-up actions.

Phase 4: User-Defined Driver Loader

Create UserDriverLoader (mirror of UserVaultLoader): discover .ts files in extensions/drivers/, bundle, import, validate, register with driverTypeRegistry.

Wire into src/cli/mod.ts startup alongside loadUserModels() and loadUserVaults().

Phase 5: Wire Driver Resolution Through Execution Path

Connect driver/driverConfig from YAML through StepExecutionContextMethodContextMethodExecutionService. Add --driver CLI flag to both:

  • swamp model method run (src/cli/commands/model_method_run.ts)
  • swamp workflow run (src/cli/commands/workflow_run.ts)

The workflow CLI flag flows into the execution service as the workflow-level driver, so step/job-level overrides in YAML still take precedence.

Phase 6: DockerExecutionDriver (future PR)

Serialize ExecutionRequest to JSON, pipe via stdin to docker run, stream stderr for real-time logs, parse ExecutionResult from stdout, return kind: "pending" outputs for host-side persistence. Runner image: minimal Deno container.

Files Summary

New files

File Phase
src/domain/drivers/execution_driver.ts 1
src/domain/drivers/driver_type_registry.ts 1
src/domain/drivers/driver_types.ts 1
src/domain/drivers/raw_execution_driver.ts 1+3
src/domain/drivers/docker_execution_driver.ts 1
src/domain/drivers/mod.ts 1
src/domain/drivers/driver_config.ts 2
src/domain/drivers/driver_resolution.ts 2
src/domain/drivers/user_driver_loader.ts 4

Modified files

File Phase Change
src/domain/definitions/definition.ts 2 Add driver/driverConfig to schema + class
src/domain/workflows/workflow.ts 2 Add driver/driverConfig to schema + class
src/domain/workflows/job.ts 2 Add driver/driverConfig to schema + class
src/domain/workflows/step.ts 2 Add driver/driverConfig to schema + class
src/domain/models/model.ts 3 Add driver/driverConfig to MethodContext
src/domain/models/method_execution_service.ts 3 Refactor to delegate to driver
src/infrastructure/persistence/paths.ts 4 Add driverBundles to SWAMP_SUBDIRS
src/cli/mod.ts 4 Add loadUserDrivers(), import driver barrel
src/domain/workflows/execution_service.ts 5 Pass driver config through execution context
src/cli/commands/model_method_run.ts 5 Pass definition driver + --driver CLI flag
src/cli/commands/workflow_run.ts 5 Add --driver CLI flag, pass to execution service

Security Context

This work directly addresses SA-01: Extension Models Execute Without Runtime Sandbox. The driver abstraction creates the architectural boundary needed for:

  • Docker driver: Full container isolation, configurable network/resource limits
  • Future Deno Worker driver: --allow-read=$REPO_DIR --deny-env --deny-run
  • Future subprocess driver: Restricted Deno permissions
  • Vault secret containment: Non-raw drivers receive pre-resolved secrets via serialized request, never the VaultService object

The security policy layer (e.g., community extensions defaulting to isolated drivers, permission manifests) will be addressed in a follow-up issue.

02Bog Flow
OPENTRIAGEDIN PROGRESSCLOSED

Closed

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.