Packages

Deterministic workflow engine for agent steps on the BEAM: declared-as-data DAGs, compile-time validation passes, phased parallel execution.

Current section

Files

Jump to
Raw

README.md

# mimir_workflows
**Deterministic workflows for agent steps on the BEAM.**
`mimir_workflows` provides a `Step` behaviour, DAG operations, a declarative
string-keyed workflow IR with pluggable compile passes, and an in-memory
phase runner with concurrency caps and explicit failure results.
Kind-agnostic by design — the IR validates universal shape; `"kind"`
vocabulary belongs to the host. One engine serves a code-review pipeline and
an agent composition equally.
## 30 seconds
```elixir
spec_map = %{
"name" => "brief",
"version" => 1,
"params" => ["month"],
"steps" => [
%{"id" => "analyze", "kind" => "agent", "depends_on" => [],
"input" => %{"q" => "KPIs for {{params.month}}"}},
%{"id" => "narrate", "kind" => "agent",
"depends_on" => ["analyze"], "input" => "{{analyze}}"}
]
}
# Compile: schema/dag/refs built-ins + your own passes (policy, budget, kinds).
# Diagnostics accumulate across ALL passes — the caller sees every problem at once.
{:ok, spec, _warnings} = MimirWorkflows.Compiler.compile(spec_map, passes: [{MyApp.PolicyPass, tenant}])
# Lower IR steps onto your Step modules and run: minimal phases, parallel in-phase.
steps =
for step <- spec.steps do
%{id: step.id, module: MyApp.kind_module(step.kind),
params: step.raw, depends_on: step.depends_on}
end
{:ok, results} = MimirWorkflows.Runner.run(steps, telemetry_meta: %{workflow_id: "wf_1"})
MimirWorkflows.Result.usage(results)
#=> %{"input_tokens" => 1200, "output_tokens" => 340, "calls" => 2}
```
## Invariants
- **Depends on nothing in-house** — no `Mimir.*`, no `ManagedAgents.*`, no
RMA or Jido types (grep-enforced in CI). Only `:telemetry`.
- **Step ids are opaque terms, never atomized** — untrusted IR never mints
atoms; parsed specs keep string ids.
- Policy, budget, allowlists, routing, agent registries are **host
concerns**, delivered through `MimirWorkflows.Compiler.Pass` and step
params/modules. Governed agent execution lives one layer up, in
`mimir_orchestration`.
- **Graceful degradation** (convention): conditional steps stay in the DAG
and no-op (`{:ok, %{"skipped" => true}}`) rather than mutating graph shape
— the DAG stays static and analyzable.
## Telemetry
| event | measurements | metadata |
|---|---|---|
| `[:mimir_workflows, :run, :start]` | `system_time` | `run_ref` ∪ `telemetry_meta` |
| `[:mimir_workflows, :run, :stop]` | `duration` | `run_ref`, `status` |
| `[:mimir_workflows, :step, :start]` | `system_time` | `run_ref`, `step_id`, `phase` |
| `[:mimir_workflows, :step, :stop]` | `duration` | `run_ref`, `step_id`, `phase` |
| `[:mimir_workflows, :step, :exception]` | `duration` | + `reason` |
The `telemetry_meta` option is how hosts thread correlation ids into every
event without the library knowing what they mean.
## What this is not
No durability, persistence, resume, human-in-the-loop, or
compensation/rollback — a durable tier is a deliberate later decision, made
when a concrete workflow demands it, evaluated against existing OTP-native
options first. No conditionals in the IR (additive later). No workflow
registry (hosts persist their own declarations; the library compiles
values).