Skip to content

Commit 71b922c

Browse files
committed
improvement(execution): replace the remote-workers flag and its env fallback with a local-workers kill switch
A configured worker host is now used by default; the AppConfig-only isolated-vm-local-workers kill switch forces local workers during rollout. Drops IVM_REMOTE_WORKERS, and an unreachable AppConfig keeps the host in use.
1 parent bf0f345 commit 71b922c

7 files changed

Lines changed: 48 additions & 23 deletions

File tree

‎.claude/rules/sim-sandbox.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ payload or `ivm.Reference` wrapper in the worker:
8888
## Where the worker runs
8989

9090
The pool in `isolated-vm.ts` opens each worker either as a local child process
91-
or, when `IVM_REMOTE_WORKER_URL` is set and the `isolated-vm-remote-workers`
92-
flag is on, as a WebSocket connection to `isolated-vm-host.cjs`, which runs the
91+
or, when `IVM_REMOTE_WORKER_URL` is set (and the `isolated-vm-local-workers`
92+
kill switch is off), as a WebSocket connection to `isolated-vm-host.cjs`, which runs the
9393
same worker in a dedicated service that holds no platform credentials. Every
9494
rule in this file applies in both modes.
9595

‎.cursor/rules/sim-sandbox.mdc‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ payload or `ivm.Reference` wrapper in the worker:
8484
## Where the worker runs
8585

8686
The pool in `isolated-vm.ts` opens each worker either as a local child process
87-
or, when `IVM_REMOTE_WORKER_URL` is set and the `isolated-vm-remote-workers`
88-
flag is on, as a WebSocket connection to `isolated-vm-host.cjs`, which runs the
87+
or, when `IVM_REMOTE_WORKER_URL` is set (and the `isolated-vm-local-workers`
88+
kill switch is off), as a WebSocket connection to `isolated-vm-host.cjs`, which runs the
8989
same worker in a dedicated service that holds no platform credentials. Every
9090
rule in this file applies in both modes.
9191

‎apps/sim/lib/core/config/env.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,9 +454,8 @@ export const env = createEnv({
454454
IVM_MAX_BROKER_ARGS_JSON_CHARS: z.string().optional().default('262144'), // Max JSON payload size for sandbox task broker args (isolate→host)
455455
IVM_MAX_BROKER_RESULT_JSON_CHARS: z.string().optional().default('16777216'),// Max JSON payload size for sandbox task broker results (host→isolate)
456456
IVM_MAX_BROKERS_PER_EXECUTION: z.string().optional().default('1000'), // Max broker calls per sandbox task execution
457-
IVM_REMOTE_WORKER_URL: z.string().optional(), // ws(s)://<host>/v1/worker of a dedicated worker host; used only while the isolated-vm-remote-workers flag is on
457+
IVM_REMOTE_WORKER_URL: z.string().optional(), // ws(s)://<host>/v1/worker of a dedicated worker host; workers run there unless the isolated-vm-local-workers kill switch is on
458458
IVM_REMOTE_WORKER_TOKEN: z.string().optional(), // Token the worker host expects (its IVM_HOST_TOKEN, >= 32 chars); required with IVM_REMOTE_WORKER_URL
459-
IVM_REMOTE_WORKERS: z.string().optional(), // Fallback for the isolated-vm-remote-workers feature flag when AppConfig is not the source of truth
460459

461460
// Knowledge Base Processing Configuration - Shared across all processing methods
462461
KB_CONFIG_MAX_DURATION: z.number().optional().default(600), // Max processing duration in seconds (10 minutes)

‎apps/sim/lib/core/config/feature-flags.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,13 @@ const FEATURE_FLAGS = {
121121
'targeting. Off-AppConfig falls back to KNOWLEDGE_MEMBER_ACCESS.',
122122
fallback: 'KNOWLEDGE_MEMBER_ACCESS',
123123
},
124-
'isolated-vm-remote-workers': {
124+
'isolated-vm-local-workers': {
125125
description:
126-
'Run JavaScript sandbox workers on the dedicated worker host at IVM_REMOTE_WORKER_URL ' +
127-
'instead of as local child processes. Global on/off only; no effect where ' +
128-
'IVM_REMOTE_WORKER_URL is unset. While on, an unreachable host fails executions ' +
129-
'instead of falling back to local workers. Off-AppConfig falls back to IVM_REMOTE_WORKERS.',
130-
fallback: 'IVM_REMOTE_WORKERS',
126+
'Kill switch: run JavaScript sandbox workers as local child processes even where ' +
127+
'IVM_REMOTE_WORKER_URL names a dedicated worker host. Global on/off only; off by default, ' +
128+
'so a configured host is used unless this is turned on. AppConfig-only: when AppConfig is ' +
129+
'unreachable the host stays in use.',
130+
fallback: null,
131131
},
132132
'mothership-abandoned-run-takeover-disabled': {
133133
description:

‎apps/sim/lib/execution/isolated-vm.test.ts‎

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,10 +357,7 @@ describe('isolated-vm scheduler', () => {
357357
}
358358
})
359359

360-
it('never falls back to a local worker while remote workers are enabled', async () => {
361-
featureFlagsMockFns.mockIsFeatureEnabled.mockImplementation(
362-
async (flag) => flag === 'isolated-vm-remote-workers'
363-
)
360+
it('never falls back to a local worker while a worker host is configured', async () => {
364361
vi.stubGlobal('WebSocket', UnreachableWebSocket)
365362
try {
366363
const { executeInIsolatedVM, spawnMock } = await loadExecutionModule({
@@ -383,6 +380,36 @@ describe('isolated-vm scheduler', () => {
383380
expect(result.result).toBeNull()
384381
expect(result.error?.isSystemError).toBe(true)
385382
expect(spawnMock).not.toHaveBeenCalled()
383+
} finally {
384+
vi.unstubAllGlobals()
385+
}
386+
})
387+
388+
it('runs local workers while the kill switch is on, even with a worker host configured', async () => {
389+
featureFlagsMockFns.mockIsFeatureEnabled.mockImplementation(
390+
async (flag) => flag === 'isolated-vm-local-workers'
391+
)
392+
vi.stubGlobal('WebSocket', UnreachableWebSocket)
393+
try {
394+
const { executeInIsolatedVM, spawnMock } = await loadExecutionModule({
395+
envOverrides: {
396+
IVM_REMOTE_WORKER_URL: 'ws://worker-host.internal:3100/v1/worker',
397+
IVM_REMOTE_WORKER_TOKEN: 'remote-worker-token-of-at-least-32-chars',
398+
},
399+
spawns: [() => createReadyProc('ran locally')],
400+
})
401+
402+
const result = await executeInIsolatedVM({
403+
code: 'return "ran locally"',
404+
params: {},
405+
envVars: {},
406+
contextVariables: {},
407+
timeoutMs: 100,
408+
requestId: 'req-kill-switch',
409+
})
410+
411+
expect(result.result).toBe('ran locally')
412+
expect(spawnMock).toHaveBeenCalledTimes(1)
386413
} finally {
387414
vi.unstubAllGlobals()
388415
featureFlagsMockFns.mockIsFeatureEnabled.mockReset().mockResolvedValue(false)

‎apps/sim/lib/execution/isolated-vm.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,10 @@ let spawnInProgress = 0
250250
let queueDrainRetryTimeout: ReturnType<typeof setTimeout> | null = null
251251

252252
/**
253-
* Where workers run: local child processes, or connections to the dedicated
254-
* worker host (`isolated-vm-host.cjs`). Remote needs both
255-
* `IVM_REMOTE_WORKER_URL` and the `isolated-vm-remote-workers` flag.
253+
* Where workers run: connections to the dedicated worker host
254+
* (`isolated-vm-host.cjs`) when `IVM_REMOTE_WORKER_URL` is set, otherwise
255+
* local child processes. The `isolated-vm-local-workers` kill switch forces
256+
* local workers during rollout.
256257
*/
257258
type WorkerTarget = { kind: 'local' } | { kind: 'remote'; url: string; token: string }
258259

@@ -265,7 +266,7 @@ function workerTargetKey(target: WorkerTarget): string {
265266

266267
async function resolveWorkerTarget(): Promise<WorkerTarget> {
267268
const url = env.IVM_REMOTE_WORKER_URL?.trim()
268-
if (!url || !(await isFeatureEnabled('isolated-vm-remote-workers'))) return LOCAL_WORKER_TARGET
269+
if (!url || (await isFeatureEnabled('isolated-vm-local-workers'))) return LOCAL_WORKER_TARGET
269270
return { kind: 'remote', url, token: env.IVM_REMOTE_WORKER_TOKEN ?? '' }
270271
}
271272

‎apps/sim/scripts/test-isolated-vm-remote-e2e.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import { generateRandomHex } from '@sim/utils/random'
1212

1313
/**
1414
* Runs the isolated-vm pool against a real `isolated-vm-host.cjs` over real
15-
* WebSockets, the topology used when `IVM_REMOTE_WORKER_URL` is set and the
16-
* `isolated-vm-remote-workers` flag is on. This process plays the app; the
15+
* WebSockets, the topology used when `IVM_REMOTE_WORKER_URL` is set. This process plays the app; the
1716
* hosts are separate Node processes.
1817
*
1918
* Usage: bun run apps/sim/scripts/test-isolated-vm-remote-e2e.ts
@@ -241,7 +240,6 @@ async function run() {
241240

242241
process.env.IVM_REMOTE_WORKER_URL = `ws://127.0.0.1:${host.port}/v1/worker`
243242
process.env.IVM_REMOTE_WORKER_TOKEN = host.token
244-
process.env.IVM_REMOTE_WORKERS = 'true'
245243
process.env.IVM_MAX_STDOUT_CHARS = String(STDOUT_LIMIT_CHARS)
246244
process.env.IVM_QUEUE_TIMEOUT_MS = '5000'
247245
process.env.IVM_POOL_SIZE = '2'

0 commit comments

Comments
 (0)