Skip to content

AI Ergonomics: Ambiguous WIO imports due to WorkflowContext extension can confuse code generation models #182

Description

@lmlynik

Issue Description

This is an ergonomic feedback issue concerning how Large Language Models (LLMs) and AI code assistants interact with the workflows4s library structure. The current design, while convenient for human developers, creates an ambiguity that AI models frequently get wrong, leading to non-compiling code that requires manual correction.

The core of the issue is that WIO can be brought into scope in two common ways:

  1. Direct Import: import workflows4s.wio.WIO
  2. Context Import: By defining an object that extends WorkflowContext and then importing its members, e.g., import MyWorkflowContext.*

Because WorkflowContext provides access to the WIO companion object's methods (pure, runIO, etc.), a wildcard import from a context object makes the direct import of WIO redundant and ambiguous.

The Problem for AI Models

AI code generation models, like GitHub Copilot, operate on statistical patterns found in vast codebases. They learn that to use a symbol Foo, it's common practice to add import com.example.Foo.

When generating a workflows4s workflow, the AI correctly identifies the need for State and Event types from a custom WorkflowContext, and adds the import:
import MyWorkflowContext.*

However, it also recognizes the usage of WIO.pure, WIO.fork, etc., and from its general training data, it often adds a "helpful" but problematic direct import:
import workflows4s.wio.WIO

Example of AI-Generated Code Leading to Error

An AI assistant will frequently generate code like this:

import workflows4s.wio.WIO // <-- AI adds this based on general patterns
import com.example.myproject.MyWorkflowContext.* // <-- This import also brings WIO members into scope

// Compiler Error: reference to WIO is ambiguous;
// it is imported twice in the same scope by
// import workflows4s.wio.WIO
// and import com.example.myproject.MyWorkflowContext.*

class MyWorkflow {
  
  val step1 = WIO.pure // <-- Ambiguous reference
    .make[MyWorkflowContext.State]
    .value { state =>
      // ...
    }
}

This forces the developer to manually diagnose the import conflict and remove the redundant import workflows4s.wio.WIO. While a minor issue, it adds friction to the development process and is a consistent "mistake" that AI models will likely continue to make due to the statistical nature of their training.

Discussion: Rethinking the Pattern for AI Comprehensibility

This issue isn't a bug, but rather an opportunity to consider if the library's design could be more "statistically comprehensible" to avoid these common AI pitfalls. A pattern that establishes a single, unambiguous way to access core DSL components would be more robust.

Some potential ideas for discussion:

  1. Decouple WIO from WorkflowContext: Could WorkflowContext be limited to just the Event and State type members? This would force all users (human and AI) to always import workflows4s.wio.WIO separately. The pattern would be less convenient but completely unambiguous.

  2. Introduce a DSL Entrypoint: Instead of having WIO members available at the top level, WorkflowContext could provide a dsl or workflow object.

    • Example:
      import MyWorkflowContext.*
      
      class MyWorkflow {
        val step1 = dsl.pure.make[State]... // Or `workflow.pure`
      }

    This would make the source of the DSL explicit (dsl) while keeping the context import for types. An AI would be less likely to add a conflicting import workflows4s.wio.WIO because the code doesn't use WIO directly.

This is a nitpick, but as AI-assisted development becomes the norm, designing APIs that are not just human-friendly but also "AI-friendly" could significantly improve developer experience. Rethinking this pattern might lead to a more robust and less error-prone interaction with modern tooling.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions