{"id":25170111,"url":"https://github.com/codaxy/conductor-sharp","last_synced_at":"2026-03-05T16:21:49.032Z","repository":{"id":37090044,"uuid":"472906155","full_name":"codaxy/conductor-sharp","owner":"codaxy","description":null,"archived":false,"fork":false,"pushed_at":"2025-04-23T13:50:16.000Z","size":718,"stargazers_count":5,"open_issues_count":9,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-30T22:55:42.109Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codaxy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-22T19:19:18.000Z","updated_at":"2025-04-23T12:11:27.000Z","dependencies_parsed_at":"2023-11-24T10:24:55.966Z","dependency_job_id":"b7a201dc-4241-4123-bc30-cf7aa7028c9e","html_url":"https://github.com/codaxy/conductor-sharp","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codaxy%2Fconductor-sharp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codaxy%2Fconductor-sharp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codaxy%2Fconductor-sharp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codaxy%2Fconductor-sharp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codaxy","download_url":"https://codeload.github.com/codaxy/conductor-sharp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252547379,"owners_count":21765979,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-09T08:38:34.126Z","updated_at":"2026-03-05T16:21:49.015Z","avatar_url":"https://github.com/codaxy.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ConductorSharp\n\nA comprehensive .NET client library for [Conductor](https://github.com/conductor-oss/conductor) workflow orchestration engine. Features a strongly-typed workflow builder DSL, task handlers, and quality-of-life additions for building robust workflow applications.\n\n[![NuGet](https://img.shields.io/nuget/v/ConductorSharp.Client.svg)](https://www.nuget.org/packages/ConductorSharp.Client)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n**Note: This documentation has been AI generated and human reviewed.**\n\n\u003e **AI Assistant Users**: See [SKILL.md](SKILL.md) for a condensed reference guide optimized for AI coding assistants. It provides quick-reference documentation for all task types, configuration options, and common patterns. This file follows the [Agent Skills](https://code.claude.com/docs/en/skills) open standard for extending AI assistant capabilities.\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Core Concepts](#core-concepts)\n  - [Workflow Definition](#workflow-definition)\n  - [Task Handlers](#task-handlers)\n  - [Input/Output Models](#inputoutput-models)\n  - [Task Input Specification](#task-input-specification)\n- [Task Types](#task-types)\n- [Configuration](#configuration)\n- [Pipeline Behaviors](#pipeline-behaviors)\n- [Health Checks](#health-checks)\n- [Patterns Package](#patterns-package)\n- [Kafka Cancellation Notifier](#kafka-cancellation-notifier)\n- [Toolkit CLI](#toolkit-cli)\n- [API Services](#api-services)\n- [Running the Examples](#running-the-examples)\n- [General Notes](#general-notes)\n\n## Installation\n\n### Core Packages\n\n```bash\n# API client for Conductor\ndotnet add package ConductorSharp.Client\n\n# Workflow engine with builder DSL, task handlers, and worker scheduling\ndotnet add package ConductorSharp.Engine\n```\n\n### Additional Packages\n\n```bash\n# Built-in tasks (WaitSeconds, ReadWorkflowTasks, C# Lambda Tasks)\ndotnet add package ConductorSharp.Patterns\n\n# Kafka-based task cancellation notifications\ndotnet add package ConductorSharp.KafkaCancellationNotifier\n\n# CLI tool for scaffolding task/workflow definitions\ndotnet tool install --global ConductorSharp.Toolkit\n```\n\n## Quick Start\n\n### 1. Configure Services\n\n```csharp\nusing ConductorSharp.Engine.Extensions;\nusing Microsoft.Extensions.Hosting;\n\nvar builder = Host.CreateApplicationBuilder(args);\nbuilder.Services\n    .AddConductorSharp(baseUrl: \"http://localhost:8080\")\n    .AddExecutionManager(\n        maxConcurrentWorkers: 10,\n        sleepInterval: 500,\n        longPollInterval: 100,\n        domain: null,\n        typeof(Program).Assembly\n    )\n    .AddPipelines(pipelines =\u003e\n    {\n        pipelines.AddRequestResponseLogging();\n        pipelines.AddValidation();\n    });\n\nbuilder.Services.RegisterWorkflow\u003cMyWorkflow\u003e();\n\nvar host = builder.Build();\nawait host.RunAsync();\n```\n\n### 2. Define a Task Handler\n\n```csharp\nusing ConductorSharp.Engine.Builders.Metadata;\nusing ConductorSharp.Engine;\n\npublic class PrepareEmailRequest : IRequest\u003cPrepareEmailResponse\u003e\n{\n    public string CustomerName { get; set; }\n    public string Address { get; set; }\n}\n\npublic class PrepareEmailResponse\n{\n    public string EmailBody { get; set; }\n}\n\n[OriginalName(\"EMAIL_prepare\")]\npublic class PrepareEmailHandler : TaskRequestHandler\u003cPrepareEmailRequest, PrepareEmailResponse\u003e\n{\n    public override async Task\u003cPrepareEmailResponse\u003e Handle(PrepareEmailRequest request, CancellationToken cancellationToken)\n    {\n        var body = $\"Hello {request.CustomerName} at {request.Address}!\";\n        return new PrepareEmailResponse { EmailBody = body };\n    }\n}\n```\n\n### 3. Define a Workflow\n\n```csharp\nusing ConductorSharp.Engine.Builders;\nusing ConductorSharp.Engine.Builders.Metadata;\n\npublic class SendNotificationInput : WorkflowInput\u003cSendNotificationOutput\u003e\n{\n    public int CustomerId { get; set; }\n}\n\npublic class SendNotificationOutput : WorkflowOutput\n{\n    public string EmailBody { get; set; }\n}\n\n[OriginalName(\"NOTIFICATION_send\")]\n[WorkflowMetadata(OwnerEmail = \"team@example.com\")]\npublic class SendNotificationWorkflow : Workflow\u003cSendNotificationWorkflow, SendNotificationInput, SendNotificationOutput\u003e\n{\n    public SendNotificationWorkflow(\n        WorkflowDefinitionBuilder\u003cSendNotificationWorkflow, SendNotificationInput, SendNotificationOutput\u003e builder\n    ) : base(builder) { }\n\n    public GetCustomerHandler GetCustomer { get; set; }\n    public PrepareEmailHandler PrepareEmail { get; set; }\n\n    public override void BuildDefinition()\n    {\n        _builder.AddTask(\n            wf =\u003e wf.GetCustomer,\n            wf =\u003e new GetCustomerRequest { CustomerId = wf.WorkflowInput.CustomerId }\n        );\n\n        _builder.AddTask(\n            wf =\u003e wf.PrepareEmail,\n            wf =\u003e new PrepareEmailRequest \n            { \n                CustomerName = wf.GetCustomer.Output.Name,\n                Address = wf.GetCustomer.Output.Address \n            }\n        );\n\n        _builder.SetOutput(wf =\u003e new SendNotificationOutput\n        {\n            EmailBody = wf.PrepareEmail.Output.EmailBody\n        });\n    }\n}\n```\n\n## Core Concepts\n\n### Workflow Definition\n\nWorkflows are defined by inheriting from `Workflow\u003cTWorkflow, TInput, TOutput\u003e`:\n\n```csharp\npublic class MyWorkflow : Workflow\u003cMyWorkflow, MyWorkflowInput, MyWorkflowOutput\u003e\n{\n    public MyWorkflow(WorkflowDefinitionBuilder\u003cMyWorkflow, MyWorkflowInput, MyWorkflowOutput\u003e builder) \n        : base(builder) { }\n\n    // Task properties - these become task references in the workflow\n    public SomeTaskHandler FirstTask { get; set; }\n    public AnotherTaskHandler SecondTask { get; set; }\n\n    public override void BuildDefinition()\n    {\n        // Add tasks with strongly-typed input expressions\n        _builder.AddTask(wf =\u003e wf.FirstTask, wf =\u003e new SomeTaskRequest { Input = wf.WorkflowInput.SomeValue });\n        _builder.AddTask(wf =\u003e wf.SecondTask, wf =\u003e new AnotherTaskRequest { Input = wf.FirstTask.Output.Result });\n        \n        // Set workflow output\n        _builder.SetOutput(wf =\u003e new MyWorkflowOutput { Result = wf.SecondTask.Output.Value });\n    }\n}\n```\n\n### Task Handlers\n\n```csharp\n[OriginalName(\"MY_TASK_name\")]\npublic class MyTaskHandler : TaskRequestHandler\u003cMyTaskRequest, MyTaskResponse\u003e\n{\n    public override async Task\u003cMyTaskResponse\u003e Handle(MyTaskRequest request, CancellationToken cancellationToken)\n    {\n        return new MyTaskResponse { /* ... */ };\n    }\n}\n```\n\n### Input/Output Models\n\n```csharp\n// Workflow I/O\npublic class MyWorkflowInput : WorkflowInput\u003cMyWorkflowOutput\u003e\n{\n    public string CustomerId { get; set; }\n}\n\npublic class MyWorkflowOutput : WorkflowOutput\n{\n    public string Result { get; set; }\n}\n\n// Task I/O\npublic class MyTaskRequest : IRequest\u003cMyTaskResponse\u003e\n{\n    [Required]\n    public string InputValue { get; set; }\n}\n\npublic class MyTaskResponse\n{\n    public string OutputValue { get; set; }\n}\n```\n\n### Task Input Specification\n\nIn Conductor, task inputs in workflows are specified using Conductor expressions with the format: `${SOURCE.input/output.JSONPath}`. The `SOURCE` can be `workflow` or a task reference name in the workflow definition. `input/output` refers to the input of the workflow or output of the task. JSONPath is used to traverse the input/output object.\n\nConductorSharp generates these expressions automatically when writing workflows. Here's an example:\n\n```csharp\n_builder.AddTask(\n    wf =\u003e wf.PrepareEmail,\n    wf =\u003e new PrepareEmailRequest\n    {\n        CustomerName = $\"{wf.GetCustomer.Output.FirstName} {wf.GetCustomer.Output.LastName}\",\n        Address = wf.WorkflowInput.Address\n    }\n);\n```\n\nThis is converted to the following Conductor input parameters specification:\n\n```json\n\"inputParameters\": {\n    \"customer_name\": \"${get_customer.output.first_name} ${get_customer.output.last_name}\",\n    \"address\": \"${workflow.input.address}\"\n}\n```\n\n#### Casting\n\nWhen input/output parameters are of different types, casting can be used:\n\n```csharp\nwf =\u003e new PrepareEmailRequest\n{\n    CustomerName = ((FullName)wf.GetCustomer.Output.Name).FirstName,\n    Address = (string)wf.GetCustomer.Output.Address\n}\n```\n\nThis translates to:\n\n```json\n\"inputParameters\": {\n    \"customer_name\": \"${get_customer.output.name.first_name}\",\n    \"address\": \"${get_customer.output.address}\"\n}\n```\n\n#### Array Initialization\n\nArray initialization is supported. Arrays can be typed or dynamic:\n\n```csharp\nwf =\u003e new()\n{\n    Integers = new[] { 1, 2, 3 },\n    TestModelList = new List\u003cArrayTaskInput.TestModel\u003e\n    {\n        new ArrayTaskInput.TestModel { String = wf.Input.TestValue },\n        new ArrayTaskInput.TestModel { String = \"List2\" }\n    },\n    Models = new[]\n    {\n        new ArrayTaskInput.TestModel { String = \"Test1\" },\n        new ArrayTaskInput.TestModel { String = \"Test2\" }\n    },\n    Objects = new dynamic[] { new { AnonymousObjProp = \"Prop\" }, new { Test = \"Prop\" } }\n}\n```\n\nThis translates to:\n\n```json\n\"inputParameters\": {\n    \"integers\": [1, 2, 3],\n    \"test_model_list\": [\n        {\n            \"string\": \"${workflow.input.test_value}\"\n        },\n        {\n            \"string\": \"List2\"\n        }\n    ],\n    \"models\": [\n        {\n            \"string\": \"Test1\"\n        },\n        {\n            \"string\": \"Test2\"\n        }\n    ],\n    \"objects\": [\n        {\n            \"anonymous_obj_prop\": \"Prop\"\n        },\n        {\n            \"test\": \"Prop\"\n        }\n    ]\n}\n```\n\n#### Object Initialization\n\nObject initialization is supported, including anonymous objects when initializing sub-properties:\n\n```csharp\nwf =\u003e new()\n{\n    NestedObjects = new TestModel\n    {\n        Integer = 1,\n        String = \"test\",\n        Object = new TestModel\n        {\n            Integer = 1,\n            String = \"string\",\n            Object = new { NestedInput = \"1\" }\n        }\n    }\n}\n```\n\nThis translates to:\n\n```json\n\"inputParameters\": {\n    \"nested_objects\": {\n        \"integer\": 1,\n        \"string\": \"test\",\n        \"object\": {\n            \"integer\": 1,\n            \"string\": \"string\",\n            \"object\": {\n                \"nested_input\": \"1\"\n            }\n        }\n    }\n}\n```\n\n#### Indexing\n\nDictionary indexing is supported. Indexing using an indexer on arbitrary types is currently not supported:\n\n```csharp\nwf =\u003e new()\n{\n    CustomerName = wf.WorkflowInput.Dictionary[\"test\"].CustomerName,\n    Address = wf.WorkflowInput.DoubleDictionary[\"test\"][\"address\"]\n}\n```\n\nThis translates to:\n\n```json\n\"inputParameters\": {\n    \"customer_name\": \"${workflow.input.dictionary['test'].customer_name}\",\n    \"address\": \"${workflow.input.double_dictionary['test']['address']}\"\n}\n```\n\n#### Workflow Name\n\nYou can embed the name of any workflow in task input specification using `NamingUtil.NameOf\u003cT\u003e()`:\n\n```csharp\nwf =\u003e new()\n{\n    Name = $\"Workflow name: {NamingUtil.NameOf\u003cStringInterpolation\u003e()}\",\n    WfName = NamingUtil.NameOf\u003cStringInterpolation\u003e()\n}\n```\n\nThis translates to:\n\n```json\n\"inputParameters\": {\n    \"name\": \"Workflow name: TEST_StringInterpolation\",\n    \"wf_name\": \"TEST_StringInterpolation\"\n}\n```\n\nNote: `StringInterpolation` has an attribute `[OriginalName(\"TEST_StringInterpolation\")]` applied.\n\n#### String Concatenation\n\nString concatenation is supported. You can concatenate strings with numbers, input/output parameters, and interpolation strings:\n\n```csharp\nwf =\u003e new()\n{\n    Input = 1\n        + \"Str_\"\n        + \"2Str_\"\n        + wf.WorkflowInput.Input\n        + $\"My input: {wf.WorkflowInput.Input}\"\n        + NamingUtil.NameOf\u003cStringAddition\u003e()\n        + 1\n}\n```\n\nThis translates to:\n\n```json\n\"inputParameters\": {\n    \"input\": \"1Str_2Str_${workflow.input.input}My input: ${workflow.input.input}string_addition1\"\n}\n```\n\nNote: `StringAddition` has an attribute `[OriginalName(\"string_addition\")]` applied.\n\n### Metadata Attributes\n\n| Attribute | Target | Description |\n|-----------|--------|-------------|\n| `[OriginalName(\"NAME\")]` | Class | Custom task/workflow name in Conductor |\n| `[WorkflowMetadata(...)]` | Class | Workflow metadata (OwnerEmail, OwnerApp, Description, FailureWorkflow) |\n| `[Version(n)]` | Class | Version number for sub-workflow references |\n| `[TaskDomain(\"domain\")]` | Class | Assign task to specific domain |\n\nNote: There is no task equivalent of the `WorkflowMetadata` attribute. The task metadata is configured when registering the task:\n\n```csharp\nservices.RegisterWorkerTask\u003cMyTaskHandler\u003e(options =\u003e\n{\n    options.OwnerEmail = \"team@example.com\";\n    options.Description = \"My task description\";\n});\n```\n\n## Task Types\n\n### Simple Task\n\n```csharp\n_builder.AddTask(wf =\u003e wf.MySimpleTask, wf =\u003e new MySimpleTaskRequest { Input = wf.WorkflowInput.Value });\n```\n\n### Sub-Workflow Task\n\n```csharp\npublic SubWorkflowTaskModel\u003cChildWorkflowInput, ChildWorkflowOutput\u003e ChildWorkflow { get; set; }\n\n_builder.AddTask(wf =\u003e wf.ChildWorkflow, wf =\u003e new ChildWorkflowInput { CustomerId = wf.WorkflowInput.CustomerId });\n```\n\n### Switch Task (Conditional Branching)\n\n```csharp\npublic SwitchTaskModel SwitchTask { get; set; }\npublic TaskA TaskInCaseA { get; set; }\npublic TaskB TaskInCaseB { get; set; }\n\n_builder.AddTask(\n    wf =\u003e wf.SwitchTask,\n    wf =\u003e new SwitchTaskInput { SwitchCaseValue = wf.WorkflowInput.Operation },\n    new DecisionCases\u003cMyWorkflow\u003e\n    {\n        [\"caseA\"] = builder =\u003e builder.AddTask(wf =\u003e wf.TaskInCaseA, wf =\u003e new TaskARequest { }),\n        [\"caseB\"] = builder =\u003e builder.AddTask(wf =\u003e wf.TaskInCaseB, wf =\u003e new TaskBRequest { }),\n        DefaultCase = builder =\u003e { /* default case tasks */ }\n    }\n);\n```\n\n### Dynamic Task\n\n```csharp\npublic DynamicTaskModel\u003cExpectedInput, ExpectedOutput\u003e DynamicHandler { get; set; }\n\n_builder.AddTask(\n    wf =\u003e wf.DynamicHandler,\n    wf =\u003e new DynamicTaskInput\u003cExpectedInput, ExpectedOutput\u003e\n    {\n        TaskInput = new ExpectedInput { CustomerId = wf.WorkflowInput.CustomerId },\n        TaskToExecute = wf.WorkflowInput.TaskName  // Task name resolved at runtime\n    }\n);\n```\n\n### Dynamic Fork-Join Task\n\n```csharp\npublic DynamicForkJoinTaskModel DynamicFork { get; set; }\n\n_builder.AddTask(\n    wf =\u003e wf.DynamicFork,\n    wf =\u003e new DynamicForkJoinInput\n    {\n        DynamicTasks = /* list of tasks */,\n        DynamicTasksInput = /* corresponding inputs */\n    }\n);\n```\n\n### Do-While Loop Task\n\n```csharp\npublic DoWhileTaskModel DoWhile { get; set; }\npublic CustomerGetHandler GetCustomer { get; set; }\n\n_builder.AddTask(\n    wf =\u003e wf.DoWhile,\n    wf =\u003e new DoWhileInput { Value = wf.WorkflowInput.Loops },\n    \"$.do_while.iteration \u003c $.value\",  // Loop condition\n    builder =\u003e\n    {\n        builder.AddTask(wf =\u003e wf.GetCustomer, wf =\u003e new CustomerGetRequest { CustomerId = \"CUSTOMER-1\" });\n    }\n);\n```\n\nNote: ConductorSharp does not provide a strongly typed output for the `DoWhile` task, as can be seen from the implementation:\n\n```csharp\npublic class DoWhileTaskModel : TaskModel\u003cDoWhileInput, NoOutput\u003e\n{\n}\n```\n\n### Lambda Task (JavaScript)\n\n```csharp\npublic class LambdaInput : IRequest\u003cLambdaOutput\u003e\n{\n    public string Value { get; set; }\n}\n\npublic class LambdaOutput\n{\n    public string Something { get; set; }\n}\n\npublic LambdaTaskModel\u003cLambdaInput, LambdaOutput\u003e LambdaTask { get; set; }\n\n\n_builder.AddTask(\n    wf =\u003e wf.LambdaTask,\n    wf =\u003e new LambdaInput { Value = wf.WorkflowInput.Input },\n    script: \"return { something: $.Value.toUpperCase() }\"  // JavaScript expression\n);\n```\n\nFor context, in the above parameterized generic class `LambdaTaskModel`, the `LambdaOutput` instance is available as `Output.Result.Something`. This is less than ideal, but is the current way of things. Reasoning can be seen in the implementation:\n\n```csharp\npublic abstract class LambdaOutputModel\u003cO\u003e\n{\n  public O Result { get; set; }\n}\n\npublic abstract class LambdaTaskModel\u003cI, O\u003e where I : IRequest\u003cO\u003e\n{\n  public I Input { get; set; }\n\n  public LambdaOutputModel\u003cO\u003e Output { get; set; }\n}\n```\n\n### Wait Task\n\n```csharp\npublic WaitTaskModel WaitTask { get; set; }\n\n_builder.AddTask(\n    wf =\u003e wf.WaitTask,\n    wf =\u003e new WaitTaskInput { Duration = \"1h\" }  // or Until = \"2024-01-01T00:00:00Z\"\n);\n```\n\n### Terminate Task\n\n```csharp\npublic TerminateTaskModel TerminateTask { get; set; }\n\n_builder.AddTask(\n    wf =\u003e wf.TerminateTask,\n    wf =\u003e new TerminateTaskInput\n    {\n        TerminationStatus = \"COMPLETED\",\n        WorkflowOutput = new { Result = \"Done\" }\n    }\n);\n```\n\n### Human Task\n\n```csharp\npublic HumanTaskModel\u003cHumanTaskOutput\u003e HumanTask { get; set; }\n\n_builder.AddTask(\n    wf =\u003e wf.HumanTask,\n    wf =\u003e new HumanTaskInput\u003cHumanTaskOutput\u003e { /* ... */ }\n);\n```\n\n### JSON JQ Transform Task\n\n```csharp\npublic JsonJqTransformTaskModel\u003cJqInput, JqOutput\u003e TransformTask { get; set; }\n\n_builder.AddTask(\n    wf =\u003e wf.TransformTask,\n    wf =\u003e new JqInput { QueryExpression = \".data | map(.name)\", Data = wf.WorkflowInput.Items }\n);\n```\n\n### PassThrough Task (Raw Definition)\n\nFor tasks not covered by the builder:\n\n```csharp\n_builder.AddTasks(new WorkflowTask\n{\n    Name = \"CUSTOM_task\",\n    TaskReferenceName = \"custom_ref\",\n    Type = \"CUSTOM\",\n    InputParameters = new Dictionary\u003cstring, object\u003e { [\"key\"] = \"value\" }\n});\n```\n\n### Optional Tasks\n\nMark tasks as optional (workflow continues on failure):\n\n```csharp\n_builder.AddTask(wf =\u003e wf.OptionalTask, wf =\u003e new OptionalTaskRequest { }).AsOptional();\n```\n\n## Configuration\n\n### Execution Manager\n\n```csharp\nservices\n    .AddConductorSharp(baseUrl: \"http://localhost:8080\")\n    .AddExecutionManager(\n        maxConcurrentWorkers: 10,    // Max concurrent task executions\n        sleepInterval: 500,          // Base polling interval (ms)\n        longPollInterval: 100,       // Long poll timeout (ms)\n        domain: \"my-domain\",         // Optional worker domain\n        typeof(Program).Assembly     // Assemblies containing handlers\n    );\n```\n\n### Multiple Conductor Instances\n\n```csharp\nservices\n    .AddConductorSharp(baseUrl: \"http://primary-conductor:8080\")\n    .AddAlternateClient(\n        baseUrl: \"http://secondary-conductor:8080\",\n        key: \"Secondary\",\n        apiPath: \"api\",\n        ignoreInvalidCertificate: false\n    );\n\n// Usage with keyed services\npublic class MyController(\n    IWorkflowService primaryService,\n    [FromKeyedServices(\"Secondary\")] IWorkflowService secondaryService\n) { }\n```\n\n### Poll Timing Strategies\n\n```csharp\n// Default: Inverse exponential backoff\n.AddExecutionManager(...)\n\n// Constant interval polling\n.AddExecutionManager(...)\n.UseConstantPollTimingStrategy()\n```\n\n### Worker Task Registration\n\nRegister standalone tasks without workflow:\n\n```csharp\nservices.RegisterWorkerTask\u003cMyTaskHandler\u003e(options =\u003e\n{\n    options.OwnerEmail = \"team@example.com\";\n    options.Description = \"My task description\";\n});\n```\n\n## Pipeline Behaviors\n\nBehaviors form a middleware pipeline for task execution (powered by MediatR):\n\n```csharp\n.AddPipelines(pipelines =\u003e\n{\n    // Add custom behavior (runs first)\n    pipelines.AddCustomBehavior(typeof(MyCustomBehavior\u003c,\u003e));\n    \n    // Built-in behaviors\n    pipelines.AddExecutionTaskTracking();  // Track task execution metrics\n    pipelines.AddContextLogging();         // Add context to log scopes\n    pipelines.AddRequestResponseLogging(); // Log requests/responses\n    pipelines.AddValidation();             // Validate using DataAnnotations\n})\n```\n\n### Custom Behavior Example\n\n```csharp\npublic class TimingBehavior\u003cTRequest, TResponse\u003e : IPipelineBehavior\u003cTRequest, TResponse\u003e\n{\n    public async Task\u003cTResponse\u003e Handle(\n        TRequest request, \n        RequestHandlerDelegate\u003cTResponse\u003e next, \n        CancellationToken cancellationToken)\n    {\n        var sw = Stopwatch.StartNew();\n        var response = await next();\n        Console.WriteLine($\"Execution took {sw.ElapsedMilliseconds}ms\");\n        return response;\n    }\n}\n```\n\n## Health Checks\n\n### ASP.NET Core Integration\n\n```csharp\n// In Program.cs\nbuilder.Services.AddHealthChecks()\n    .AddCheck\u003cConductorSharpHealthCheck\u003e(\"conductor-worker\");\n\n// Configure health service\n.AddExecutionManager(...)\n.SetHealthCheckService\u003cFileHealthService\u003e()  // or InMemoryHealthService\n```\n\n### Available Health Services\n\n| Service | Description |\n|---------|-------------|\n| `InMemoryHealthService` | In-memory health state (default) |\n| `FileHealthService` | Persists health to `CONDUCTORSHARP_HEALTH.json` file |\n\n### Execution Context\n\nAccess workflow/task metadata in handlers:\n\n```csharp\npublic class MyHandler : TaskRequestHandler\u003cMyRequest, MyResponse\u003e\n{\n    private readonly ConductorSharpExecutionContext _context;\n\n    public MyHandler(ConductorSharpExecutionContext context)\n    {\n        _context = context;\n    }\n\n    public override async Task\u003cMyResponse\u003e Handle(MyRequest request, CancellationToken cancellationToken)\n    {\n        var workflowId = _context.WorkflowId;\n        var taskId = _context.TaskId;\n        var correlationId = _context.CorrelationId;\n        // ...\n    }\n}\n```\n\n## Patterns Package\n\nAdditional built-in tasks and utilities:\n\n```csharp\n.AddExecutionManager(...)\n.AddConductorSharpPatterns()      // Adds WaitSeconds, ReadWorkflowTasks\n.AddCSharpLambdaTasks()           // Adds C# lambda task support\n```\n\n### WaitSeconds Task\n\n```csharp\npublic WaitSeconds WaitTask { get; set; }\n\n_builder.AddTask(wf =\u003e wf.WaitTask, wf =\u003e new WaitSecondsRequest { Seconds = 30 });\n```\n\n### ReadWorkflowTasks Task\n\nRead task data from another workflow:\n\n```csharp\npublic ReadWorkflowTasks ReadTasks { get; set; }\n\n_builder.AddTask(\n    wf =\u003e wf.ReadTasks,\n    wf =\u003e new ReadWorkflowTasksInput \n    { \n        WorkflowId = wf.WorkflowInput.TargetWorkflowId,\n        TaskNames = \"task1,task2\"  // Comma-separated reference names\n    }\n);\n```\n\n### C# Lambda Tasks\n\nExecute C# code inline in workflows:\n\n```csharp\npublic CSharpLambdaTaskModel\u003cLambdaInput, LambdaOutput\u003e InlineLambda { get; set; }\n\n_builder.AddTask(\n    wf =\u003e wf.InlineLambda,\n    wf =\u003e new LambdaInput { Value = wf.WorkflowInput.Input },\n    input =\u003e new LambdaOutput { Result = input.Value.ToUpperInvariant() }\n);\n```\n\n### Signal Wait\n\nThe Signal Wait pattern allows workflows to pause execution until an external signal is received, without consuming worker threads. Useful for human approvals, external callbacks, or long-running async operations.\n\n#### Setup\n\n```csharp\n.AddExecutionManager(...)\n.AddSignalWait\u003cYourSignalStore\u003e(\"MY_PREFIX\")\n```\n\n**Important**: It is recommended to implement your own `ISignalStore` with a persistent backend (database, Redis, etc.). The built-in `InMemorySignalStore` is provided for development and testing only - it uses a static dictionary that only works within a single process and loses all data on restart.\n\n**Important**: The purpose of 'MY_PREFIX' is to allow multiple microservices running on the same Conductor instance to have different signal stores and not accidentally poll eachothers tasks.\n\n#### Using SignalWait in a Workflow\n\n```csharp\npublic class OrderWorkflow : Workflow\u003cOrderWorkflow, OrderInput, OrderOutput\u003e\n{\n    public SignalWait WaitForPayment { get; set; }\n    \n    public override void BuildDefinition()\n    {\n        // ... previous tasks ...\n        \n        _builder.AddTask(\n            wf =\u003e wf.WaitForPayment,\n            wf =\u003e new SignalWaitInput { SignalKey = $\"payment:{wf.WorkflowInput.OrderId}\" }\n        );\n        \n        // ... tasks after signal received ...\n    }\n}\n```\n\n#### Sending Signals\n\n```csharp\npublic class PaymentController : ControllerBase\n{\n    private readonly ISignalService _signalService;\n    \n    [HttpPost(\"webhook\")]\n    public async Task\u003cIActionResult\u003e PaymentReceived(PaymentNotification notification)\n    {\n        await _signalService.SendSignalAsync(\n            $\"payment:{notification.OrderId}\",\n            TaskResultStatus.COMPLETED,\n            new Dictionary\u003cstring, object\u003e { [\"transactionId\"] = notification.TransactionId }\n        );\n        return Ok();\n    }\n}\n```\n\n#### Monitoring Pending Signals\n\n```csharp\nvar pending = await _signalStore.GetPendingWaitersAsync();\n```\n\n#### Architecture\n\nThe signal system consists of:\n\n| Component | Description |\n|-----------|-------------|\n| `SignalWait` | Sub-workflow that registers a waiter and enters a WAIT task |\n| `RegisterWaiter` | Task that registers the workflow in the signal store |\n| `ISignalStore` | Persistence abstraction - implement with your own backend |\n| `ISignalService` | Service for sending signals to waiting workflows |\n| `SignalSweeperService` | Background service that reconciles signals with WAIT tasks |\n| `InMemorySignalStore` | Development-only in-memory implementation |\n\nSignals and waiters can arrive in any order - if a signal arrives before the workflow registers, the workflow will see it immediately and skip waiting.\n\n#### Task Configuration\n\nThe `RegisterWaiter` task is registered with specific settings:\n- **ConcurrentExecLimit = 1**: Only one registration can execute at a time per worker, preventing race conditions when multiple workflows register simultaneously\n- **RetryCount = 10** with **RetryDelaySeconds = 1**: Provides resilience against transient failures (e.g., database connection issues)\n\nThese settings mean that if many workflows start waiting at the same time, registrations will be serialized, which may introduce slight delays but ensures consistency in the signal store.\n\n## Kafka Cancellation Notifier\n\nHandle task cancellation via Kafka events:\n\n```csharp\n.AddExecutionManager(...)\n.AddKafkaCancellationNotifier(\n    kafkaBootstrapServers: \"localhost:9092\",\n    topicName: \"conductor.status.task\",\n    groupId: \"my-worker-group\",\n    createTopicOnStartup: true\n)\n```\n\n**appsettings.json:**\n```json\n{\n  \"Conductor\": {\n    \"BaseUrl\": \"http://localhost:8080\",\n    \"MaxConcurrentWorkers\": 10,\n    \"SleepInterval\": 500,\n    \"LongPollInterval\": 100,\n    \"KafkaCancellationNotifier\": {\n      \"BootstrapServers\": \"localhost:9092\",\n      \"GroupId\": \"my-worker\",\n      \"TopicName\": \"conductor.status.task\"\n    }\n  }\n}\n```\n\n## Toolkit CLI\n\nGenerate C# models from existing Conductor task/workflow definitions.\n\n### Installation\n\n```bash\ndotnet tool install --global ConductorSharp.Toolkit --version 4.0.0\n```\n\n### Configuration\n\nCreate `conductorsharp.yaml`:\n\n```yaml\nbaseUrl: http://localhost:8080\napiPath: api\nnamespace: MyApp.Generated\ndestination: ./Generated\n```\n\n### Usage\n\n```bash\n# Scaffold all tasks and workflows\ndotnet-conductorsharp\n\n# Use custom config file\ndotnet-conductorsharp -f myconfig.yaml\n\n# Filter by name\ndotnet-conductorsharp -n CUSTOMER_get -n ORDER_create\n\n# Filter by owner email\ndotnet-conductorsharp -e team@example.com\n\n# Filter by owner app\ndotnet-conductorsharp -a my-application\n\n# Skip tasks or workflows\ndotnet-conductorsharp --no-tasks\ndotnet-conductorsharp --no-workflows\n\n# Preview without generating files\ndotnet-conductorsharp --dry-run\n```\n\n### Command Options\n\n| Option | Description |\n|--------|-------------|\n| `-f, --file` | Configuration file path (default: `conductorsharp.yaml`) |\n| `-n, --name` | Filter by task/workflow name (can specify multiple) |\n| `-a, --app` | Filter by owner app |\n| `-e, --email` | Filter by owner email |\n| `--no-tasks` | Skip task scaffolding |\n| `--no-workflows` | Skip workflow scaffolding |\n| `--dry-run` | Preview what would be generated |\n\n## API Services\n\nInject these services to interact with Conductor programmatically:\n\n| Service | Description |\n|---------|-------------|\n| `IWorkflowService` | Start, pause, resume, terminate workflows |\n| `ITaskService` | Update tasks, get logs, poll for tasks |\n| `IMetadataService` | Manage workflow/task definitions |\n| `IAdminService` | Admin operations, queue management |\n| `IEventService` | Event handlers |\n| `IQueueAdminService` | Queue administration |\n| `IWorkflowBulkService` | Bulk workflow operations |\n| `IHealthService` | Conductor server health |\n| `IExternalPayloadService` | External payload storage |\n\n### Example Usage\n\n```csharp\npublic class WorkflowController : ControllerBase\n{\n    private readonly IWorkflowService _workflowService;\n    private readonly IMetadataService _metadataService;\n\n    public WorkflowController(IWorkflowService workflowService, IMetadataService metadataService)\n    {\n        _workflowService = workflowService;\n        _metadataService = metadataService;\n    }\n\n    [HttpPost(\"start\")]\n    public async Task\u003cstring\u003e StartWorkflow([FromBody] StartRequest request)\n    {\n        return await _workflowService.StartAsync(new StartWorkflowRequest\n        {\n            Name = \"MY_workflow\",\n            Version = 1,\n            Input = new Dictionary\u003cstring, object\u003e { [\"customerId\"] = request.CustomerId }\n        });\n    }\n\n    [HttpGet(\"definitions\")]\n    public async Task\u003cICollection\u003cWorkflowDef\u003e\u003e GetDefinitions()\n    {\n        return await _metadataService.ListWorkflowsAsync();\n    }\n}\n```\n\n## Running the Examples\n\n### Prerequisites\n\n1. Clone and run Conductor:\n   ```bash\n   git clone https://github.com/conductor-oss/conductor.git\n   cd conductor\n   docker-compose up -d\n   ```\n\n2. Conductor UI available at: http://localhost:5000 (may vary by version)\n\n### Starting the Examples\n\nThe solution includes three example projects:\n\n| Project | Description |\n|---------|-------------|\n| `ConductorSharp.Definitions` | Console app with workflow definitions |\n| `ConductorSharp.ApiEnabled` | Web API with workflow execution endpoints |\n| `ConductorSharp.NoApi` | Console app with Kafka cancellation support |\n\n```bash\n# Run with Docker Compose\ndocker-compose up\n\n# Or run individual projects\ncd examples/ConductorSharp.Definitions\ndotnet run\n```\n\n## General Notes\n\n### Events Not Supported\n\nThe Conductor events are currently not supported by the library.\n\n## License\n\nMIT License - see [LICENSE](LICENSE) for details.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodaxy%2Fconductor-sharp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodaxy%2Fconductor-sharp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodaxy%2Fconductor-sharp/lists"}