Oi (oi v0.3.0)
Copy MarkdownOi means Orchid integration.
Core concepts
Graph → Compile → Execute — three-phase pipeline:
- Build a
Graphof step nodes and edges Oi.compile(graph)— topology → staticCompiled(bundles + plan, reusable)Oi.execute(compiled, opts)— resolve inputs, apply orchid_adapters, run via pluggable executor
- Build a
Compiled — immutable compilation product. Compile once, dispatch many times with different inputs / interventions / executors.
Inputs vs Interventions — two kinds of external data:
inputs:—%{"io_key" => payload}seeded into drafting memory before dispatchinterventions:—%{{:port, node, port} => {type, payload}}merged into Orchid baggage, consumed byOrchid.Hook.ApplyInterventionsper-step
Executor — pluggable task fan-out per stage. Built-in:
Sync(serial),TaskSup(Task.Supervisor).Pool(NimblePool) planned.Session — optional multi-tenant isolation via
Oi.Runtime.Session, wrapping per-tenantOrchidSymbiont.Runtime+Task.Supervisor.Extension model — Oi intentionally does not implement its own workflow plugin system. Use Orchid hooks through
:orchid_optsfor step-level concerns such as auth, logging, metrics, retry, and caching.
Quick start
Define a step
defmodule MyApp.Steps.Upcase do
use Oi.Step, name: :upcase
manifest(
inputs: [:text],
outputs: [result: :string]
)
routine text, _opts do
text |> String.upcase() |> ok()
end
endBuild and run
alias Oi.Topology.Graph
alias Oi.Topology.Graph.Node
graph =
Graph.new()
|> Graph.add_node(%Node{
id: :up,
container: MyApp.Steps.Upcase,
inputs: [:text],
outputs: [:result]
})
{:ok, compiled} = Oi.compile(graph)
{:ok, result} = Oi.execute(compiled,
inputs: %{"up|text" => "hello"}
)
Oi.Result.reify(result, "up|result")
# => {:ok, "HELLO"}With interventions
# Use vanilla orchid with custom opts(without orchid_adapters)
{:ok, result} = Oi.execute(compiled,
inputs: %{"step1|in" => "hello"},
interventions: %{
{:port, :step2, :in} => {:override, "forced_value"}
},
orchid_opts: [
global_hooks_stack: [Orchid.Hook.ApplyInterventions]
]
)Multi-tenant with Session
Oi.Runtime.Session.start("tenant-1")
graph = build_graph()
{:ok, compiled} = Oi.compile(graph)
{:ok, result} = Oi.execute(compiled,
inputs: %{"step|in" => "data"},
executor: Oi.Executor.TaskSup,
executor_opts: [sup: Oi.Runtime.Session.tasks_tuple("tenant-1")],
orchid_baggage: %{scope_id: "tenant-1"},
orchid_opts: [global_hooks_stack: [OrchidSymbiont.Hooks.Injector]]
)
Oi.Runtime.Session.stop("tenant-1")Symbiont step
defmodule MyApp.Steps.Predict do
use Oi.Step, name: :predict, symbiont?: true
manifest(
inputs: [:features],
outputs: [prediction: :string],
models: [:model]
)
routine features, models, _opts do
{:ok, result} = OrchidSymbiont.call(models.model, {:predict, features})
ok(result)
end
end
Summary
Functions
Compile graph into static bundles + plan.
Execute compiled plan with inputs and interventions.
Types
@type name() :: String.t()
Functions
@spec compile(Oi.Topology.Graph.t(), Oi.Topology.Cluster.t()) :: {:ok, Oi.Compiled.t()} | {:error, :cycle_detected}
Compile graph into static bundles + plan.
Pure topology — no interventions. Reusable across different intervention sets and inputs.
@spec execute( Oi.Compiled.t(), keyword() ) :: {:ok, Oi.Result.t()} | {:error, term()}
Execute compiled plan with inputs and interventions.
Options
:inputs— map of io_key => payload, seeded into drafting as initial memory:interventions— map of{:port, node, port} => {type, payload}. Types::override,:offset,:custometc. Resolved per-bundle by Worker. Note: interventions are NOT for external inputs — use:inputsfor that.:executor—Oi.Executor.Sync(default),Oi.Executor.TaskSup, orOi.Executor.Pool:executor_opts— passed to the executor (e.g.[sup: MyTaskSup]):orchid_adapters— OrchidPlugin pipeline:orchid_baggage— merged into Orchid run baggage
Examples
# Compile once
{:ok, compiled} = Oi.compile(graph, cluster)
# Execute with inputs + interventions
{:ok, result} = Oi.execute(compiled,
inputs: %{"step1|in" => "foo"},
interventions: %{{:port, :step1, :in} => {:override, "Bar"}}
)
# Same compiled plan, different inputs
{:ok, result_b} = Oi.execute(compiled, inputs: %{"step1|in" => "baz"})
# With Task.Supervisor
{:ok, result} = Oi.execute(compiled,
executor: Oi.Executor.TaskSup,
executor_opts: [sup: Oi.Runtime.Session.tasks_tuple("svs-1")]
)