Skip to content

.NET: Latest release changes WorkflowBuilder API #1305

Closed

Description

I think an unintentional change might have crept in, and if not the docs will need to be updated.

Previously, I could do e.g.

let workflow =
    WorkflowBuilder(agent1)
        .AddEdge(agent1, agent2)
        .WithOutputFrom(agent2)
        .Build();

Now I have to pass an ExecutorIsh like so:

let workflow =
    WorkflowBuilder(ExecutorIsh agent1)
        .AddEdge(ExecutorIsh agent1, ExecutorIsh agent2)
        .WithOutputFrom(ExecutorIsh agent2)
        .Build();

Side note, I think the name ExecutorIsh is really not great (specifically the 'ish' part - either it is or isn't and needs a different name).

Activity

  1. stephentoub commented on Oct 8, 2025

    @stephentoub
    Member

    Side note, I think the name ExecutorIsh is really not great (specifically the 'ish' part - either it is or isn't and needs a different name).

    100%
    #758

  2. changed the title [-]Latest release changes WorkflowBuilder API[/-] [+].NET: Latest release changes WorkflowBuilder API[/+] on Oct 8, 2025
  3. Ryan-Palmer commented on Oct 8, 2025

    @Ryan-Palmer
    Author

    100%

    Ah I guess the implicit casting you mentioned has been removed (yay!) but the ExecutorIsh remains (boo!)

  4. lokitoth commented on Oct 13, 2025

    @lokitoth
    Contributor

    There should not be any changes here. Do you have a code sample that used to work and doesn't now? - and were there other changes you made?

    ExecutorIsh was the top-level input since the very beginning, and this has not changed, so I am unsure what is causing you to need to explicitly cast to it now?

    With that said: ExecutorIsh is likely to undergo further changes, but in principle, being able to pass in Executors, Agents, etc. directly to the builder APIs should remain.

  5. Ryan-Palmer commented on Oct 14, 2025

    @Ryan-Palmer
    Author
  6. teknikal-wizard commented on Oct 14, 2025

    @teknikal-wizard

    (Ryan from another account)
    Looks like F# won't play with implicit operators as I suspected:
    https://stackoverflow.com/questions/10719770/is-there-anyway-to-use-c-sharp-implicit-operators-from-f

    public static implicit operator ExecutorIsh(AIAgent aiAgent) => new(aiAgent);

    Given that this is a .NET library rather than a C# app, I would suggest not leaning on C# only features if at all possible.

    I don't know how it worked before, perhaps there were overloaded methods?

  7. lokitoth commented on Oct 20, 2025

    @lokitoth
    Contributor

    I pasted a sample in the original issue, I don't know what more to say

    I was just trying to warp my head around what could have changed, since the only input type that the WorkflowBuilder APIs ever took was ExecutorIsh - there was never a point at which it took Agent directly. It is odd that it used to work with the implicit cast and now fails.

    Still, will need to think about how to make this work nicely from F#. You may never have as "nice" as interface as C# unless I add some special-casing.

    Do you know if there is a way for me to add special overloads that are F# specific that present the API as a type union?

  8. stephentoub commented on Oct 20, 2025

    @stephentoub
    Member

    I suggest the WorkflowBuilder APIs should just be in terms of Executor, and anything else (AIAgent, etc.) should be explicitly converted to an Executor, either via a ToXx extension method, or overloads of an Executor.Create method with an overload for each type, or some such thing. I don't think cuteness with implicit operators is a good way to go, even if it makes the code a little more terse.

  9. Ryan-Palmer commented on Oct 20, 2025

    @Ryan-Palmer
    Author

    Perhaps rather than have an option (implicit casting) that only works in C# and another (unions) that only works in F#, a simple solution that works for both would be constructor overloads for either Executor or Agent, then the framework sorts it out internally?

  10. lokitoth commented on Oct 21, 2025

    @lokitoth
    Contributor

    The issue with that is that we need to be able to have collections of any of the following (somewhat) unrelated types and would prefer not to force everyone to wrap it in a wrapper (kind of like you had to wrap in "ExecutorIsh"). That's what I am trying to do: Get back to being able to pass both AIAgent, and Executor instances, as well as configured factories and things like RequestPort and Workflow directly.

    E.g., the following should still work:

    Executor executor;
    AIAgent agent;
    Workflow workflow;
    RequestPort requestPort;
    ExecutorFactoryFunc factory;
    WorkflowBuilder builder;
    
    builder.AddFanOut(source, [agent, workflow, requestPort, factory])

    My goal is to make the simple case simple. I will add as many special-casings, even up to language-specific ones, to make that work.

    My question is: What will be necessary to make it work nicely in F#. I know how to make it nice in C#.

    Beyond making the consumption of the APIs nice in the simple case for developers, I genuinely do not have a strong opinion as to how it should be done: Happy to include all of the above.

  11. Ryan-Palmer commented on Oct 21, 2025

    @Ryan-Palmer
    Author

    Given you want to mix them in a list, then overloads aren't a way forward.

    I'd really recommend against trying to make F# and C# specific APIs.

    I agree with Stephen Toub (@stephentoub) - just add AsExecutor() methods to any classes you want to be able to use in an Executor context?

    There is already a precedent for this in the functional API that I only discovered through this issue (which I much prefer and think should be promoted over the verbose classes, but I am an F# guy haha) : #1505

    Another option would be to make them all implement an IExecutor interface?

  12. lokitoth commented on Oct 21, 2025

    @lokitoth
    Contributor

    just add AsExecutor() methods to any classes you want to be able to use in an Executor context?

    That would add to the boilerplate / wordiness that is already raised as an isue in #1505. I genuinely would prefer to special-case it per language to avoid having 30% of the workflow construction just be ToExecutor() calls

  13. Ryan-Palmer commented on Oct 21, 2025

    @Ryan-Palmer
    Author

    That's fair, but a language specific API would be confusing and I think is generally frowned upon for a general .NET library.

    Don Syme (@dsyme) Isaac Abraham (@isaacabraham) perhaps you have an opinion here?

    How about an IExecutor interface? That would have been my first thought if I needed to mix them in a collection.

  14. lokitoth commented on Oct 21, 2025

    @lokitoth
    Contributor

    AIAgent is in an assembly that Workflows depends on, so we would need to upstream ICanBeExecutor... and it would just be a "tag" interface, which is not ideal, or the abstractions library will need to pick up Workflow concepts which has its own drawbacks.

    Going to experiment with this a little - see if I can merge the concepts of ExecutorRegistration and ExecutorIsh and at least implement the following:

    1. A ToExecutor() (or RegisterExecutor()) extension method on all of the relevant types to create the appropriate ExecutorRegistration
    2. The implicit casts for C#

    After we can see if there is syntactic sugar we can add to the F# side to get back to "no cast, no ToExecutor" like it was before. I am confused how it ever worked, honestly.

  15. stephentoub commented on Oct 21, 2025

    @stephentoub
    Member

    (FWIW, I'm still not a fan of the implicit casts. I get that it removes a small amount of ceremony from the code, but that's a double-edged sword. It makes the code harder to understand because of these magic conversions happening between unrelated types, there are hidden performance implications, reference equality goes out the window which is very unexpected when it just looks like an assignment, etc.)

  16. markwallace-microsoft commented on Mar 20, 2026

    @markwallace-microsoft
    Contributor

    Ryan Palmer (@Ryan-Palmer), friendly reminder — this issue is waiting on your response. Please share any updates when you get a chance. (This is an automated message.)

  17. alliscode commented on Jun 2, 2026

    @alliscode
    Member

    Resolved by #1637

  18. added
    questionUsage: [Issues, PRs], Target: all issues or PRs where additional context is needed from the author
    and removed
    questionUsage: [Issues, PRs], Target: all issues or PRs where additional context is needed from the author
    on Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

.NETUsage: [Issues, PRs], Target: .NetworkflowsUsage: [Issues, PRs], Target: Workflows

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions