Skip to content

Commit 0b0aeee

Browse files
authored
Merge pull request #4 from wavezync/feat/call-workflows
feat: workflow orchestration (call_workflow / start_workflow)
2 parents 90587e6 + 3dfd1aa commit 0b0aeee

17 files changed

Lines changed: 2607 additions & 778 deletions

README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ A durable, resumable workflow engine for Elixir. Similar to Temporal/Inngest.
1414
- **Compensations** - Saga pattern with automatic rollback
1515
- **Cron Scheduling** - Recurring workflows with cron expressions
1616
- **Reliability** - Automatic retries with exponential/linear/constant backoff
17+
- **Orchestration** - Parent/child workflow composition
1718
- **Persistence** - PostgreSQL-backed execution state
1819

1920
## Installation
@@ -439,6 +440,38 @@ hours(2) # 7_200_000 ms
439440
days(7) # 604_800_000 ms
440441
```
441442

443+
### Orchestration
444+
445+
```elixir
446+
use Durable.Orchestration
447+
448+
# Synchronous: call child and wait for result
449+
case call_workflow(MyApp.PaymentWorkflow, %{"amount" => 100}, timeout: hours(1)) do
450+
{:ok, result} -> {:ok, assign(data, :payment, result)}
451+
{:error, reason} -> {:error, reason}
452+
end
453+
454+
# Fire-and-forget: start child and continue
455+
{:ok, child_id} = start_workflow(MyApp.EmailWorkflow, %{"to" => email}, ref: :welcome)
456+
457+
# call_workflow also works inside parallel blocks (executed inline)
458+
parallel do
459+
step :payment, fn data ->
460+
case call_workflow(MyApp.PaymentWorkflow, %{"amount" => data.total}, ref: :pay) do
461+
{:ok, result} -> {:ok, assign(data, :payment, result)}
462+
{:error, reason} -> {:error, reason}
463+
end
464+
end
465+
466+
step :shipping, fn data ->
467+
case call_workflow(MyApp.ShippingWorkflow, %{"id" => data.order_id}, ref: :ship) do
468+
{:ok, result} -> {:ok, assign(data, :shipping, result)}
469+
{:error, reason} -> {:error, reason}
470+
end
471+
end
472+
end
473+
```
474+
442475
### API
443476

444477
```elixir
@@ -449,6 +482,7 @@ Durable.list_executions(workflow: Module, status: :running)
449482
Durable.cancel(id, "reason")
450483
Durable.send_event(id, "event", payload)
451484
Durable.provide_input(id, "input_name", data)
485+
Durable.list_children(parent_id)
452486
```
453487

454488
## Guides
@@ -457,10 +491,10 @@ Durable.provide_input(id, "input_name", data)
457491
- [Parallel](guides/parallel.md) - Concurrent execution
458492
- [Compensations](guides/compensations.md) - Saga pattern
459493
- [Waiting](guides/waiting.md) - Sleep, events, human input
494+
- [Orchestration](guides/orchestration.md) - Parent/child workflow composition
460495

461496
## Coming Soon
462497

463-
- Workflow orchestration (parent/child workflows)
464498
- Phoenix LiveView dashboard
465499

466500
## License
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# DurableWorkflow Implementation Plan
22

3+
> **⚠️ ARCHIVED (2026-01-23):** This document is no longer maintained.
4+
> See `WORKPLAN.md` for current status and `arch.md` for technical reference.
5+
>
6+
> Key changes since this document was written:
7+
> - ForEach primitive was **removed** (use `Enum.map` instead)
8+
> - Parallel execution uses new results model (`__results__`, `into:`, `returns:`)
9+
> - Loop primitive was **never implemented** (use step retries or `Enum` functions)
10+
311
## Executive Summary
412

513
This document outlines the complete implementation plan for **Durable**, a durable, resumable workflow engine for Elixir.

agents/WORKPLAN.md

Lines changed: 102 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
# Durable Workflow Engine - Work Plan
22

3-
**Last Updated:** 2026-01-03
3+
**Last Updated:** 2026-03-08
44
**Overall Progress:** ~75% Complete
5-
**Reference:** See `IMPLEMENTATION_PLAN.md` for detailed design and code examples
5+
**Reference:** See `arch.md` for technical architecture
66

77
---
88

99
## Quick Stats
1010

1111
| Metric | Value |
1212
|--------|-------|
13-
| Source Modules | 41 |
14-
| Passing Tests | 214 |
13+
| Source Modules | 42 |
14+
| Passing Tests | ~291 |
1515
| Documentation Guides | 6 |
16-
| Lines of Code | ~8,500 |
16+
| Lines of Code | ~11,000 |
1717

1818
---
1919

@@ -24,8 +24,8 @@
2424
| 0 | Project Foundation | Complete | 100% |
2525
| 1 | Core MVP | Complete | 100% |
2626
| 2 | Observability | Partial | 40% |
27-
| 3 | Advanced Features | Mostly Complete | 85% |
28-
| 4 | Scalability | Not Started | 0% |
27+
| 3 | Advanced Features | Mostly Complete | 90% |
28+
| 4 | Scalability | Partial | ~5% |
2929
| 5 | Developer Experience | Partial | 35% |
3030

3131
---
@@ -86,9 +86,9 @@
8686

8787
---
8888

89-
## Phase 3: Advanced Features [85%]
89+
## Phase 3: Advanced Features [90%]
9090

91-
### 3.1-3.3 Wait Primitives [COMPLETE - 46 tests]
91+
### 3.1-3.3 Wait Primitives [COMPLETE - 52 tests]
9292

9393
| Feature | Status |
9494
|---------|--------|
@@ -105,7 +105,7 @@
105105
| Timeout handling | Complete |
106106
| Context preservation | Complete |
107107

108-
### 3.4 Conditional Branching [COMPLETE - 10 tests]
108+
### 3.4 Conditional Branching [COMPLETE - 19 tests]
109109

110110
| Feature | Status |
111111
|---------|--------|
@@ -117,32 +117,33 @@
117117

118118
### 3.5 Loops [SKIPPED]
119119

120-
Intentionally skipped - use step-level retries or `foreach` instead.
120+
Intentionally skipped - use step-level retries or Elixir's `Enum` functions instead.
121121

122-
### 3.6 Parallel Execution [COMPLETE - 13 tests]
122+
### 3.6 Parallel Execution [COMPLETE - 20 tests]
123123

124124
| Feature | Status |
125125
|---------|--------|
126126
| `parallel do` macro | Complete |
127-
| Context merge strategies | Complete |
127+
| Results model (`__results__`) | Complete |
128+
| `into:` custom merge function | Complete |
129+
| `returns:` option | Complete |
128130
| Error strategies | Complete |
129131
| Resume durability | Complete |
130132

131-
### 3.7 ForEach [COMPLETE - 13 tests]
133+
See `guides/parallel.md` for comprehensive documentation.
132134

133-
| Feature | Status |
134-
|---------|--------|
135-
| `foreach` macro | Complete |
136-
| `current_item/0`, `current_index/0` | Complete |
137-
| Sequential/Concurrent modes | Complete |
138-
| `:collect_as` option | Complete |
139-
| `:on_error` handling | Complete |
135+
### 3.7 ForEach [REMOVED]
136+
137+
**Decision (2026-01-23):** The `foreach` primitive was removed. Users should use
138+
Elixir's built-in enumeration tools (`Enum.map`, `Task.async_stream`) for batch
139+
processing instead. This simplifies the DSL while providing the same functionality
140+
through idiomatic Elixir.
140141

141142
### 3.8 Switch/Case [NOT STARTED]
142143

143144
Low priority - `branch` macro covers most cases.
144145

145-
### 3.9 Compensation/Saga [COMPLETE - 6 tests]
146+
### 3.9 Compensation/Saga [COMPLETE - 10 tests]
146147

147148
| Feature | Status |
148149
|---------|--------|
@@ -151,7 +152,7 @@ Low priority - `branch` macro covers most cases.
151152
| Reverse-order execution | Complete |
152153
| CompensationRunner | Complete |
153154

154-
### 3.10 Cron Scheduling [COMPLETE - 45 tests]
155+
### 3.10 Cron Scheduling [COMPLETE - 49 tests]
155156

156157
| Feature | Status |
157158
|---------|--------|
@@ -165,22 +166,35 @@ Low priority - `branch` macro covers most cases.
165166
| Manual trigger | Complete |
166167
| Telemetry events | Complete |
167168

169+
### 3.11 Workflow Orchestration [COMPLETE - 12 tests]
170+
171+
| Feature | Status |
172+
|---------|--------|
173+
| `call_workflow/3` (synchronous) | Complete |
174+
| `start_workflow/3` (fire-and-forget) | Complete |
175+
| `call_workflow` in `parallel` blocks (inline execution) | Complete |
176+
| Idempotent resume | Complete |
177+
| Cascade cancellation | Complete |
178+
| Parent notification on child complete/fail | Complete |
179+
| Nested workflows (A → B → C) | Complete |
180+
| `Durable.list_children/2` API | Complete |
181+
182+
See `guides/orchestration.md` for comprehensive documentation.
183+
168184
### Remaining Phase 3 Work
169185

170186
| Feature | Priority | Complexity |
171187
|---------|----------|------------|
172-
| Workflow Orchestration (`call_workflow`) | High | Medium |
173-
| Parent-child tracking | High | Low |
174188
| Switch/Case macro | Low | Low |
175189
| Pipe-based API | Low | Medium |
176190

177191
---
178192

179-
## Phase 4: Scalability [0%]
193+
## Phase 4: Scalability [~5%]
180194

181195
| Feature | Priority | Complexity |
182196
|---------|----------|------------|
183-
| Queue Adapter Behaviour | Complete | - |
197+
| Queue Adapter Behaviour | **Complete** | - |
184198
| Redis Queue Adapter | Medium | Medium |
185199
| RabbitMQ Queue Adapter | Low | Medium |
186200
| Message Bus Behaviour | Medium | Low |
@@ -229,60 +243,91 @@ Note: Multi-node scheduling already works via `FOR UPDATE SKIP LOCKED`.
229243
### High Priority
230244

231245
1. Guide: Getting Started
232-
2. Workflow Orchestration (`call_workflow`)
233-
3. HexDocs Publishing
234-
4. `mix durable.status`
246+
2. HexDocs Publishing
247+
3. `mix durable.status`
235248

236249
### Medium Priority
237250

238-
5. Guide: Testing Workflows
239-
6. `Durable.TestCase`
240-
7. Graph Generation
241-
8. `mix durable.list`
242-
9. pg_notify Message Bus
251+
4. Guide: Testing Workflows
252+
5. `Durable.TestCase`
253+
6. Graph Generation
254+
7. `mix durable.list`
255+
8. pg_notify Message Bus
243256

244257
### Lower Priority
245258

246-
10. Switch/Case macro
247-
11. Redis Queue Adapter
248-
12. Phoenix Dashboard
249-
13. Example Project
250-
14. Pipe-based API
259+
9. Switch/Case macro
260+
10. Redis Queue Adapter
261+
11. Phoenix Dashboard
262+
12. Example Project
263+
13. Pipe-based API
251264

252265
---
253266

254267
## Test Coverage
255268

256269
| Test File | Tests | Area |
257270
|-----------|-------|------|
258-
| scheduler_test.exs | 45 | Cron scheduling |
259-
| wait_test.exs | 46 | Wait primitives |
260-
| decision_test.exs | 13 | Decision steps |
261-
| parallel_test.exs | 13 | Parallel execution |
262-
| foreach_test.exs | 13 | ForEach iteration |
271+
| wait_test.exs | 52 | Wait primitives |
272+
| scheduler_test.exs | 49 | Cron scheduling |
273+
| parallel_test.exs | 20 | Parallel execution |
274+
| branch_test.exs | 19 | Branch macro |
275+
| postgres_test.exs | 16 | Queue adapter |
276+
| decision_test.exs | 14 | Decision steps |
263277
| log_capture_test.exs | 13 | Log/IO capture |
278+
| orchestration_test.exs | 12 | Workflow orchestration |
264279
| integration_test.exs | 11 | End-to-end flows |
265-
| branch_test.exs | 10 | Branch macro |
266-
| durable_test.exs | 8 | Core API |
267-
| compensation_test.exs | 6 | Saga pattern |
268-
| Other | ~36 | Queue, handlers, etc. |
269-
| **Total** | **214** | |
280+
| validation_test.exs | 10 | Input validation |
281+
| context_test.exs | 10 | Context management |
282+
| compensation_test.exs | 10 | Saga pattern |
283+
| durable_test.exs | 10 | Core API |
284+
| handler_test.exs | 8 | Log handler |
285+
| io_server_test.exs | 7 | IO capture |
286+
| resume_edge_cases_test.exs | 5 | Resume edge cases |
287+
| log_capture/integration_test.exs | 5 | Log capture integration |
288+
| Other | ~20 | Misc |
289+
| **Total** | **~291** | |
270290

271291
---
272292

273293
## Known Limitations
274294

275-
1. Wait primitives not supported in parallel/foreach blocks
276-
2. No backward jumps in decision steps (forward-only by design)
277-
3. Context is single-level atomized (top-level keys only)
278-
4. No workflow versioning
295+
1. Wait primitives not supported in parallel blocks
296+
2. Child workflows with waits (`sleep`, `wait_for_event`) not supported in parallel blocks
297+
3. No backward jumps in decision steps (forward-only by design)
298+
4. Context is single-level atomized (top-level keys only)
299+
5. No workflow versioning
300+
6. No foreach/loop DSL primitives (use Elixir's `Enum` functions)
279301

280302
---
281303

282304
## Next Steps
283305

284306
1. **Documentation** - Getting Started guide and HexDocs publishing
285-
2. **Workflow Orchestration** - Child workflow support (`call_workflow`)
286-
3. **Graph Visualization** - Understanding complex workflows
307+
2. **Graph Visualization** - Understanding complex workflows
308+
3. **Testing Helpers** - `Durable.TestCase` for easier workflow testing
309+
310+
The existing ~291 tests provide good confidence in implemented features. Suitable for internal use; additional documentation needed before public release.
311+
312+
---
287313

288-
The existing 214 tests provide good confidence in implemented features. Suitable for internal use; additional documentation needed before public release.
314+
## Changelog
315+
316+
### 2026-02-27
317+
- Added `call_workflow` support inside `parallel` blocks (inline synchronous execution)
318+
- Child workflows in parallel execute synchronously with process state save/restore
319+
- 3 new tests for parallel call_workflow (total: ~291)
320+
- Updated guides/orchestration.md, guides/parallel.md, and README.md
321+
- Added workflow orchestration: `call_workflow/3` (synchronous) and `start_workflow/3` (fire-and-forget)
322+
- Added `Durable.Orchestration` module with `use Durable.Orchestration` macro
323+
- Added cascade cancellation (cancelling parent cancels active children)
324+
- Added parent notification on child completion/failure
325+
- Added `Durable.list_children/2` API
326+
- Added `guides/orchestration.md` documentation
327+
- 12 new tests for orchestration
328+
329+
### 2026-01-23
330+
- Removed `foreach` primitive (use `Enum.map` or `Task.async_stream` instead)
331+
- Updated parallel execution with new results model (`__results__`, `into:`, `returns:`)
332+
- Updated documentation in `guides/parallel.md`
333+
- Archived `IMPLEMENTATION_PLAN.md` (now `IMPLEMENTATION_PLAN_ARCHIVED.md`)

0 commit comments

Comments
 (0)