README.md

# spooks
An agentic workflow framework in Elixir

## Installation

You must run an ecto migration to create the `spooks_workflow_checkpoints` table if you plan on using checkpoints.

```elixir
defmodule MyApp.Repo.Migrations.AddSpooksTables do
  use Ecto.Migration

  def down do
    drop table("spooks_workflow_checkpoints")
  end

  def up do
    create table("spooks_workflow_checkpoints") do
      add :workflow_identifier, :string
      add :workflow_module, :string
      add :workflow_context, :map
      add :workflow_event_module, :string
      add :workflow_event, :map
      add :checkpoint_timeout, :naive_datetime

      timestamps()
    end

    create unique_index("spooks_workflow_checkpoints", [:workflow_identifier])
  end
end

```

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `spooks` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:spooks, "~> 0.1.0"}
  ]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/spooks>.

## Usage

Create your own workflow! Be sure you include the following line in your workflow module:

```elixir
use Spooks.Workflow
```

Create a struct for each event in your workflow. You can add whichever fields you like to your struct.

> [!NOTE]
> Note that all your events must `@derive Jason.Encoder` because they are saved to the database checkpoints.

```elixir
defmodule MyApp.MyCustomEvent do
  @derive Jason.Encoder
  defstruct []
end
```

In your workflow, add a function for each event. The name of the function *must* be the same as the event name only with underscores and with the final `_event` replaced with `_step`. Your function must take 2 arguments. The event and the context. It must return either the `{:ok, next_event, updated_ctx}` or `{:error, reason}` tuple.

For example:

```elixir
def my_custom_step(event, ctx) do
  next_event = ...
  udpated_ctx = ...
  {:ok, next_event, updated_ctx}
end
```

You _should_ add a `@step` annotation to your step functions. This is used for generating diagrams of your agentic workflow. 

`in` must be

- an event module name 

`out` can be 

- nil (the end of the agentic workflow)
- an event module name 
- a list of event module names (for branching)

```elixir
@step %Step{in: MyFirstEvent, out: MySecondEvent}
def my_first_step(event, ctx) do
  ...
end

@step %Step{in: MySecondEvent, out: [MyThirdEvent,StopEvent]}
def my_second_step(event, ctx) do
  ...
end

@step %Step{in: MyFirstEvent, out: nil}
def my_last_step(event, ctx) do
  {:ok, nil, ctx}
end
```

There are two built in events for `Spooks.Event.StartEvent` and `Spooks.Event.EndEvent`.

If you would like to add data or get data from the context you should use the following functions:

> [!HINT]
> You can also use a list of keys for `put_data` and `get_data` for nested data structures.

```elixir
@step %Step{in: MyFirstEvent, out: MySecondEvent}
def my_first_step(event, ctx) do
  new_ctx = Spooks.Context.SpooksContext.put_data(:greeting, "hello world!")
  {:ok, %MySecondEvent{}, new_ctx}
end

@step %Step{in: MySecondEvent, out: MyThirdEvent}
def my_second_step(event, ctx) do
  custom_greeting = Spooks.Context.SpooksContext.get_data(:greeting)
  IO.puts(custom_greeting)
  {:ok, %MyThirdEvent{}, ctx}
end
```

If you wish to save checkpoints after each step, pass in your repository to the workflow context.

```elixir
workflow = Spooks.Sample.SampleWorkflow
repo = MyApp.Repo

workflow_context = Spooks.Context.SpooksContext.new(workflow, repo)
Spooks.WorkflowEngine.run_workflow(workflow_context)
```

If you do not wish to save checkpoints:

```elixir
workflow = Spooks.Sample.SampleWorkflow
workflow_context = Spooks.Context.SpooksContext.new(workflow, nil)

Spooks.WorkflowEngine.run_workflow(workflow_context)
```