Deployment and scaling

July 28, 2026 · View on GitHub

Deployment shapes

ShapeUse
Worker serviceA long-lived process hosting WorkflowTaskHost for one or more task types. The standard production shape.
Agent worker serviceruntime.ServeAsync(ct, agents) — hosts local [Tool] workers for already-deployed agents.
Definition pushRegisterWorkflow / runtime.DeployAsync(agents) in CI. No execution, no workers.
EmbeddedWorkers alongside application code. Fine for low volume; couples worker lifetime to your app.

Separating definition push from worker hosting is the important split: CI pushes definitions, and worker services scale independently.

// CI: push definitions, exit.
await runtime.DeployAsync(docAssistant, opsBot);

// Worker service: host tool workers, block until shut down.
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };
await runtime.ServeAsync(cts.Token, docAssistant, opsBot);

ServeAsync deploys each agent idempotently before starting its workers, so it is safe to run without a preceding deploy. Pass blocking: false to return once workers are polling — useful when hosting inside an existing application lifetime.

Scaling workers

Scale horizontally: run more worker processes polling the same task type. The server distributes tasks; no coordination is needed and no worker owns a partition.

The exception is stateful agent runs, which pin tasks to the process that started them via a per-run domain. Those do not distribute, and adding processes does not help a stalled run. See reliability.md.

Poll-loop tuning

Core SDK workers configure themselves through WorkflowTaskExecutorConfiguration, per worker. Agent tool workers use AgentConfig:

SettingEnv varDefault
Worker threads per task typeCONDUCTOR_AGENT_WORKER_THREADS1
Poll interval (ms)CONDUCTOR_AGENT_WORKER_POLL_INTERVAL100
Auto-start workersCONDUCTOR_AGENT_AUTO_START_WORKERStrue
Daemon worker threadsCONDUCTOR_AGENT_DAEMON_WORKERStrue

Tuning guidance:

  • Thread count should track task duration, not task volume. Long-running I/O-bound tasks want more threads; fast CPU-bound tasks want roughly one per core across the fleet.
  • Poll interval trades latency against server load. Lowering it across many workers multiplies request volume — prefer more threads on fewer processes over aggressive polling on many.
  • Set these per worker. A slow task and a fast task in one process should not share a configuration.

DaemonWorkers = false makes worker threads foreground, so the process will not exit while they run — appropriate for a dedicated worker service, wrong for a CLI that should terminate.

Graceful shutdown

Cancel the token and let in-flight tasks finish, rather than killing the process — an abandoned in-progress task waits for its server-side timeout before being rescheduled.

Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };

await using on the runtime shuts down any local tool workers it started.

Containers

The repo ships a Dockerfile at the root and a Harness/ project with its own image, built by .github/workflows/harness-image.yml. csharp-examples/ has a Dockerfile and a DockerfileMacArm variant for Apple Silicon.

Containerised workers need CONDUCTOR_SERVER_URL reachable from inside the container — localhost refers to the container, not the host.

Capacity signals

Watch queue depth and poll/execution metrics to decide when to scale. See observability.md.