README.md

<div align="center">
  <h2>🦑 Squid Mesh</h2>

  <p><i>Workflow automation platform for Elixir applications.</i></p>

  <p>
    <a href="https://github.com/ccarvalho-eng/squid_mesh/actions/workflows/ci.yml">
      <img alt="CI" src="https://github.com/ccarvalho-eng/squid_mesh/actions/workflows/ci.yml/badge.svg" />
    </a>
    <a href="https://hex.pm/packages/squid_mesh">
      <img alt="Hex" src="https://img.shields.io/hexpm/v/squid_mesh" alt="Hex.pm"/></a>
    </a>
    <a href="https://hexdocs.pm/squid_mesh">
      <img alt="HexDocs" src="https://img.shields.io/badge/docs-hexdocs-purple" />
    </a>
    <a href="https://elixirforum.com/t/squid-mesh-workflow-automation-runtime-for-elixir-applications/75162">
      <img alt="Elixir Forum" src="https://img.shields.io/badge/Elixir_Forum-Join_Discussion-4B275F?logo=elixir&logoColor=white" />
    </a>
    <a href="https://github.com/ccarvalho-eng/squid_mesh/blob/main/LICENSE">
      <img alt="License: Apache 2.0" src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" />
    </a>
  </p>
</div>

Squid Mesh lets Phoenix and OTP applications define, run, inspect, replay, and
recover durable workflows in code.

> <i>The name blends a squid’s coordinated arms with a mesh of connected
> workflow steps, capturing the idea of orchestrating many moving parts without
> rebuilding the coordination layer in every app.</i>

> [!WARNING]
> Squid Mesh is still in early development. The runtime is suitable for
> evaluation, local development, and integration work, but it is not yet
> positioned as production-ready. See
> [Production Readiness](docs/production_readiness.md) for the current
> checklist and remaining bar.

## What You Get

- declarative workflows with manual and cron triggers
- durable run, step, and attempt history in Postgres
- step-level retries, delays, replay, and inspection on top of your existing `Oban`
- built-in steps like `:log` and `:wait`, plus custom steps with `Jido.Action`

## Runtime Shape

- Squid Mesh owns workflow structure, payload validation, runtime state, and retry policy
- Oban owns durable execution, queueing, delayed scheduling, and redelivery
- your host app keeps its existing `Repo`, `Oban`, and application boundaries

## Quick Start

Requirements:

- an existing Elixir application
- an existing Ecto `Repo`
- Postgres for persisted runtime state
- an existing `Oban` setup for background execution

### 1. Install from Hex.pm

```elixir
defp deps do
  [
    {:squid_mesh, "~> 0.1.0-alpha.2"}
  ]
end
```

If the host app defines custom steps with `use Jido.Action`, add `:jido`
explicitly as well:

```elixir
defp deps do
  [
    {:jido, "~> 2.0"},
    {:squid_mesh, "~> 0.1.0-alpha.2"}
  ]
end
```

### 2. Configure Squid Mesh and Oban

```elixir
config :squid_mesh,
  repo: MyApp.Repo,
  execution: [
    name: Oban,
    queue: :squid_mesh
  ]

config :my_app, Oban,
  repo: MyApp.Repo,
  queues: [squid_mesh: 10]
```

The host app's `Oban` config must include the `:squid_mesh` queue when Squid
Mesh is using that queue name.

### 3. Install migrations

```sh
mix deps.get
mix squid_mesh.install
mix ecto.migrate
```

`mix squid_mesh.install` copies only Squid Mesh tables into the host app's
`priv/repo/migrations`. The host app still owns its `Oban` setup and
`oban_jobs` migration.

## Example: Daily RSS To Discord

This kind of workflow is where Squid Mesh gets interesting: one cron trigger,
typed payload defaults, built-in steps, custom steps, explicit failure routing,
and step-level retry on the side effect that actually needs it.

```elixir
defmodule Content.Workflows.PostDailyDigest do
  use SquidMesh.Workflow

  workflow do
    trigger :daily_digest do
      cron("0 9 * * 1-5", timezone: "Etc/UTC")

      payload do
        field(:feed_url, :string, default: "https://example.com/feed.xml")
        field(:discord_webhook_url, :string)
        field(:posted_on, :string, default: {:today, :iso8601})
      end
    end

    step(:fetch_feed, Content.Steps.FetchFeed, output: :feed)
    step(:build_digest, Content.Steps.BuildDigest,
      input: [:feed, :posted_on],
      output: :digest
    )
    step(:announce_post, :log, message: "Posting digest to Discord", level: :info)
    step(:record_failed_delivery, Content.Steps.RecordFailedDelivery)

    step(:post_to_discord, Content.Steps.PostToDiscord,
      input: [:digest, :discord_webhook_url],
      retry: [max_attempts: 5, backoff: [type: :exponential, min: 1_000, max: 30_000]]
    )

    transition(:fetch_feed, on: :ok, to: :build_digest)
    transition(:build_digest, on: :ok, to: :announce_post)
    transition(:announce_post, on: :ok, to: :post_to_discord)
    transition(:post_to_discord, on: :ok, to: :complete)
    transition(:post_to_discord, on: :error, to: :record_failed_delivery)
    transition(:record_failed_delivery, on: :ok, to: :complete)
  end
end
```

The step modules can stay small and domain-focused, while Squid Mesh handles
durable state, scheduling through Oban, retries, failure routing after retry
exhaustion, and run inspection.

When a step needs a narrower contract than the whole payload plus accumulated
context, use `input: [...]` to select keys and `output: :key` to namespace the
returned map for downstream steps.

Start the workflow through the public API and inspect the result with history:

```elixir
{:ok, run} =
  SquidMesh.start_run(Content.Workflows.PostDailyDigest, %{
    discord_webhook_url: webhook_url
  })

SquidMesh.inspect_run(run.id, include_history: true)
```

With history enabled, the inspected run includes both chronological `step_runs`
and a graph-aware `steps` view so host apps can render dependency workflows in a
useful order.

## Documentation

Use the docs index for setup, workflow authoring, operations, and architecture:

- [Docs index](docs/index.md)
- [Host app integration](docs/host_app_integration.md)
- [Workflow authoring guide](docs/workflow_authoring.md)
- [Example host app](examples/minimal_host_app/README.md)

## Contributing

- [Contributing guide](CONTRIBUTING.md)
- [Code of conduct](CODE_OF_CONDUCT.md)