-
Notifications
You must be signed in to change notification settings - Fork 6.9k
Open
Labels
enhancementNew feature or requestNew feature or requesttriageIssue needs to be triaged/prioritizedIssue needs to be triaged/prioritized
Description
Feature Description
Workflows currently support DI via inline Resource(factory=...) in step signatures:
class MyWorkflow(Workflow):
@step
async def first(
self,
ev: StartEvent,
memory: Annotated[Memory, Resource(factory=get_memory)],
) -> StopEvent:
...This couples step definitions to resource construction logic.
Proposal: Adopt a typed dependency context pattern (similar to PydanticAI's deps), where each step receives a single deps parameter instead of individually-annotated resources.
1. Typed dependency container:
@dataclass
class MyDeps:
memory: Memory
llm: LLM
customer_id: int2. Steps receive deps — no factory knowledge:
class MyWorkflow(Workflow[MyDeps]):
@step
async def first(self, ev: StartEvent, deps: WorkflowContext[MyDeps]) -> StopEvent:
response = await deps.llm.achat(...)
deps.memory.put(response)
return StopEvent(result=response)3. Wiring at the call site (Composition Root):
workflow = MyWorkflow(deps=MyDeps(
memory=ChatMemoryBuffer.from_defaults(token_limit=3000),
llm=OpenAI(model="gpt-4o"),
customer_id=123,
))
# Testing — zero changes to workflow code
workflow = MyWorkflow(deps=MyDeps(memory=MockMemory(), llm=MockLLM(), customer_id=999))Optional: explicit lifecycle scope
workflow = MyWorkflow(deps=MyDeps(...), deps_scope=DepsScope.RUN) # RUN | STEP | SINGLETONReason
The current Resource(factory=...) annotation pattern introduces several issues:
| Concern | Impact |
|---|---|
| Coupling | Steps know how dependencies are created, not just what they need |
| Separation of Concerns | Orchestration mixed with wiring/configuration |
| Environment switching | Swapping InMemoryStore → RedisStore requires editing workflow source code |
| Testing | Mocking means patching factories inside annotations rather than passing different instances |
| Lifecycle | Unclear whether resource is per-step, per-run, or singleton |
If this pattern is already achievable today, I'd appreciate a pointer to the relevant docs or examples.
Value of Feature
| Aspect | Per-param Resource(factory=...) |
Typed deps context |
|---|---|---|
| Adding a dependency | Modify signature + factory | Add field to dataclass |
| Type safety / discoverability | Scattered across annotations | Single container, IDE-friendly |
| Testing | Patch individual factories | Pass different deps instance |
| Runtime context (user ID, etc.) | Awkward via Resource | Natural field on deps |
Key benefits:
- Clean separation: steps declare what, composition root decides how
- Testability: pass mocks at construction without patching internals
- Environment flexibility: swap implementations via config, not code changes
- Explicit lifecycle: caller controls resource scope
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or requesttriageIssue needs to be triaged/prioritizedIssue needs to be triaged/prioritized