Pluggable execution backends — run workers in Docker or on a remote host (T332)
By default every isolate-step worker provisions a local git worktree and executes its CLI or
loop work on the local host. The execution backend abstraction decouples WHERE the worker
runs from WHAT it runs — it controls the compute target, not the process sandbox
(HOST_SANDBOX_MODE remains the process-confinement layer and still applies independently).
Three backends ship out of the box:
| Backend | Mode value | Prerequisites | Use case |
|---|---|---|---|
| Local (default) | local | none | existing behaviour unchanged |
| Docker | docker | docker in PATH | isolate each worker in a container; useful when the CI/CD machine has Docker but not all CLIs installed, or for stronger worktree isolation than seatbelt/bwrap |
| SSH | ssh | ssh + rsync in PATH, CYBERINE_SSH_HOST set | off-load heavy build workers to a beefier remote machine while the orchestrator runs on a laptop |
Docker backend — bind-mount the local worktree into the container at the same absolute path,
run the worker CLI inside it, let the bind mount reflect file mutations back to local so
captureDiff() works unchanged:
# Build a custom image with the CLI pre-installed (e.g. opencode).
# The container must be able to find the tool that CYBERINE_BUILD_FLEET workers use.
docker build -t cyberine-worker -f Dockerfile.worker .
CYBERINE_EXEC_BACKEND=docker \
CYBERINE_EXEC_DOCKER_IMAGE=cyberine-worker \
CYBERINE_EXEC_FORWARD_ENV=DEEPSEEK_API_KEY,OPENROUTER_API_KEY \
npm run wf -- .local/dogfood/implement.workflow.tsSSH backend — rsync the worktree to the remote, run there, rsync mutations back:
# Ensure the remote has ssh+rsync access and the worker CLI installed.
CYBERINE_EXEC_BACKEND=ssh \
CYBERINE_SSH_HOST=user@build-server.internal \
CYBERINE_SSH_KEY=~/.ssh/id_ed25519_build \
CYBERINE_SSH_REMOTE_DIR=/tmp/cyberine-exec \
CYBERINE_EXEC_FORWARD_ENV=DEEPSEEK_API_KEY,OPENROUTER_API_KEY \
npm run wf -- .local/dogfood/implement.workflow.tsAvailability detection and fallback — when the requested backend's prerequisites are not
satisfied at run time (no docker binary, or CYBERINE_SSH_HOST not set), the engine logs a
[exec-backend] warning on stderr and falls back to local automatically, so existing runs
are never broken by an unconfigured env. To enforce a specific backend without fallback:
import { resolveExecutionBackend } from './src/engine/executionBackend.ts';
const backend = resolveExecutionBackend({ mode: 'docker', fallbackToLocal: false });
// throws ExecutionBackendError when docker is unavailableCompose with the host sandbox — the execution backend and host sandbox are independent
layers. A worker can run inside Docker via the execution backend (compute target) while
simultaneously confined by the seatbelt host sandbox (process confinement). The
HOST_SANDBOX_MODE wrapping in CliAgentAdapter still applies on top of the exec backend.
Extend with a custom backend — push to EXECUTION_BACKENDS before the first resolveExecutionBackend() call:
import { EXECUTION_BACKENDS } from './src/engine/executionBackend.ts';
import type { ExecutionBackend, RunCommandOpts, RunCommandResult } from './src/engine/executionBackend.ts';
class DaytonaBackend implements ExecutionBackend {
readonly mode = 'daytona' as any;
isAvailable() { return !!process.env['DAYTONA_API_KEY']; }
async prepareRemoteWorkspace(local: string) { /* provision workspace via Daytona API */ return remoteUrl; }
async runCommand(cmd: string, args: string[], opts: RunCommandOpts): Promise<RunCommandResult> { /* ... */ }
async syncBack(local: string, remote: string) { /* download artefacts */ }
async cleanupRemoteWorkspace(remote: string) { /* destroy workspace */ }
}
EXECUTION_BACKENDS.push(new DaytonaBackend());Stateful session reuse — warm CLI-agent bindings + session tree ingestion (T333)
By default every CLI worker cold-spawns a brand-new session for every task. With warm bindings enabled the adapter records the session ID returned by the first successful dispatch, then passes it...
Extend recipes (the vendor-independence payoff)
Three independent ways, none touch the core: