Skip to content

Commit e8d0788

Browse files
committed
Release version 0.2.2 with enhanced flow control features
1 parent e3a8c2b commit e8d0788

File tree

12 files changed

+582
-164
lines changed

12 files changed

+582
-164
lines changed

.formatter.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Used by "mix format"
22
[
3-
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
3+
inputs: ["{mix,.formatter}.exs", "{config,lib,test,examples}/**/*.{ex,exs}"]
44
]

CHANGELOG.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.2.2] - 2025-03-30
11+
### Added
12+
- Enhanced flow control with signal strategies:
13+
- Forward strategy (default) - passes signals unchanged to next handler
14+
- Transform strategy - modifies signals before passing to next handler
15+
- Restart strategy - allows handlers to restart the flow with a new signal
16+
- Added `continue_on_skip` option to allow flow continuation after a skip result
17+
- Improved function flow processing with more robust result normalization
18+
- New enhanced_flow_control.exs example demonstrating all flow control features
19+
- Added comprehensive flow control documentation in guides/flow_control.md
20+
21+
### Fixed
22+
- Resolved potential infinite loop issue in restart strategy with termination conditions
23+
- Fixed minor code formatting and style issues throughout the codebase
24+
1025
## [0.2.1] - 2025-03-27
1126
### Changed
1227
- Improved code quality and readability throughout the codebase
@@ -48,5 +63,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4863
- Tool registry for extensible command execution
4964
- Comprehensive documentation and examples
5065

51-
[Unreleased]: https://github.com/USERNAME/agent_forge/compare/v0.1.0...HEAD
66+
[Unreleased]: https://github.com/USERNAME/agent_forge/compare/v0.2.2...HEAD
67+
[0.2.2]: https://github.com/USERNAME/agent_forge/releases/tag/v0.2.2
5268
[0.1.0]: https://github.com/USERNAME/agent_forge/releases/tag/v0.1.0

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ graph TB
3232
# Add to mix.exs
3333
def deps do
3434
[
35-
{:agent_forge, "~> 0.2.1"}
35+
{:agent_forge, "~> 0.2.2"}
3636
]
3737
end
3838

3939
# Run
4040
mix deps.get
4141
```
4242

43-
> **Note**: AgentForge 0.2.1 introduces the plugin system with improved code quality! See the [Plugin System Guide](guides/plugin_system.md) for more details.
43+
> **Note**: AgentForge 0.2.2 introduces enhanced flow control features! See the [Flow Control Guide](guides/flow_control.md) for more details.
4444
4545
### Simple Example
4646

@@ -146,6 +146,9 @@ See the documentation for more details.
146146
- [Data Processing](examples/data_processing.exs): Basic data transformation pipeline
147147
- [Async Workflow](examples/async_workflow.exs): Handling async operations
148148
- [Configuration-based](examples/config_workflow.exs): YAML-defined workflows
149+
- [Flow Control](examples/enhanced_flow_control.exs): Advanced flow control features
150+
- [Limited Workflow](examples/limited_workflow.exs): Execution limits example
151+
- [Plugin System](examples/plugin_system.exs): Plugin system example
149152

150153
## Design Philosophy
151154

examples/async_workflow.exs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ defmodule AsyncWorkflow do
1111
def simulate_async_job(caller) do
1212
# Simulate a background job
1313
spawn(fn ->
14-
Process.sleep(100) # Very short sleep for example
14+
# Very short sleep for example
15+
Process.sleep(100)
1516
send(caller, {:job_complete, %{result: "Completed"}})
1617
end)
1718
end
@@ -30,7 +31,8 @@ defmodule AsyncWorkflow do
3031
message = "Job completed with result: #{inspect(result.result)}"
3132
{Signal.emit(:notification, message), state}
3233
after
33-
2000 -> # Longer timeout to ensure we catch the message
34+
# Longer timeout to ensure we catch the message
35+
2000 ->
3436
{Signal.emit(:error, "Job timed out"), state}
3537
end
3638
end

examples/config_workflow.exs

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,39 @@ defmodule ConfigWorkflow do
1010

1111
def validate_field(data, field, rules) do
1212
value = Map.get(data, String.to_atom(field))
13+
1314
cond do
1415
rules["required"] && is_nil(value) ->
1516
{:error, "#{field} is required"}
17+
1618
rules["type"] == "number" && not is_number(value) ->
1719
{:error, "#{field} must be a number"}
20+
1821
rules["min"] && value < rules["min"] ->
1922
{:error, "#{field} must be at least #{rules["min"]}"}
23+
2024
true ->
2125
{:ok, value}
2226
end
2327
end
2428

2529
def create_validation_transform(config) do
2630
fn signal, state ->
27-
result = Enum.reduce_while(config["validate"], {:ok, signal.data}, fn rule, {:ok, acc} ->
28-
case validate_field(acc, rule["field"], rule) do
29-
{:ok, _} ->
30-
{:cont, {:ok, acc}}
31-
{:error, reason} ->
32-
{:halt, {:error, reason}}
33-
end
34-
end)
31+
result =
32+
Enum.reduce_while(config["validate"], {:ok, signal.data}, fn rule, {:ok, acc} ->
33+
case validate_field(acc, rule["field"], rule) do
34+
{:ok, _} ->
35+
{:cont, {:ok, acc}}
36+
37+
{:error, reason} ->
38+
{:halt, {:error, reason}}
39+
end
40+
end)
3541

3642
case result do
3743
{:ok, data} ->
3844
{Signal.emit(:validated, data), state}
45+
3946
{:error, reason} ->
4047
{Signal.halt(reason), state}
4148
end
@@ -45,12 +52,15 @@ defmodule ConfigWorkflow do
4552
def create_enrichment_transform(config) do
4653
fn signal, state ->
4754
try do
48-
enriched_data = Enum.reduce(config["add_fields"], signal.data, fn
49-
%{"timestamp" => "now()"}, acc ->
50-
Map.put(acc, :timestamp, DateTime.utc_now())
51-
field, acc ->
52-
Map.merge(acc, field)
53-
end)
55+
enriched_data =
56+
Enum.reduce(config["add_fields"], signal.data, fn
57+
%{"timestamp" => "now()"}, acc ->
58+
Map.put(acc, :timestamp, DateTime.utc_now())
59+
60+
field, acc ->
61+
Map.merge(acc, field)
62+
end)
63+
5464
{Signal.emit(:enriched, enriched_data), state}
5565
rescue
5666
e in RuntimeError -> {Signal.emit(:error, e.message), state}
@@ -59,26 +69,30 @@ defmodule ConfigWorkflow do
5969
end
6070

6171
def create_branch(config, flows) do
62-
condition = case config["condition"] do
63-
"age >= 18" ->
64-
fn signal, _ -> Map.get(signal.data, :age) >= 18 end
65-
end
72+
condition =
73+
case config["condition"] do
74+
"age >= 18" ->
75+
fn signal, _ -> Map.get(signal.data, :age) >= 18 end
76+
end
6677

67-
then_flow = flows[config["then_flow"]]
68-
|> Enum.map(fn step -> create_handler(step, flows) end)
78+
then_flow =
79+
flows[config["then_flow"]]
80+
|> Enum.map(fn step -> create_handler(step, flows) end)
6981

70-
else_flow = flows[config["else_flow"]]
71-
|> Enum.map(fn step -> create_handler(step, flows) end)
82+
else_flow =
83+
flows[config["else_flow"]]
84+
|> Enum.map(fn step -> create_handler(step, flows) end)
7285

7386
Primitives.branch(condition, then_flow, else_flow)
7487
end
7588

7689
def create_notification(config) do
7790
fn signal, state ->
7891
try do
79-
message = config["message"]
80-
|> String.replace("{name}", to_string(Map.get(signal.data, :name)))
81-
|> String.replace("{age}", to_string(Map.get(signal.data, :age)))
92+
message =
93+
config["message"]
94+
|> String.replace("{name}", to_string(Map.get(signal.data, :name)))
95+
|> String.replace("{age}", to_string(Map.get(signal.data, :age)))
8296

8397
{Signal.emit(:notification, message), state}
8498
rescue
@@ -91,10 +105,13 @@ defmodule ConfigWorkflow do
91105
case {step["type"], step["name"]} do
92106
{"transform", "validate_input"} ->
93107
create_validation_transform(step["config"])
108+
94109
{"transform", "enrich_data"} ->
95110
create_enrichment_transform(step["config"])
111+
96112
{"branch", _} ->
97113
create_branch(step["config"], flows)
114+
98115
{"notify", _} ->
99116
create_notification(step["config"])
100117
end
@@ -180,7 +197,7 @@ defmodule ConfigWorkflow do
180197
IO.puts("\nUsing #{if File.exists?(yaml_path), do: "YAML configuration", else: "stub data"}")
181198

182199
# Create handlers from configuration
183-
handlers = Enum.map(workflow["steps"], & create_handler(&1, workflow["flows"]))
200+
handlers = Enum.map(workflow["steps"], &create_handler(&1, workflow["flows"]))
184201

185202
# Test data
186203
test_cases = [
@@ -200,6 +217,7 @@ defmodule ConfigWorkflow do
200217
case process_with_error_handling(handlers, signal, state) do
201218
{:ok, result} ->
202219
IO.puts("Success: #{inspect(result)}")
220+
203221
{:error, reason} ->
204222
IO.puts("Error: #{reason}")
205223
end
@@ -210,11 +228,15 @@ defmodule ConfigWorkflow do
210228
case Flow.process(handlers, signal, state) do
211229
{:ok, result, _} ->
212230
{:ok, result}
231+
213232
{:error, {:badmap, msg}} ->
214-
clean_msg = msg
215-
|> String.replace(~r/Transform error: expected a map got: "/, "")
216-
|> String.replace(~r/"$/, "")
233+
clean_msg =
234+
msg
235+
|> String.replace(~r/Transform error: expected a map got: "/, "")
236+
|> String.replace(~r/"$/, "")
237+
217238
{:error, clean_msg}
239+
218240
{:error, reason} ->
219241
{:error, format_error(reason)}
220242
end

examples/data_processing.exs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,36 @@ defmodule DataProcessing do
1010

1111
def process_orders do
1212
# Define data validation
13-
validate_order = Primitives.transform(fn order ->
14-
cond do
15-
is_nil(order.id) -> raise "Order ID is required"
16-
order.amount <= 0 -> raise "Invalid order amount"
17-
true -> order
18-
end
19-
end)
13+
validate_order =
14+
Primitives.transform(fn order ->
15+
cond do
16+
is_nil(order.id) -> raise "Order ID is required"
17+
order.amount <= 0 -> raise "Invalid order amount"
18+
true -> order
19+
end
20+
end)
2021

2122
# Transform orders to add tax
22-
add_tax = Primitives.transform(fn order ->
23-
tax = order.amount * 0.1
24-
Map.put(order, :total, order.amount + tax)
25-
end)
23+
add_tax =
24+
Primitives.transform(fn order ->
25+
tax = order.amount * 0.1
26+
Map.put(order, :total, order.amount + tax)
27+
end)
2628

2729
# Branch based on order size
28-
route_order = Primitives.branch(
29-
fn signal, _ -> signal.data.total > 1000 end,
30-
[fn signal, state -> {Signal.emit(:large_order, signal.data), state} end],
31-
[fn signal, state -> {Signal.emit(:standard_order, signal.data), state} end]
32-
)
30+
route_order =
31+
Primitives.branch(
32+
fn signal, _ -> signal.data.total > 1000 end,
33+
[fn signal, state -> {Signal.emit(:large_order, signal.data), state} end],
34+
[fn signal, state -> {Signal.emit(:standard_order, signal.data), state} end]
35+
)
3336

3437
# Create notification for large orders
35-
notify_large_order = Primitives.notify(
36-
[:console],
37-
format: fn order -> "Large order received: ##{order.id} (Total: $#{order.total})" end
38-
)
38+
notify_large_order =
39+
Primitives.notify(
40+
[:console],
41+
format: fn order -> "Large order received: ##{order.id} (Total: $#{order.total})" end
42+
)
3943

4044
# Compose the workflow
4145
large_order_flow = [
@@ -69,6 +73,7 @@ defmodule DataProcessing do
6973
case Flow.process(large_order_flow, signal, state2) do
7074
{:ok, result, _final_state} ->
7175
IO.puts("Large order processed: #{inspect(result)}")
76+
7277
{:error, reason} ->
7378
IO.puts("Error processing large order: #{reason}")
7479
end
@@ -77,6 +82,7 @@ defmodule DataProcessing do
7782
case Flow.process(standard_order_flow, signal, state2) do
7883
{:ok, result, _final_state} ->
7984
IO.puts("Standard order processed: #{inspect(result)}")
85+
8086
{:error, reason} ->
8187
IO.puts("Error processing standard order: #{reason}")
8288
end

0 commit comments

Comments
 (0)