# Getting Started
This guide covers the essential workflow authoring and integration concepts. It introduces runtime, reliability, and operations features incrementally.
> ### Learn with Livebook
>
> The fastest way to start is the interactive Livebook. It demonstrates workflow creation, step modules, run inspection, and approval flows.
> [](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fdark-trench%2Fsquidie%2Fblob%2Fmain%2Fdocs%2Fgetting_started.livemd)
For production integration, follow the steps below. They introduce retries, manual gates, cron, child runs, and Bedrock leases after establishing the base execution loop.
## Mental Model
Squidie has three boundaries:
1. **Workflow definition** - a compiled Elixir module that declares triggers,
payload fields, steps, transitions, retries, waits, approvals, and recovery
markers.
2. **Journal runtime** - the Jido-native runtime that records run, dispatch,
attempt, manual-control, and terminal facts in durable storage.
3. **Host execution** - supervised host processes that call
`Squidie.execute_next/1`, plus optional schedulers or lease-capable
backends such as Bedrock.
The workflow definition says what should happen. The journal says what did
happen and what is ready next. Host workers provide capacity; they do not own
workflow state.
## 1. Install The Runtime
Start with the smallest embedded setup:
```elixir
config :squidie,
repo: MyApp.Repo,
queue: "default"
```
Install and run the migration:
```sh
mix squidie.install
mix ecto.migrate
```
This creates a Jido-backed journal store in the host repo. Production stores must provide ordered per-thread appends, optimistic conflict detection, and durable checkpoint reads.
Read next: [Host app integration](host_app_integration.md).
## 2. Write A Small Workflow
Workflow authors should think in business steps, not agents or jobs:
```elixir
defmodule MiddleEarth.Workflows.RingErrand do
use Squidie.Workflow
workflow do
trigger :leave_shire do
manual()
payload do
field :bearer, :string, default: "Frodo"
field :ring_id, :string
end
end
step :pack_lembas, Hobbiton.Steps.PackLembas
step :cross_moria, Fellowship.Steps.CrossMoria,
retry: [max_attempts: 3]
step :reach_mordor, Mordor.Steps.ReachMordor
transition :pack_lembas, on: :ok, to: :cross_moria
transition :cross_moria, on: :ok, to: :reach_mordor
transition :reach_mordor, on: :ok, to: :complete
end
end
```
Prefer `use Squidie.Step` for custom step modules. Raw `Jido.Action` modules
remain available for interop, but the Squidie step contract keeps workflow
code easier to read.
Read next: [Workflow authoring](workflow_authoring.md), or run the
[workflow-authoring Livebook](workflow_authoring.livemd) for an interactive
dependency and input-mapping walkthrough.
## 3. Start And Drain A Run
Manual triggers start through the public API:
```elixir
{:ok, run} =
Squidie.start(
MiddleEarth.Workflows.RingErrand,
:leave_shire,
%{ring_id: "one-ring"}
)
```
Inspection keeps explicit names such as `inspect_run/2` and
`inspect_run_graph/2` rather than adding an `inspect/2` alias.
Public start, replay, and control helpers use concise names: `start/3`,
`resume/3`, `approve/3`, `reject/3`, `cancel/2`, and `replay/2`.
`Squidie.Runtime.Signal` constructors keep run-suffixed names because those
names describe persisted command intent.
Workers drain journal attempts:
```elixir
Squidie.execute_next(owner_id: "worker-1")
```
Wrap this call in a supervised worker loop. Start simple: call `execute_next/1`, back off when it returns `{:ok, :none}`, then add metrics and capacity controls as needed.
Read next: [Host app integration](host_app_integration.md#journal-worker-contract).
## 4. Start Child Runs When Work Expands
When a native step discovers runtime work that should have its own durable
history, start a child workflow from that step's `Squidie.Step.Context`:
```elixir
defmodule Hobbiton.Steps.SendPartyInvites do
use Squidie.Step, name: :send_party_invites
@impl true
def run(%{party_id: party_id, guests: guests}, %Squidie.Step.Context{} = context) do
children =
for guest <- guests do
{:ok, child} =
Squidie.start_child_run(
context,
Hobbiton.Workflows.DeliverInvite,
%{party_id: party_id, guest_id: guest.id},
child_key: "invite_#{guest.id}",
metadata: %{guest_id: guest.id}
)
child.run_id
end
{:ok, %{invite_run_ids: children}}
end
end
```
Each child is a normal journal run with its own inspection, retry, replay, and
cancellation boundary. The `child_key` makes the start idempotent for the
parent run and parent step, so step retries do not create duplicate children.
Read next: [Workflow authoring](workflow_authoring.md#child-workflow-runs).
## 5. Inspect What Happened
Every run should be explainable from durable facts:
```elixir
{:ok, run} = Squidie.inspect_run(run.run_id, include_history: true)
{:ok, explanation} = Squidie.explain_run(run.run_id)
```
Use list APIs for dashboard indexes and inspection APIs for details:
```elixir
{:ok, runs} = Squidie.list_runs([])
{:ok, graph} = Squidie.inspect_run_graph(run.run_id)
```
This is the surface SquidSonar and other tooling should build on: list runs by
workflow or globally, then fetch one run's graph, history, and explanation by
id.
Read next: [Architecture](architecture.md) and
[Jido runtime architecture](jido_runtime_architecture.md).
## 6. Add Reliability Deliberately
Retries, waits, and recovery routes are workflow semantics, not job-backend
accidents.
Use retries for recoverable steps:
```elixir
step :cross_moria, Fellowship.Steps.CrossMoria,
retry: [max_attempts: 5, backoff: [type: :exponential, min: 1_000, max: 30_000]]
```
Use waits for workflow-scale delays:
```elixir
step :wait_for_gandalf, :wait, duration: 30_000
```
Use recovery markers when an error path has a business meaning:
```elixir
transition :cross_moria,
on: :error,
to: :walk_home_awkwardly,
recovery: :compensation
```
Keep external side effects idempotent. Squidie can fence stale workflow
mutations, but it cannot make a payment provider, email API, or webhook exactly
once.
Read next: [Operations](operations.md).
## 7. Add Human Boundaries
Manual steps are durable workflow state:
```elixir
approval_step :wait_for_council, output: :approval
transition :wait_for_council, on: :ok, to: :cross_moria
transition :wait_for_council, on: :error, to: :walk_home_awkwardly
```
Operators resolve them through public APIs:
```elixir
Squidie.approve(run_id, %{actor: "ops_123", comment: "verified"})
Squidie.reject(run_id, %{actor: "ops_123", comment: "fraud risk"})
```
Inspection history keeps pause, approval, rejection, and resume facts visible
with the rest of the run history.
## 8. Add Cron Only When Needed
Cron triggers declare schedule intent in the workflow, but the host app owns
the recurring scheduler:
```elixir
trigger :daily_digest do
cron "0 9 * * 1-5", timezone: "Etc/UTC", idempotency: :return_existing_run
end
```
The scheduler should deliver a `Squidie.Executor.Payload.cron/3` payload to
`Squidie.Runtime.Runner.perform/2`. Step and compensation payloads are not
part of the journal-backed runtime contract.
For idempotent cron starts, pass a stable `signal_id` or a complete
`intended_window` so duplicate scheduler delivery returns or skips the existing
run instead of starting a second one.
## 9. Use Bedrock For Backend-Owned Leases
The core runtime stays backend-neutral. A basic host can run a worker loop that
calls `execute_next/1`; a larger host can use a durable backend for delivery
and lease ownership.
Bedrock is the recommended reference backend today because the example app
already covers durable queueing, delayed visibility, claims, heartbeats,
completion, retry, and dead-letter behavior. That path is useful when multiple
workers or nodes may compete for work and the host wants backend-owned lease
semantics around the Squidie journal.
Read next: [Bedrock setup](host_app_integration.md#bedrock-lease-backend-setup)
and the [Bedrock minimal host app](../examples/bedrock_minimal_host_app/README.md).
## Common Gotchas
| Gotcha | What to do |
| --- | --- |
| Treating Squidie like only a job queue | Model business lifecycle in workflow steps, transitions, retries, waits, and manual boundaries. |
| Depending on external exactly-once behavior | Use idempotency keys, natural keys, or domain duplicate checks in side-effecting steps. |
| Hiding decisions in step internals | Put branches, manual gates, retries, and recovery routes in the workflow where inspection can explain them. |
| Using long waits as general timers | Use waits for workflow-scale delays; use host scheduling when the whole run should start later. |
| Letting delivery code own workflow rules | Keep delivery and job boundaries thin; call host-owned modules that wrap Squidie public APIs. |
| Assuming every database is a good journal store | Keep the adapter boundary database-agnostic, but require ordered appends, conflict detection, and durable checkpoint reads for production. |
## Where To Go Next
- New host app setup: [Host app integration](host_app_integration.md)
- Workflow syntax and examples: [Workflow authoring](workflow_authoring.md)
- Interactive workflow authoring:
[Workflow authoring Livebook](workflow_authoring.livemd)
- Executable product examples: [Reference workflows](reference_workflows.md)
- Runtime internals: [Jido runtime architecture](jido_runtime_architecture.md)
- Journal protocol details: [Durable dispatch protocol](durable_dispatch_protocol.md)
- Operating guidance: [Operations](operations.md)
- Telemetry and logs: [Observability](observability.md)
- Current release bar: [Production readiness](production_readiness.md)