Current section

Files

Jump to
squid_mesh README.md
Raw

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://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>
## Features
- Define workflows with triggers, payload contracts, steps, transitions, and retries.
- Run them durably on top of your app's existing `Repo` and `Oban`.
- Inspect runs with step and attempt history.
- Replay, cancel, and schedule recurring runs without inventing the runtime yourself.
> [!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.
## Quick Start
Requirements:
- an existing Elixir application
- an existing Ecto `Repo`
- Postgres for persisted runtime state
- an existing `Oban` setup for background execution
- step modules that can run as Jido actions when you want custom steps
### 1. Add the dependency
```elixir
defp deps do
[
{:squid_mesh, "~> 0.1.0-alpha.1"}
]
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.1"}
]
end
```
For direct Git evaluation:
```elixir
defp deps do
[
{:squid_mesh, github: "ccarvalho-eng/squid_mesh", tag: "v0.1.0-alpha.1"}
]
end
```
### 2. Configure Squid Mesh
```elixir
config :squid_mesh,
repo: MyApp.Repo,
execution: [
name: Oban,
queue: :squid_mesh
]
```
### 3. Install Squid Mesh 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`. It does not manage `oban_jobs`; embedded applications
are expected to use their own existing `Oban` setup.
If you are wiring Squid Mesh into a fresh app, add the host app's `Oban`
migration first:
```elixir
defmodule MyApp.Repo.Migrations.AddObanJobs do
use Ecto.Migration
def up, do: Oban.Migrations.up()
def down, do: Oban.Migrations.down()
end
```
### 4. Activate cron workflows if needed
Cron triggers are declared in workflows and activated through the host app's
Oban plugins:
```elixir
config :my_app, Oban,
repo: MyApp.Repo,
plugins: [
{SquidMesh.Plugins.Cron,
workflows: [
MyApp.Workflows.DailyDigest
]}
],
queues: [squid_mesh: 10]
```
## Runtime Overview
- Squid Mesh owns workflow structure, run state, step state, retries, replay, and inspection.
- Oban owns durable execution, scheduling, and job redelivery.
- Jido provides the action contract for custom workflow steps.
- Postgres stores runs, step runs, attempts, and queued execution state.
Squid Mesh does not try to re-implement worker coordination that Oban already
provides. It records workflow state, then inserts and schedules step jobs
through Oban for first execution, step progression, and retries. Oban is the
durable execution layer underneath that flow; Squid Mesh remains the workflow
runtime and source of truth for what should happen next.
## Example Uses
- Post an RSS digest to Discord every morning.
- Turn a Linear issue into a planning workflow for your team.
- Run recovery, approval, and back-office flows inside Phoenix apps.
## Cron Workflow Example: Daily RSS To Discord
```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)
step(:build_digest, Content.Steps.BuildDigest)
step(:post_to_discord, Content.Steps.PostToDiscord,
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: :post_to_discord)
transition(:post_to_discord, on: :ok, to: :complete)
end
end
```
## Manual Workflow Example: Plan A Linear Task
```elixir
defmodule Planning.Workflows.PlanLinearTask do
use SquidMesh.Workflow
workflow do
trigger :plan_task do
manual()
payload do
field(:linear_issue_id, :string)
field(:requester_id, :string)
end
end
step(:load_issue, Planning.Steps.LoadLinearIssue)
step(:draft_plan, Planning.Steps.DraftExecutionPlan)
step(:attach_plan, Planning.Steps.AttachPlanToLinearIssue)
transition(:load_issue, on: :ok, to: :draft_plan)
transition(:draft_plan, on: :ok, to: :attach_plan)
transition(:attach_plan, on: :ok, to: :complete)
end
end
```
## Step Example
```elixir
defmodule Content.Steps.PostToDiscord do
use Jido.Action,
name: "post_to_discord",
description: "Posts the digest to Discord",
schema: [
discord_webhook_url: [type: :string, required: true],
digest: [type: :string, required: true]
]
@impl true
def run(%{discord_webhook_url: webhook_url, digest: digest}, _context) do
case SquidMesh.Tools.invoke(SquidMesh.Tools.HTTP, %{
method: :post,
url: webhook_url,
json: %{content: digest}
}) do
{:ok, result} ->
{:ok, %{discord_status: result.payload.status}}
{:error, error} ->
{:error, SquidMesh.Tools.Error.to_map(error)}
end
end
end
```
## Call It From Your App
```elixir
defmodule Planning.WorkflowRuns do
def plan_linear_task(linear_issue_id, requester_id) do
SquidMesh.start_run(Planning.Workflows.PlanLinearTask, %{
linear_issue_id: linear_issue_id,
requester_id: requester_id
})
end
end
```
If a workflow defines a single trigger, `SquidMesh.start_run/2` uses that
trigger automatically.
Inspect a run with history:
```elixir
SquidMesh.inspect_run(run_id, include_history: true)
```
Workflows can mix:
- custom step modules for domain behavior
- built-in `:wait` and `:log` steps
- tool adapters like `SquidMesh.Tools.HTTP` for normalized integrations
Retry behavior stays on the step that owns the work:
```elixir
step(:check_gateway_status, Billing.Steps.CheckGatewayStatus,
retry: [max_attempts: 5, backoff: [type: :exponential, min: 1_000, max: 30_000]]
)
```
## Operational Boundaries
Current guarantees:
- runs, steps, and attempts are durable in Postgres
- queued and scheduled work survives restarts through Oban
- retry, replay, inspection, and cancellation operate on persisted run state
Current non-goals:
- exactly-once external effects without idempotent step design
- custom worker leases or heartbeats beyond Oban
- dynamic cron registration after boot
## Documentation
- [HexDocs](https://hexdocs.pm/squid_mesh)
- [Compatibility matrix](docs/compatibility.md)
- [Workflow authoring guide](docs/workflow_authoring.md)
- [Host app integration](docs/host_app_integration.md)
- [Architecture](docs/architecture.md)
- [Operations guide](docs/operations.md)
- [Example host app](examples/minimal_host_app/README.md)
## Contributing
- [Contributing guide](CONTRIBUTING.md)
- [Code of conduct](CODE_OF_CONDUCT.md)
For a standalone development harness with its own `Repo` and `Oban`, use
`examples/minimal_host_app`.
Fast local smoke path:
```sh
cd examples/minimal_host_app
MIX_ENV=test mix example.smoke
```