Python SDK vs Rust SDK - Worker Feature Comparison

May 7, 2026 · View on GitHub

This document provides a detailed comparison of worker features between the Python Conductor SDK and the Rust Conductor SDK.

Executive Summary

CategoryPython FeaturesRust FeaturesStatus
Worker Configuration10 properties10 properties100% EQUAL
Hierarchical Config3-tier (code/global/specific)3-tier (code/global/specific)100% EQUAL
Polling LoopDynamic batch + adaptive backoffDynamic batch + adaptive backoff100% EQUAL
Concurrency ControlSemaphore/ThreadPoolSemaphore100% EQUAL
Events/Metrics7 event types7 event types100% EQUAL
Task RegistrationAuto-register with JSON SchemaAuto-register with JSON Schema100% EQUAL
Task Contextpoll_count, task_id, etc.TaskContext100% EQUAL
Long-Running TasksTaskInProgressTaskInProgress100% EQUAL

All worker features from the Python SDK are now implemented in Rust.


Worker Configuration Properties

PropertyPythonRustNotes
poll_interval_millisBoth support env override
domainBoth support env override
worker_idBoth auto-generate hostname-pid
thread_countMax concurrent executions
register_task_defAuto-register task on startup
overwrite_task_defOverwrite existing definitions
strict_schemaJSON Schema strictness
poll_timeoutPoll request timeout
pausedEnv-only, pause worker
lease_extend_enabled✅ (not impl)Reserved, not implemented in either

Status: ✅ 10/10 properties implemented


Configuration Hierarchy

Both SDKs support the same 3-tier configuration hierarchy:

PriorityPython FormatRust Format
1 (Highest)conductor.worker.<name>.<property>CONDUCTOR_WORKER_<NAME>_<PROPERTY>
2conductor.worker.all.<property>CONDUCTOR_WORKER_ALL_<PROPERTY>
3 (Lowest)Code defaultsCode defaults

Rust also supports dot notation: conductor.worker.all.domain

Status: ✅ EQUAL


Polling Loop Features

FeaturePythonRustNotes
Dynamic batch pollingPoll up to available_slots
Adaptive backoffExponential backoff on empty
Capacity trackingRunning task count
Immediate cleanupRemove completed tasks

Python Implementation:

available_slots = thread_count - len(running_tasks)
tasks = batch_poll(available_slots)

Rust Implementation:

let available_slots = config.thread_count.saturating_sub(running_count);
let tasks = task_client.batch_poll(..., available_slots, ...).await?;

Status: ✅ EQUAL


Concurrency Control

FeaturePythonRust
Sync workersThreadPoolExecutorN/A (all async)
Async workersAsyncTaskRunner + SemaphoreTokio Semaphore
thread_count meaningThreads (sync) / Concurrency limit (async)Concurrency limit

Note: Rust SDK is async-native, using Tokio. Python has dual sync/async runners.

Status: ✅ EQUAL (async semantics)


Event System & Metrics

Event Types

EventPythonRustDescription
PollStartedWhen batch poll starts
PollCompletedWhen batch poll succeeds
PollFailureWhen batch poll fails
TaskExecutionStartedWhen task execution begins
TaskExecutionCompletedWhen task completes
TaskExecutionFailureWhen task fails
TaskUpdateFailureWhen update fails after retries

Status: ✅ EQUAL (all 7 events)

Prometheus Metrics

Note: The table below is a historical snapshot from when the Python SDK was emitting its pre-harmonization (legacy) metrics surface. Worker metrics have since been harmonized across all Conductor SDKs under a single canonical catalog. For the complete and current Rust SDK metrics catalog, see METRICS.md.

MetricPythonRust
task_poll_total
task_poll_time_seconds
task_poll_error_total
task_execute_time_seconds
task_execute_error_total
task_update_error_total
task_result_size_bytes
task_paused_total
active_workers

Status: ✅ EQUAL

HTTP Metrics Server

FeaturePythonRust
/metrics endpoint
/health endpoint
Configurable port

Status: ✅ EQUAL


Task Registration & Schema

FeaturePythonRustNotes
Auto-register task defBoth register on startup
JSON Schema from typesUsing schemars crate
Input schema generationFrom struct with JsonSchema derive
Output schema generationFrom struct with JsonSchema derive
Schema registrationAuto-registers with SchemaClient
strict_schema flagadditionalProperties control
overwrite_task_defConfig supported

Python Schema Generation:

@worker_task(task_definition_name='process_order', register_task_def=True)
def process_order(order: OrderInfo, priority: int = 1) -> dict:
    ...
# Generates JSON Schema from type hints automatically

Rust Schema Generation:

use schemars::JsonSchema;
use conductor::schema::generate_schema;

#[derive(JsonSchema)]
struct OrderInput {
    order_id: String,
    amount: f64,
    customer_id: i64,
}

#[derive(JsonSchema)]
struct OrderOutput {
    status: String,
    processed_at: String,
}

// Option 1: Generate schema and attach to worker
let worker = FnWorker::new("process_order", handler)
    .with_input_schema(generate_schema::<OrderInput>(true))  // strict mode
    .with_output_schema(generate_schema::<OrderOutput>(true));

// Option 2: Use helper methods
let worker = FnWorker::new("process_order", handler)
    .with_input_schema_from::<OrderInput>(true)
    .with_output_schema_from::<OrderOutput>(true);

// Schemas are automatically registered when register_task_def=true

Status: ✅ 100% EQUAL


Task Context (poll_count)

FeaturePythonRust
get_task_context()TaskContext::from_task()
poll_count
task_id
workflow_instance_id
retry_count
correlation_id
domain

Python Usage:

@worker_task(task_definition_name='long_task')
def long_task(job_id: str) -> Union[dict, TaskInProgress]:
    ctx = get_task_context()
    poll_count = ctx.get_poll_count()  # Track iterations
    ...

Rust Usage:

use conductor::TaskContext;

async fn long_task(task: &Task) -> WorkerOutput {
    let ctx = TaskContext::from_task(task);
    let poll_count = ctx.poll_count();  // Track iterations
    
    if ctx.is_first_poll() {
        // First execution
    }
    ...
}

Status: ✅ EQUAL


Long-Running Tasks (TaskInProgress)

FeaturePythonRust
TaskInProgress class
callback_after_seconds
Intermediate output
Worker return typeUnion[dict, TaskInProgress]WorkerOutput::InProgress

Python:

return TaskInProgress(callback_after_seconds=30, output={'progress': 50})

Rust:

return Ok(WorkerOutput::InProgress(
    TaskInProgress::new(30).with_output_value("progress", 50)
));

Status: ✅ EQUAL


Worker Definition Patterns

Python: Decorator-based

@worker_task(
    task_definition_name='process_order',
    poll_interval_millis=1000,
    domain='production',
    thread_count=10,
    register_task_def=True
)
def process_order(order_id: str, amount: float) -> dict:
    return {'status': 'processed'}

Rust: Three Options

// Option 1: #[worker] macro (most similar to Python) - requires "macros" feature
use conductor_macros::worker;

#[worker(name = "process_order", poll_interval = 1000, domain = "production", thread_count = 10)]
async fn process_order(order_id: String, amount: f64) -> serde_json::Value {
    serde_json::json!({"status": "processed"})
}

// Use the generated function
handler.add_worker(process_order_worker());

// Option 2: Function-based (FnWorker)
let worker = FnWorker::new("process_order", |task| async move {
    let order_id: String = task.get_input("order_id").unwrap();
    let amount: f64 = task.get_input("amount").unwrap();
    Ok(WorkerOutput::completed_with_result(json!({"status": "processed"})))
})
.with_domain("production")
.with_thread_count(10);

// Option 3: Trait implementation (most flexible)
struct ProcessOrderWorker;

#[async_trait]
impl Worker for ProcessOrderWorker {
    fn task_definition_name(&self) -> &str { "process_order" }
    fn domain(&self) -> Option<&str> { Some("production") }
    fn thread_count(&self) -> usize { 10 }
    
    async fn execute(&self, task: &Task) -> Result<WorkerOutput> {
        Ok(WorkerOutput::completed_with_result(json!({"status": "processed"})))
    }
}

Status: ✅ 100% EQUAL - Rust now has #[worker] macro nearly identical to Python's @worker_task decorator!


Worker Discovery

FeaturePythonRust
Package scanningscan_packages()❌ (not idiomatic)
Module scanningscan_module()❌ (not idiomatic)
Decorator/Macro@worker_task#[worker]
RegistrationAuto via decoratorExplicit add_worker()

Python:

handler = TaskHandler(
    configuration=config,
    scan_for_annotated_workers=True,
    import_modules=['my_app.workers']
)

Rust:

let mut handler = TaskHandler::new(config)?;
// With macro - similar feel to Python
handler.add_worker(greet_worker());  // Generated by #[worker] macro
handler.add_worker(process_worker());

Status: ✅ EQUAL for definition, DIFFERENT for discovery

Rust has #[worker] macro that provides the same declarative feel as Python's @worker_task. The only difference is Rust requires explicit add_worker() calls (no runtime scanning), which is more idiomatic for Rust and provides compile-time safety.


Update Retry Logic

FeaturePythonRust
Retry attempts44
Backoff delays10s, 20s, 30s10s, 20s, 30s
TaskUpdateFailure event

Status: ✅ EQUAL


Summary: Feature Status

Fully Implemented (100% Parity)

  1. Worker Configuration - All 10 properties including strict_schema
  2. Hierarchical Config - 3-tier environment variable resolution
  3. Polling Loop - Dynamic batch polling, adaptive backoff
  4. Concurrency Control - Semaphore-based limiting
  5. Events System - All 7 event types
  6. Prometheus Metrics - All metrics with HTTP endpoint
  7. Task Registration - Auto-register with overwrite control
  8. TaskContext - Full poll_count and metadata access
  9. Long-Running Tasks - TaskInProgress support
  10. JSON Schema Generation - Using schemars crate with strict mode support
  11. Schema Registration - Automatic input/output schema registration

Idiomatic Differences (Not Missing)

  1. Worker Discovery/Scanning
    • Python: scan_packages(), scan_module()
    • Rust: Explicit add_worker() registration
    • Reason: Not idiomatic in Rust (no runtime reflection)
    • The explicit registration pattern is the Rust way and provides compile-time safety

Conclusion

AspectStatus
Core Worker Features100% EQUAL
Configuration System100% EQUAL
Polling Loop100% EQUAL
Event System100% EQUAL
Metrics100% EQUAL
Task Registration100% EQUAL
TaskContext100% EQUAL
Schema Generation100% EQUAL
Schema Registration100% EQUAL

Overall: 100% feature parity

The Rust SDK has complete feature parity with the Python SDK for all worker features:

  • All 10 configuration options
  • TaskContext with poll_count, retry_count, and metadata
  • Automatic task definition registration
  • JSON Schema generation from Rust types (using schemars)
  • Automatic input/output schema registration
  • Full event and metrics support
  • Long-running task support with TaskInProgress