feat(automator): CONDUCTOR_WORKER_ISOLATION=thread worker isolation mode#436
Merged
Conversation
CONDUCTOR_WORKER_ISOLATION env knob (process default / thread), the Process->Thread shim moved verbatim from worker_manager plus the missing Queue->queue.Queue half, thread-safe signal.signal, idempotency flag. Dead code at this commit — wired up in the next one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TaskHandler.__init__ applies thread isolation (before the logging Queue / logger Process are created) when the env var selects it; the Windows-only worker_manager shim becomes a delegating alias to the shared implementation. Default (process) path is byte-identical — spawn remains the start method. Fixes tool workers never polling inside Firecracker microVM guests, where multiprocessing's fork+exec bootstraps (spawn children, resource_tracker) fail. Verified E2E in the AgentSpan runtime stack. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stant
The flag guards set_start_method("spawn"), not fork — the old name
described behavior this code stopped having. _VALID_MP_START_METHODS
had zero references.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…olation() directly _patch_conductor_use_threads_on_windows was a private, single-caller delegating alias after the shim moved to worker_isolation; Windows and CONDUCTOR_WORKER_ISOLATION=thread now share one visible entry point. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests.
🚀 New features to boost your workflow:
|
v1r3n
approved these changes
Jul 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Inside Firecracker microVM guests (and similar minimal environments),
multiprocessing's twofork+exec bootstraps reliably fail:
Process()underspawnexits 255 before its target runs;Queue()breaks theresource_trackerhandshake (silent corruption at 1 vCPU,BrokenPipeError [Errno 32]inresource_tracker._sendat 4 vCPUs).TaskHandlerhard-wires both (Queue()in__init__, oneProcessper worker), so every workerdies at bootstrap and tool tasks are never polled (
pollCount: 0forever). Ruled out by directreproduction: jailer/seccomp (bare-Firecracker repro), memory (256 MB→1 GB identical), musl/Alpine
(chroot repro passes), PID-1 reaping (
unshare --pidrepro passes). Identical work onthreading.Threadsucceeds every time.Fix
A first-class worker isolation mode, selected by env var:
CONDUCTOR_WORKER_ISOLATIONprocess(default / unset)multiprocessing.Processunderspawnthreadthreading.Thread; log relay a plainqueue.Queueconductor.ai.agents.runtime.worker_managerto a newconductor.client.automator.worker_isolation(the layer that owns the worker bootstrap, andusable by plain
TaskHandlerdeployments without theagentsextra). It gains the half Windowsnever needed:
task_handler.Queue → queue.Queue.TaskHandler.__init__applies it as its first statement (must precede_setup_logging_queue(),which creates the
Queueand starts the loggerProcess)._patch_conductor_use_threads_on_windowsremains as a delegating alias; theplatform.system() == "Windows"call site is unchanged.process(fail-open).This is not a start-method change.
spawnstays the sole start method (per the removal ofCONDUCTOR_MP_START_METHODin1b03c812— fork's macOS SIGSEGV / held-lock hazards). Fork alsowouldn't fix this:
resource_trackeris fork+exec'd regardless of start method. Thread modebypasses
multiprocessingentirely.Tradeoffs (thread mode only, documented in the module docstring)
No per-worker force-kill (
terminate/killare no-ops, shutdown cooperative — in the targetenvironment the microVM is the isolation boundary and the platform hard-kills at VM level);
CPU-bound workers share the GIL (tool workers are I/O-bound);
signal.signalno-ops off the mainthread instead of raising. Known cosmetic issue: duplicate console log lines in thread mode
(double handlers once the relay is same-process) — not a correctness problem.
Testing
tests/unit/automator/test_worker_isolation.py+ alias/gate tests intests/unit/ai/test_worker_manager.py); all patched globals snapshot/restored per test.tests/unit2582 passed / 1 skipped;tests/backwardcompatibility+tests/serdesertest1077 passed.monkeypatch removed and only the env var active: tool polled + executed, workflow
COMPLETED.Negative control (env var removed): the hang reproduces — the switch is the active ingredient.
🤖 Generated with Claude Code