Packages

A lightweight, signal-driven workflow framework for building dynamic systems

Current section

Files

Jump to
agent_forge lib agent_forge.ex
Raw

lib/agent_forge.ex

defmodule AgentForge do
@moduledoc ~S"""
AgentForge is a lightweight, signal-driven workflow framework designed for personal projects.
## Key Features
- Signal-based communication
- Composable handler pipelines
- Persistent state management
- Debug tracing support
## Example
# Define a simple handler
message_handler = fn signal, state ->
new_state = Map.put(state, :last_message, signal.data)
{AgentForge.Signal.emit(:processed, "Got: #{signal.data}"), new_state}
end
# Create a flow with the handler
flow = AgentForge.new_flow([message_handler], debug: true)
# Execute the flow with a signal
{:ok, result, _state} = flow.(AgentForge.Signal.new(:message, "Hello"))
# result.data will be "Got: Hello"
"""
alias AgentForge.{Signal, Runtime}
@doc """
Creates a new flow with the given handlers and options.
Returns a function that can be used to execute the flow.
## Options
All options from `AgentForge.Runtime.execute/3` are supported.
## Examples
iex> handler = fn signal, state ->
...> {AgentForge.Signal.emit(:echo, signal.data), state}
...> end
iex> flow = AgentForge.new_flow([handler])
iex> {:ok, result, _} = flow.(AgentForge.Signal.new(:test, "hello"))
iex> result.data
"hello"
"""
def new_flow(handlers, opts \\ []) do
Runtime.configure(handlers, opts)
end
@doc """
Creates a new stateful flow that maintains state between executions.
Similar to new_flow/2 but automatically stores and retrieves state.
## Examples
iex> {:ok, _pid} = AgentForge.Store.start_link(name: :test_store)
iex> counter = fn _signal, state ->
...> count = Map.get(state, :count, 0) + 1
...> {AgentForge.Signal.emit(:count, count), Map.put(state, :count, count)}
...> end
iex> flow = AgentForge.new_stateful_flow([counter], store_name: :test_store)
iex> {:ok, result1, _} = flow.(AgentForge.Signal.new(:inc, nil))
iex> {:ok, result2, _} = flow.(AgentForge.Signal.new(:inc, nil))
iex> result2.data > result1.data
true
"""
def new_stateful_flow(handlers, opts \\ []) do
Runtime.configure_stateful(handlers, opts)
end
@doc """
Processes a flow with execution limits.
This can prevent long-running operations.
## Options
* `:timeout_ms` - Maximum execution time in milliseconds (default: 30000)
* `:collect_stats` - Whether to collect execution statistics (default: true)
* `:return_stats` - Whether to return statistics in the result (default: false)
## Examples
iex> handlers = [
...> fn _signal, state -> {{:emit, AgentForge.Signal.new(:done, "Success")}, state} end
...> ]
iex> {:ok, result, _} = AgentForge.process_with_limits(handlers, AgentForge.Signal.new(:test, "data"), %{})
iex> result.data
"Success"
"""
@spec process_with_limits(
# handler functions
list(function()),
# input signal
Signal.t(),
# initial state
map(),
# options
keyword()
) ::
{:ok, Signal.t() | term(), term()}
| {:ok, Signal.t() | term(), term(), AgentForge.ExecutionStats.t()}
| {:error, term(), term()}
| {:error, term(), term(), AgentForge.ExecutionStats.t()}
def process_with_limits(handlers, signal, initial_state, opts \\ []) do
# Use Runtime.execute_with_limits instead of directly calling Flow.process_with_limits
# This ensures proper state persistence between executions
Runtime.execute_with_limits(
handlers,
signal,
opts |> Keyword.put(:initial_state, initial_state)
)
end
@doc """
Gets statistics from the last flow execution.
Returns nil if no flow has been executed yet or statistics collection was disabled.
## Examples
iex> handlers = [fn signal, state -> {{:emit, signal}, state} end]
iex> signal = AgentForge.Signal.new(:test, "data")
iex> {:ok, _, _} = AgentForge.process_with_limits(handlers, signal, %{})
iex> stats = AgentForge.get_last_execution_stats()
iex> stats.steps
1
"""
def get_last_execution_stats do
Runtime.get_last_execution_stats()
end
# Re-export commonly used functions from Signal module
defdelegate new_signal(type, data, meta \\ %{}), to: Signal, as: :new
defdelegate emit(type, data, meta \\ %{}), to: Signal
defdelegate emit_many(signals), to: Signal
defdelegate halt(value), to: Signal
end